diff --git a/.editorconfig b/.editorconfig index 4e5cf63eb16e0..7400f6df98b17 100644 --- a/.editorconfig +++ b/.editorconfig @@ -237,7 +237,7 @@ csharp_preserve_single_line_statements = true # warning RS0005: Do not use generic CodeAction.Create to create CodeAction dotnet_diagnostic.RS0005.severity = none -[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures, VisualStudio}/**/*.{cs,vb}] +[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] # IDE0011: Add braces csharp_prefer_braces = when_multiline:warning diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 90960c656649f..518dc286e1e3f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -27,3 +27,7 @@ src/VisualStudio/ @dotnet/roslyn-ide src/VisualStudio/LiveShare @dotnet/roslyn-ide @daytonellwanger @srivatsn @aletsdelarosa src/Workspaces/ @dotnet/roslyn-ide +src/Compilers/**/PublicAPI.Unshipped.txt @dotnet/roslyn-api-owners +src/Workspaces/**/PublicAPI.Unshipped.txt @dotnet/roslyn-api-owners +src/Features/**/PublicAPI.Unshipped.txt @dotnet/roslyn-api-owners +src/EditorFeatures/**/PublicAPI.Unshipped.txt @dotnet/roslyn-api-owners diff --git a/.github/ISSUE_TEMPLATE/api-suggestion.md b/.github/ISSUE_TEMPLATE/api-suggestion.md new file mode 100644 index 0000000000000..5f6beb1283322 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/api-suggestion.md @@ -0,0 +1,56 @@ +--- +name: API proposal +about: Propose a change to the public API surface +title: '' +labels: [Feature Request, Concept-API] +assignees: '' + +--- + +## Background and Motivation + + + +## Proposed API + + + +```diff +namespace Microsoft.CodeAnalysis.Operations +{ + public class ISwitchExpressionOperation + { ++ public bool IsExhaustive { get; } + } +``` + +## Usage Examples + + + +## Alternative Designs + + + +## Risks + + diff --git a/README.md b/README.md index 4c9cfcf9a8a02..248a13804b4e5 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ If you are interested in fixing issues and contributing directly to the code bas - [Submitting pull requests](https://github.com/dotnet/roslyn/blob/main/CONTRIBUTING.md) - Finding a bug to fix in the [IDE](https://aka.ms/roslyn-ide-bugs-help-wanted) or [Compiler](https://aka.ms/roslyn-compiler-bugs-help-wanted) - Finding a feature to implement in the [IDE](https://aka.ms/roslyn-ide-feature-help-wanted) or [Compiler](https://aka.ms/roslyn-compiler-feature-help-wanted) +- Roslyn API suggestions should go through the [API review process]() ### Community diff --git a/Roslyn.sln b/Roslyn.sln index 303f630b773cb..2802588d0e8fd 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28503.202 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31319.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoslynDeployment", "src\Deployment\RoslynDeployment.csproj", "{600AF682-E097-407B-AD85-EE3CED37E680}" EndProject @@ -489,6 +489,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.Razor.UnitTests", "src\Tools\ExternalAccess\RazorTest\Microsoft.CodeAnalysis.ExternalAccess.Razor.UnitTests.csproj", "{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp", "src\Tools\ExternalAccess\OmniSharp\Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.csproj", "{1B73FB08-9A17-497E-97C5-FA312867D51B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp", "src\Tools\ExternalAccess\OmniSharp.CSharp\Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp.csproj", "{AE976DE9-811D-4C86-AEBB-DCDC1226D754}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests", "src\Tools\ExternalAccess\OmniSharpTest\Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests.csproj", "{3829F774-33F2-41E9-B568-AE555004FC62}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 @@ -1272,6 +1278,18 @@ Global {BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Release|Any CPU.Build.0 = Release|Any CPU + {1B73FB08-9A17-497E-97C5-FA312867D51B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B73FB08-9A17-497E-97C5-FA312867D51B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B73FB08-9A17-497E-97C5-FA312867D51B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B73FB08-9A17-497E-97C5-FA312867D51B}.Release|Any CPU.Build.0 = Release|Any CPU + {AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Release|Any CPU.Build.0 = Release|Any CPU + {3829F774-33F2-41E9-B568-AE555004FC62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3829F774-33F2-41E9-B568-AE555004FC62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3829F774-33F2-41E9-B568-AE555004FC62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3829F774-33F2-41E9-B568-AE555004FC62}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1495,6 +1513,9 @@ Global {B7D29559-4360-434A-B9B9-2C0612287999} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} {21B49277-E55A-45EF-8818-744BCD6CB732} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} {BB987FFC-B758-4F73-96A3-923DE8DCFF1A} = {8977A560-45C2-4EC2-A849-97335B382C74} + {1B73FB08-9A17-497E-97C5-FA312867D51B} = {8977A560-45C2-4EC2-A849-97335B382C74} + {AE976DE9-811D-4C86-AEBB-DCDC1226D754} = {8977A560-45C2-4EC2-A849-97335B382C74} + {3829F774-33F2-41E9-B568-AE555004FC62} = {8977A560-45C2-4EC2-A849-97335B382C74} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} diff --git a/azure-pipelines-integration-lsp.yml b/azure-pipelines-integration-lsp.yml index d436a7353ad62..92b3e4151ef29 100644 --- a/azure-pipelines-integration-lsp.yml +++ b/azure-pipelines-integration-lsp.yml @@ -8,7 +8,9 @@ trigger: - features/* - demos/* -# Branches that trigger builds on PR +# Branches that are allowed to trigger a build via /azp run. +# Automatic building of all PRs is disabled in the pipeline's trigger page. +# See https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#comment-triggers pr: - main - main-vs-deps diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 79d14e429b0cb..91df7e50c4dd0 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -45,6 +45,12 @@ stages: displayName: Build and Test jobs: + - template: /eng/common/templates/job/onelocbuild.yml + parameters: + CreatePr: false + LclSource: lclFilesfromPackage + LclPackageId: 'LCL-JUNO-PROD-ROSLYN' + - job: OfficialBuild displayName: Official Build timeoutInMinutes: 360 @@ -58,7 +64,7 @@ stages: - DotNetFramework ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: NetCoreInternal-Pool - queue: BuildPool.Windows.10.Amd64.VS2019.Pre + queue: BuildPool.Server.Amd64.VS2017 steps: # Make sure our two pipelines generate builds with distinct build numbers to avoid confliction. @@ -71,6 +77,7 @@ stages: arguments: '-buildNumber $(Build.BuildNumber) -oddOrEven even' ${{ if eq(variables['System.TeamProject'], 'internal') }}: arguments: '-buildNumber $(Build.BuildNumber) -oddOrEven odd' + condition: eq(variables['System.JobAttempt'], '1') - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" displayName: Setting SourceBranchName variable @@ -130,6 +137,17 @@ stages: ${{ if eq(variables['System.TeamProject'], 'internal') }}: nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk, devdiv/engineering + # Needed because the dnceng image we build on fails the next task without it + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + useGlobalJson: true + workingDirectory: '$(Build.SourcesDirectory)' + installationPath: '$(Build.SourcesDirectory)/.dotnet' + condition: eq(variables['System.TeamProject'], 'internal') + + # Needed to restore the Microsoft.DevDiv.Optimization.Data.PowerShell package - task: NuGetCommand@2 displayName: Restore internal tools inputs: @@ -193,7 +211,7 @@ stages: ARTIFACTSERVICES_DROP_PAT: $(_DevDivDropAccessToken) inputs: dropServiceURI: 'https://devdiv.artifacts.visualstudio.com' - buildNumber: 'ProfilingInputs/DevDiv/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)' + buildNumber: 'ProfilingInputs/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)' sourcePath: '$(Build.SourcesDirectory)\artifacts\OptProf\$(BuildConfiguration)\Data' toLowerCase: false usePat: false @@ -320,6 +338,7 @@ stages: - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: eng\common\templates\post-build\post-build.yml parameters: + publishingInfraVersion: 3 # Symbol validation is not entirely reliable as of yet, so should be turned off until # https://github.com/dotnet/arcade/issues/2871 is resolved. enableSymbolValidation: false diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 13d6ece1c57a7..c3d4926f3c653 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -118,7 +118,7 @@ jobs: jobName: Build_Unix_Debug testArtifactName: Transport_Artifacts_Unix_Debug configuration: Debug - vmImageName: 'ubuntu-16.04' + vmImageName: 'ubuntu-18.04' - template: eng/pipelines/test-unix-job.yml parameters: @@ -127,7 +127,7 @@ jobs: buildJobName: Build_Unix_Debug testArtifactName: Transport_Artifacts_Unix_Debug configuration: Debug - testArguments: --testCoreClr --helixQueueName Ubuntu.1604.Amd64.Open + testArguments: --testCoreClr --helixQueueName Ubuntu.1804.Amd64.Open - template: eng/pipelines/test-unix-job-single-machine.yml parameters: @@ -137,7 +137,7 @@ jobs: testArtifactName: Transport_Artifacts_Unix_Debug configuration: Debug testArguments: --testCoreClr - queueName: 'BuildPool.Ubuntu.1604.amd64.Open' + queueName: 'BuildPool.Ubuntu.1804.amd64.Open' - template: eng/pipelines/test-unix-job.yml parameters: @@ -148,6 +148,8 @@ jobs: configuration: Debug testArguments: --testCoreClr --helixQueueName OSX.1014.Amd64.Open +- template: eng/common/templates/jobs/source-build.yml + # Build Correctness Jobs - job: Correctness_Determinism @@ -174,7 +176,7 @@ jobs: steps: - template: eng/pipelines/checkout-windows-task.yml - - script: eng/test-build-correctness.cmd -configuration Release + - script: eng/test-build-correctness.cmd -configuration Release -enableDumps displayName: Build - Validate correctness - template: eng/pipelines/publish-logs.yml @@ -198,30 +200,6 @@ jobs: publishLocation: Container condition: succeeded() -- job: Correctness_SourceBuild - pool: - name: NetCorePublic-Pool - queue: BuildPool.Ubuntu.1604.amd64.Open - timeoutInMinutes: 90 - steps: - - template: eng/pipelines/checkout-unix-task.yml - - - script: ./eng/cibuild.sh --configuration Debug --prepareMachine --binaryLog --sourceBuild - displayName: Build - - - template: eng/pipelines/publish-logs.yml - parameters: - jobName: Correctness_SourceBuild - configuration: Release - - task: PublishBuildArtifacts@1 - displayName: Publish Logs - inputs: - PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/Debug' - ArtifactName: 'SourceBuild_Test' - publishLocation: Container - continueOnError: true - condition: not(succeeded()) - - job: Correctness_Rebuild pool: name: NetCorePublic-Pool diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 8967c03f95cdd..1e1fcaa5df24f 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -8,8 +8,8 @@ efforts behind them. # C# Next -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | 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) | | [Static Abstract Members In Interfaces](https://github.com/dotnet/csharplang/issues/4436) | [StaticAbstractMembersInInterfaces](https://github.com/dotnet/roslyn/tree/features/StaticAbstractMembersInInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/52221) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) | @@ -21,24 +21,25 @@ efforts behind them. | [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-argument-expression](https://github.com/dotnet/roslyn/tree/features/caller-argument-expression) | Prototype | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred),[jcouv](https://github.com/jcouv) | [jcouv](https://github.com/jcouv) | +| [Caller expression attribute](https://github.com/dotnet/csharplang/issues/287) | [caller-argument-expression](https://github.com/dotnet/roslyn/tree/features/caller-argument-expression) | [In Progress](https://github.com/dotnet/roslyn/issues/52745) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred),[AlekseyTs](https://github.com/AlekseyTs) | [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) | main | [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) | main | [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) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | -| [Extended property patterns](https://github.com/dotnet/csharplang/issues/4394) | [extended-property-patterns](https://github.com/dotnet/roslyn/tree/features/extended-property-patterns) | In Progress | [alrz](https://github.com/alrz) | [333fred](https://github.com/333fred) (Tentative) | [333fred](https://github.com/333fred) | -| [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174) | main | [In Progress](https://github.com/dotnet/roslyn/issues/52031) | [thomaslevesque](https://github.com/thomaslevesque/) | [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | +| [Extended property patterns](https://github.com/dotnet/csharplang/issues/4394) | [extended-property-patterns](https://github.com/dotnet/roslyn/tree/features/extended-property-patterns) | [In Progress](https://github.com/dotnet/roslyn/issues/52468) | [alrz](https://github.com/alrz) | [333fred](https://github.com/333fred) (Tentative), [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | +| [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174) | main | [Merged](https://github.com/dotnet/roslyn/issues/52031) | [thomaslevesque](https://github.com/thomaslevesque/) | [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | +| [Source Generator V2 APIs](https://github.com/dotnet/roslyn/issues/51257) | [features/source-generators](https://github.com/dotnet/roslyn/tree/features/source-generators) | [In Progress](https://github.com/dotnet/roslyn/issues/51257) | [chsienki](https://github.com/chsienki/) | [rikkigibson](https://github.com/rikkigibson), [jaredpar](https://github.com/jaredpar), [cston](https://github.com/cston) | N/A | # VB 16.9 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | |-------------------------------------------------------------------------------------------|--------|------------------------------------------------------------------|-------------------------------------------|-----------------------------------|-----------| | [Enable consumption of init-only properties](https://github.com/dotnet/roslyn/pull/50414) | main | [Merged (16.9p3)](https://github.com/dotnet/roslyn/issues/50792) | [AlekseyTs](https://github.com/AlekseyTs) | [jcouv](https://github.com/jcouv) | N/A | # C# 9 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | |----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------| | [Target-typed new](https://github.com/dotnet/csharplang/issues/100) | [target-typed-new](https://github.com/dotnet/roslyn/tree/features/target-typed-new) | [Merged into 16.7p1](https://github.com/dotnet/roslyn/issues/28489) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv) | [jcouv](https://github.com/jcouv) | | [Skip locals init](https://github.com/dotnet/csharplang/issues/1738) | [localsinit](https://github.com/dotnet/roslyn/tree/features/localsinit) | [Merged](https://github.com/dotnet/roslyn/issues/25780) | [t-camaia](https://github.com/t-camaia), [agocke](https://github.com/agocke) | [jaredpar](https://github.com/jaredpar) | [agocke](https://github.com/agocke) | @@ -58,8 +59,8 @@ efforts behind them. # C# 8.0 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | | [Default Interface Methods](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md) | main | [Merged to dev16.1 preview2](https://github.com/dotnet/roslyn/issues/19217) | [AlekseyTs](https://github.com/AlekseyTs) | [gafter](https://github.com/gafter) | [gafter](https://github.com/gafter) | | [Nullable reference type](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/nullable-reference-types-specification.md) | main | [Merged to dev16.0 preview1](https://github.com/dotnet/roslyn/issues/22152) | [cston](https://github.com/cston), [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [333fred](https://github.com/333fred) | [mattwar](https://github.com/mattwar) | | [Recursive patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md) | main | [Merged to dev16.0 preview2](https://github.com/dotnet/roslyn/issues/25935) | [gafter](https://github.com/gafter) | [agocke](https://github.com/agocke), [cston](https://github.com/cston) | [gafter](https://github.com/gafter) | @@ -75,15 +76,15 @@ efforts behind them. # VB 16.0 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | | [Line continuation comments](https://github.com/dotnet/vblang/issues/65) | [continuation-comments](https://github.com/dotnet/roslyn/tree/features/continuation-comments) | Merged in 16.1 (preview2) | [paul1956](https://github.com/paul1956) | [AlekseyTs](https://github.com/AlekseyTs) | [gafter](https://github.com/gafter) | | [Relax null-coalescing operator requirements](https://github.com/dotnet/vblang/issues/339) | [null-operator-enhancements](https://github.com/dotnet/roslyn/tree/features/null-operator-enhancements) | Merged in 16.0 | [333fred](https://github.com/333fred) | [cston](https://github.com/cston) | [gafter](https://github.com/gafter) | # C# 7.3 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | | [blittable](https://github.com/dotnet/csharplang/pull/206) | None | Merged | None | - | [jaredpar](https://github.com/jaredpar) | | [Support == and != for tuples](https://github.com/dotnet/csharplang/issues/190) | [tuple-equality](https://github.com/dotnet/roslyn/tree/features/tuple-equality) | Merged | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs) | [jcouv](https://github.com/jcouv) | | strongname | main | Merged | [tyoverby](https://github.com/tyoverby) | [agocke](https://github.com/agocke) | [jaredpar](https://github.com/jaredpar) | @@ -98,15 +99,15 @@ efforts behind them. # C# 7.2 fixes -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | | [`ref` and `this` ordering in ref extension](https://github.com/dotnet/roslyn/pull/23643) | - | Merged | [alrz](https://github.com/alrz) | - | - | | [Tiebreaker for by-val and `in` overloads](https://github.com/dotnet/roslyn/pull/23122) | main | Merged | [OmarTawfik](https://github.com/OmarTawfik) | - | - | # C# 7.2 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | | [ref readonly](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/readonly-ref.md) | main | Merged | [vsadov](https://github.com/vsadov), [OmarTawfik](https://github.com/OmarTawfik) | [cston](https://github.com/cston),[gafter](https://github.com/gafter) | [jaredpar](https://github.com/jaredpar) | | [interior pointer/Span/ref struct](https://github.com/dotnet/csharplang/pull/264) | main | Merged | [vsadov](https://github.com/vsadov) | [gafter](https://github.com/gafter), [jaredpar](https://github.com/jaredpar) | [jaredpar](https://github.com/jaredpar) | | [non-trailing named arguments](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/non-trailing-named-arguments.md) | main | Merged | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | @@ -116,8 +117,8 @@ efforts behind them. # C# 7.1 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -| ------- | ------ | ----- | ---------- | -------- | --------- | +| Feature | Branch | State | Developer | Reviewer | LDM Champ | +| ------- | ------ | ----- | --------- | -------- | --------- | | [Async Main](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.1/async-main.md) | main | Merged | [tyoverby](https://github.com/tyoverby) | [vsadov](https://github.com/vsadov) | [stephentoub](https://github.com/stephentoub) | | [Default Expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.1/target-typed-default.md) | main | Merged | [jcouv](https://github.com/jcouv) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv) | | [Ref Assemblies](https://github.com/dotnet/roslyn/blob/main/docs/features/refout.md) | main | Merged (IDE and project-system integrations ongoing) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | N/A | diff --git a/docs/area-owners.md b/docs/area-owners.md new file mode 100644 index 0000000000000..76c05e21581a2 --- /dev/null +++ b/docs/area-owners.md @@ -0,0 +1,28 @@ +# Pull Requests Tagging + +If you need to tag folks on an issue or PR, you will generally want to tag the owners (not the lead) for [area](#areas) to which the change or issue is closest to. + +## Areas + +| Area | Lead | Owners | +|----------------------------|---------|--------------------------------| +|api-Syntax |@jaredpar|@CyrusNajmabadi @333fred | +|api-Symbols |@jaredpar|@AlekseyTs | +|api-SemanticModel |@jaredpar|@AlekseyTs @333fred | +|api-IOperation |@jaredpar|@333fred | +|api-SourceGenerators |@jaredpar|@chsienki | +|api-Analyzers |@jaredpar|@mavasani @333fred | +|api-CodeFixes (Refactoring) |@jinuj |@mavasani @sharwell | +|api-Completion |@jinuj |@genlu @CyrusNajmabadi | +|api-Debugging |@jinuj |@tmat @davidwengier | +|api-EditAndContinue |@jinuj |@tmat @davidwengier | +|api-EmbeddedLanguages |@jinuj |@CyrusNajmabadi @joerobich | +|api-Formatting |@jinuj |@sharwell @joerobich | +|api-LSP |@jinuj |@dibarbet @jasonmalinowski | +|api-MetadataAsSource |@jinuj |@sharwell @jasonmalinowski | +|api-Navigation |@jinuj |@CyrusNajmabadi @dibarbet | +|api-QuickInfo |@jinuj |@sharwell @jasonmalinowski | +|api-SignatureHelp |@jinuj |@sharwell @jasonmalinowski | +|api-Structure (Outlining) |@jinuj |@CyrusNajmabadi @jasonmalinowski| +|api-Tagging (Classification)|@jinuj |@CyrusNajmabadi @joerobich | +|api-Workspace |@jinuj |@jasonmalinowski @tmat | diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md b/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md index 852599b639911..deb51f7849851 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md @@ -34,3 +34,39 @@ return a ?? (b ? 1 : 2); } ``` + +3. https://github.com/dotnet/roslyn/issues/52630 In C# 9 (.NET 5, Visual Studio 16.9), it is possible that a record uses a hidden member from a base type as a positional member. In Visual Studio 16.10, this is now an error: +```csharp +record Base +{ + public int I { get; init; } +} +record Derived(int I) // The positional member 'Base.I' found corresponding to this parameter is hidden. + : Base +{ + public int I() { return 0; } +} +``` + +4. In C# 10, method groups are implicitly convertible to `System.Delegate`, and lambda expressions are implicitly convertible to `System.Delegate` and `System.Linq.Expressions.Expression`. + + This is a breaking change to overload resolution if there exists an overload with a `System.Delegate` or `System.Linq.Expressions.Expression` parameter that is applicable and the closest applicable overload with a strongly-typed delegate parameter is in an enclosing namespace. + + ```C# + class C + { + static void Main() + { + var c = new C(); + c.M(Main); // C#9: E.M(), C#10: C.M() + c.M(() => { }); // C#9: E.M(), C#10: C.M() + } + + void M(System.Delegate d) { } + } + + static class E + { + public static void M(this object x, System.Action y) { } + } + ``` \ No newline at end of file diff --git a/docs/contributing/API Review Process.md b/docs/contributing/API Review Process.md new file mode 100644 index 0000000000000..43896603d3607 --- /dev/null +++ b/docs/contributing/API Review Process.md @@ -0,0 +1,46 @@ +# API Review Process + +.NET has a long-standing history of taking API usability extremely seriously. Thus, we generally review every single API that is added to the product. This page discusses how we conduct design reviews for the Roslyn components. + +## Which APIs should be reviewed? + +The rule of thumb is that we (**dotnet/roslyn**) review every public API that is being added to this repository in the CodeAnalysis (compiler), Workspaces, Features, and EditorFeatures libraries. APIs in the LanguageServices dlls are not usually reviewed as they are not intended for general consumption; they are for use in Visual Studio, and we don't have a compat bar for these APIs. + +## Steps + +1. **Requester files an issue**. The issue description should contain a speclet that represents a sketch of the new APIs, including samples on how the APIs are being used. The goal isn't to get a complete API list, but a good handle on how the new APIs would roughly look like and in what scenarios they are being used. Please use [this template](https://github.com/dotnet/roslyn/issues/new?template=api-suggestion.md). The issue should have the labels `Feature Request` and `Concept-API`. [Here](https://github.com/dotnet/roslyn/issues/53410) is a good example of a strong API proposal. + +2. **We assign an owner**. We'll assign a dedicated owner from our side that sponsors the issue. This is usually [the area owner](../area-owners.md#areas) for which the API proposal or design change request was filed. + +3. **Discussion**. The goal of the discussion is to help the assignee to decide whether we want to pursue the proposal or not. In this phase, the goal isn't necessarily to perform an in-depth review; rather, we want to make sure that the proposal is actionable, i.e. has a concrete design, a sketch of the APIs and some code samples that show how it should be used. If changes are necessary, the owner will set the label `api-needs-work`. To make the changes, the requester should edit the top-most issue description. This allows folks joining later to understand the most recent proposal. To avoid confusion, the requester can maintain a tiny change log, like a bolded "Updates:" followed by a bullet point list of the updates that were being made. When you the feedback is addressed, the requester should notify the owner to re-review the changes. + +4. **Owner makes decision**. When the owner believes enough information is available to make a decision, they will update the issue accordingly: + + * **Mark for review**. If the owner believes the proposal is actionable, they will label the issue with `api-ready-for-review`. + * **Close as not actionable**. In case the issue didn't get enough traction to be distilled into a concrete proposal, the owner will close the issue. + * **Close as won't fix as proposed**. Sometimes, the issue that is raised is a good one but the owner thinks the concrete proposal is not the right way to tackle the problem. In most cases, the owner will try to steer the discussion in a direction that results in a design that we believe is appropriate. However, for some proposals the problem is at the heart of the design which can't easily be changed without starting a new proposal. In those cases, the owner will close the issue and explain the issue the design has. + * **Close as won't fix**. Similarly, if proposal is taking the product in a direction we simply don't want to go, the issue might also get closed. In that case, the problem isn't the proposed design but in the issue itself. + +5. **API gets reviewed**. The group conducting the review is called *RAR*, which stands for *Roslyn API Reviewers*. In the review, we'll take notes and provide feedback. After the review, we'll publish the notes in the [API Review repository](https://github.com/dotnet/apireviews) and at the end of the relevant issue. Multiple outcomes are possible: + + * **Approved**. In this case the label `api-ready-for-review` is replaced with `api-approved`. + * **Needs work**. If we believe the proposal isn't ready yet, we'll replace the label `api-ready-for-review` with `api-needs-work`. + * **Rejected**. If we believe the proposal isn't a direction we want to pursue, we'll simply write a comment and close the issue. + +## Review schedule + + There are three methods to get an API review (this section applies to API area owners, but is included for transparency into the process): + +* **Get into the backlog**. Generally speaking, filing an issue in `dotnet/roslyn` and applying the label `api-ready-for-review` on it will make your issue show up during API reviews. The downside is that we generally walk the backlog oldest-newest, so your issue might not be looked at for a while. Progress of issues can be tracked via . +* **Fast track**. If you need to bypass the backlog apply both `api-ready-for-review` and `blocking`. All blocking issues are looked at before we walk the backlog. +* **Dedicated review**. If an issue you are the area owner for needs an hour or longer, send an email to roslynapiowners and we book dedicated time. We also book dedicated time for language feature APIs: any APIs added as part of a new language feature need to have a dedicated review session. Rule of thumb: if the API proposal has more than a dozen APIs, the APIs have complex policy, or the APIs are part of a feature branch, then you need 60 min or more. When in doubt, send mail to roslynapiowners. + +The API review board currently meets every 2 weeks. + +## Pull requests + +Pull requests against **dotnet/roslyn** that add new public API shouldn't be submitted before getting approval. Also, we don't want to get work in progress (WIP) PR's. The reason being that we want to reduce the number pending PRs so that we can focus on the work the community expects we take action on. + +If you want to collaborate with other people on the design, feel free to perform the work in a branch in your own fork. If you want to track your TODOs in the description of a PR, you can always submit a PR against your own fork. Also, feel free to advertise your PR by linking it from the issue you filed against **dotnet/roslyn** in the first step above. + +For **dotnet/roslyn** team members, PRs are allowed to be submitted and merged before a full API review session, but you should have sign-off from the area owner, and the API is expected to be covered in a formal review session before being shipped. For such issues, please work with the API area owner to make sure that the issue is marked blocking appropriately so it will be covered in short order. diff --git a/docs/contributing/Building, Debugging, and Testing on Windows.md b/docs/contributing/Building, Debugging, and Testing on Windows.md index da05840c42b92..7e3b207322af7 100644 --- a/docs/contributing/Building, Debugging, and Testing on Windows.md +++ b/docs/contributing/Building, Debugging, and Testing on Windows.md @@ -15,12 +15,12 @@ The minimal required version of .NET Framework is 4.7.2. ## Developing with Visual Studio 2019 -1. [Visual Studio 2019 16.8](https://visualstudio.microsoft.com/downloads/) +1. [Visual Studio 2019 16.9](https://visualstudio.microsoft.com/downloads/) - Ensure C#, VB, MSBuild, .NET Core and Visual Studio Extensibility are included in the selected work loads - - Ensure Visual Studio is on Version "16.8" or greater + - Ensure Visual Studio is on Version "16.9" or greater - Ensure "Use previews of the .NET Core SDK" is checked in Tools -> Options -> Environment -> Preview Features - Restart Visual Studio -1. [.NET Core SDK 5.0](https://dotnet.microsoft.com/download/dotnet-core/5.0) [Windows x64 installer](https://dotnet.microsoft.com/download/dotnet/thank-you/sdk-5.0.102-windows-x64-installer) +1. [.NET 6.0 Preview 3 SDK](https://dotnet.microsoft.com/download/dotnet-core/6.0) [Windows x64 installer](https://dotnet.microsoft.com/download/dotnet/thank-you/sdk-6.0.100-preview.3-windows-x64-installer) 1. [PowerShell 5.0 or newer](https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell). If you are on Windows 10, you are fine; you'll only need to upgrade if you're on earlier versions of Windows. The download link is under the ["Upgrading existing Windows PowerShell"](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-windows-powershell?view=powershell-6#upgrading-existing-windows-powershell) heading. 1. Run Restore.cmd 1. Open Roslyn.sln @@ -73,7 +73,7 @@ The Rosyln solution is designed to support easy debugging via F5. Several of ou projects produce VSIX which deploy into Visual Studio during build. The F5 operation will start a new Visual Studio instance using those VSIX which override our installed binaries. This means trying out a change to the language, IDE or debugger is as -simple as hitting F5. +simple as hitting F5. Note that for changes to the compiler, out-of-process builds won't use the privately built version of the compiler. The startup project needs to be set to `RoslynDeployment`. This should be the default but in some cases will need to be set explicitly. @@ -129,8 +129,8 @@ your version and go back to the originally installed version by choosing your version and clicking Uninstall. If you only install the VSIX, then the IDE will behave correctly (ie. new compiler -and IDE behavior), but the Build operation or building from the command-line won't. -To fix that, add a reference to the `Microsoft.Net.Compilers.Toolset` you built into +and IDE behavior), but the Build operation or building from the command-line won't. +To fix that, add a reference to the `Microsoft.Net.Compilers.Toolset` you built into your csproj. As shown below, you'll want to (1) add a nuget source pointing to your local build folder, (2) add the package reference, then (3) verify the Build Output of your project with a `#error version` included in your program. @@ -144,7 +144,7 @@ your csproj. As shown below, you'll want to (1) add a nuget source pointing to y ### Deploying with command-line -You can build and deploy with the following command: +You can build and deploy with the following command: `.\Build.cmd -Configuration Release -deployExtensions -launch`. Then you can launch the `RoslynDev` hive with `devenv /rootSuffix RoslynDev`. @@ -160,11 +160,11 @@ csc and vbc inside it. You can check the cibuild.cmd and see how it is used. ### Troubleshooting your setup To confirm what version of the compiler is being used, include `#error version` in your program -and the compiler will produce a diagnostic including its own version as well as the language +and the compiler will produce a diagnostic including its own version as well as the language version it is operating under. You can also attach a debugger to Visual Studio and check the loaded modules, looking at the folder -where the various `CodeAnalysis` modules were loaded from (the `RoslynDev` should load them somewhere +where the various `CodeAnalysis` modules were loaded from (the `RoslynDev` should load them somewhere under `AppData`, not from `Program File`). ### Testing on the [dotnet/runtime](https://github.com/dotnet/runtime) repo @@ -176,6 +176,12 @@ under `AppData`, not from `Program File`). - add `\artifacts\packages\Debug\Shipping\` using the local path to your `roslyn` repo to `Directory.Build.props` - add `3.9.0-dev` with the package version you just packed (look in above artifacts folder) to `eng/Versions.props` +### Testing with extra IOperation validation + +Run `build.cmd -testIOperation` which sets the `ROSLYN_TEST_IOPERATION` environment variable to `true` and runs the tests. +For running those tests in an IDE, the easiest is to find the `//#define ROSLYN_TEST_IOPERATION` directive and uncomment it. +See more details in the [IOperation test hook](https://github.com/dotnet/roslyn/blob/main/docs/compilers/IOperation%20Test%20Hook.md) doc. + ## Contributing Please see [Contributing Code](https://github.com/dotnet/roslyn/blob/main/CONTRIBUTING.md) for details on contributing changes back to the code. diff --git a/docs/contributing/Compiler Test Plan.md b/docs/contributing/Compiler Test Plan.md index f628a1d8e242b..3fcf98b6fc6e8 100644 --- a/docs/contributing/Compiler Test Plan.md +++ b/docs/contributing/Compiler Test Plan.md @@ -31,10 +31,11 @@ This document provides guidance for thinking about language interactions and tes - VB/F# interop - Performance and stress testing - Can build VS +- Check that `Obsolete` is honored for members used in binding/lowering # Type and members - Access modifiers (public, protected, internal, protected internal, private protected, private), static, ref -- type declarations (class, record with or without positional members, struct, interface, type parameter) +- type declarations (class, record class/struct with or without positional members, struct, interface, type parameter) - methods - fields - properties (including get/set/init accessors) @@ -91,8 +92,9 @@ This document provides guidance for thinking about language interactions and tes - Ref return, ref readonly return, ref ternary, ref readonly local, ref local re-assignment, ref foreach - `this = e;` in `struct` .ctor - Stackalloc (including initializers) -- Patterns (constant, declaration, `var`, positional, property, and discard forms) +- Patterns (constant, declaration, `var`, positional, property, discard, parenthesized, type, relational, `and`/`or`/`not`) - Switch expressions +- With expressions (on record classes and on value types) - Nullability annotations (`?`, attributes) and analysis - If you add a place an expression can appear in code, make sure `SpillSequenceSpiller` handles it. Test with a `switch` expression or `stackalloc` in that place. - If you add a new expression form that requires spilling, test it in the catch filter. diff --git a/docs/features/GlobalUsingDirective.md b/docs/features/GlobalUsingDirective.md new file mode 100644 index 0000000000000..562ab106ff75c --- /dev/null +++ b/docs/features/GlobalUsingDirective.md @@ -0,0 +1,11 @@ +Global Using Directive +========================= + +The *Global Using Directive* feature extends using directive syntax with an optional `global` keyword that can precede the `using` keyword. +The scope of Global Using Directives spans across all compilation units in the program. + +Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/GlobalUsingDirective.md + +Feature branch: https://github.com/dotnet/roslyn/tree/features/GlobalUsingDirective + +Test plan: https://github.com/dotnet/roslyn/issues/51307 diff --git a/docs/features/pdb-compilation-options.md b/docs/features/pdb-compilation-options.md index 28416d76f94f9..971d68a01ad7d 100644 --- a/docs/features/pdb-compilation-options.md +++ b/docs/features/pdb-compilation-options.md @@ -167,7 +167,6 @@ Example: * resource - Will be represented in metadata reference embedded in pdb * subsystemversion -* target * win32icon * win32manifest * win32res @@ -217,7 +216,6 @@ Example: * resource - Will be represented in metadata reference embedded in pdb * subsystemversion -* target * win32icon * win32manifest * win32resource @@ -257,24 +255,32 @@ Example: * verbose * warnaserror -#### Options For CSharp - -See [compiler options](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/listed-alphabetically) documentation +#### Shared Options for CSharp and Visual Basic | PDB Key | Format | Default | Description | | ---------------------- | --------------------------------------- | --------- | ------------ | -| language | `CSharp` | required | Language name. | -| language-version | `[0-9]+(\.[0-9]+)?` | required | [langversion](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/langversion-compiler-option) | +| language | `CSharp\|Visual Basic` | required | Language name. | | compiler-version | [SemVer2](https://semver.org/spec/v2.0.0.html) string | required | Full version with SHA | | runtime-version | [SemVer2](https://semver.org/spec/v2.0.0.html) string | required | [runtime version](#runtime-version) | -| optimization | `(debug|debug-plus|release)` | `debug` | [optimization](#optimization) | -| portability-policy | `(0|1|2|3)` | `0` | [portability policy](#portability-policy) | +| source-file-count | int32 | required | Count of files in the document table that are source files | +| optimization | `(debug\|debug-plus\|release\|release-debug-plus)` | `'debug'` | [optimization](#optimization) | +| portability-policy | `(0\|1\|2\|3)` | `0` | [portability policy](#portability-policy) | | default-encoding | string | none | [file encoding](#file-encoding) | | fallback-encoding | string | none | [file encoding](#file-encoding) | +| output-kind | string | require | The value passed to `/target` | +| platform | string | require | The value passed to `/platform` | + +#### Options For CSharp + +See [compiler options](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/listed-alphabetically) documentation + +| PDB Key | Format | Default | Description | +| ---------------------- | --------------------------------------- | --------- | ------------ | +| language-version | `[0-9]+(\.[0-9]+)?` | required | [langversion](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/langversion-compiler-option) | | define | `,`-separated identifier list | empty | [define](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/define-compiler-option) | -| checked | `(True|False)` | `False` | [checked](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/checked-compiler-option) | -| nullable | `(Disable|Warnings|Annotations|Enable)` | `Disable` | [nullable](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) | -| unsafe | `(True|False)` | `False` | [unsafe](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/unsafe-compiler-option) | +| checked | `(True\|False)` | `False` | [checked](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/checked-compiler-option) | +| nullable | `(Disable\|Warnings\|Annotations\|Enable)` | `Disable` | [nullable](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) | +| unsafe | `(True\|False)` | `False` | [unsafe](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/unsafe-compiler-option) | #### Options For Visual Basic @@ -282,14 +288,16 @@ See [compiler options](https://docs.microsoft.com/en-us/dotnet/visual-basic/refe | PDB Key | Format | Default | Description | | ---------------------- | ------------------------------------------ | -------- | ----------- | -| language | `Visual Basic` | required | Language name. | | language-version | `[0-9]+(\.[0-9]+)?` | required | [langversion](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/langversion) | -| compiler-version | [SemVer2](https://semver.org/spec/v2.0.0.html) string | required | Full version with SHA | -| runtime-version | [SemVer2](https://semver.org/spec/v2.0.0.html) string | required | [runtime version](#runtime-version) | -| optimization | `(debug|debug-plus|release)` | `debug` | [optimization](#optimization) | | define | `,`-separated list of name `=` value pairs | empty | [define](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/define) | -| strict | `(Off|Custom|On)` | `Off` | [optionstrict](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/optionstrict) | -| checked | `(True|False)` | `False` | Opposite of [removeintchecks](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/removeintchecks) | +| checked | `(True\|False)` | `False` | Opposite of [removeintchecks](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/removeintchecks) | +| option-strict | `(Off\|Custom\|On)` | required | [option strict](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/optionstrict) | +| option-infer | `(True\|False)` | required | [option infer](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/optioninfer) | +| option-compare-text | `(True\|False)` | required | [option compare](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/optioncompare) | +| option-explicit | `(True\|False)` | required | [option explicit](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/optionexplicit) | +| embed-runtime | `(True\|False)` | required | Whether or not the VB runtime was embedded into the PE | +| global-namespaces | `,` -separated identifier list | empty | [imports](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/imports) +| root-namespace | string | empty | [root namespace](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/command-line-compiler/rootnamespace) #### Portability Policy diff --git a/docs/features/source-generators.md b/docs/features/source-generators.md index 44805c2cc1a2d..9d03f07e455a2 100644 --- a/docs/features/source-generators.md +++ b/docs/features/source-generators.md @@ -111,7 +111,7 @@ and only be correct at build time. Based on conversations with 1st party custome there are several cases where this would be enough. However, for scenarios such as code first gRPC, and in particular Razor and Blazor, -the IDE will need to be able to generate code on-they-fly as those file types are +the IDE will need to be able to generate code on-the-fly as those file types are edited and reflect the changes back to other files in the IDE in near real-time. The proposal is to have a set of advanced callbacks that can be optionally implemented, @@ -303,4 +303,4 @@ No, we should work out priority in order to prioritize features. **Security Review**: -Do generators create any new security risks not already posed via analyzers and nuget? \ No newline at end of file +Do generators create any new security risks not already posed via analyzers and nuget? diff --git a/docs/ide/README.md b/docs/ide/README.md index 3d480721f6fa6..ff82481693dd1 100644 --- a/docs/ide/README.md +++ b/docs/ide/README.md @@ -2,6 +2,8 @@ ## TOC * [Dev16](#prod-16) + * [Dev 16.9](#prod-16-9) + * [Dev 16.8](#prod-16-8) * [Dev 16.7](#prod-16-7) * [Dev 16.6](#prod-16-6) * [Dev 16.5](#prod-16-5) @@ -20,11 +22,59 @@ ## dev16 -### 16.7 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes)) - -TO BE ADDED - -### 16.6 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-archive-v16.6)) +### 16.9 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-v16.9)) +* There is now IntelliSense completion for preprocessor symbols. +* Solution Explorer now displays the new .NET 5.0 Source Generators. +* Go To All won't display duplicate results across netcoreapp3.1 and netcoreapp2.0. +* Quick Info now displays compiler warning IDs or numbers for suppressions. +* Using directives will now automatically get added when copying and pasting types to a new file. +* When pressing `;` to accept a method from a completion list, IntelliSense will now automatically insert the parentheses along with a semicolon for object creation and method calls. +* Semantic colorization for C# 9.0 [records](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#record-types&preserve-view=true). +* Refactoring that removes unnecessary discards. +* Refactoring that converts a verbatim and regular string to an interpolated string preserving curly braces that were intended to go in the output. +* Code fix in Visual Basic that removes the shared keyword when you convert methods that are shared to a module. +* A refactoring that suggests using `new(…)` in non-contentious scenarios +* A code fix that removes redundant equality expressions for both C# and Visual Basic +* The .NET Code Style (IDE) analyzers can now be enforced on build +* The Syntax Visualizer shows the current foreground color for enhanced colors +* A new tooltip when hovering over the diagnostic ID for pragma warnings +* When you type the return key from within a comment the new line is now automatically commented out +* Inline parameter name hints enhancements +* .NET Core Debugging with WSL 2 + +### 16.8 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-v16.8)) +* Roslyn analyzers are now included in the .NET 5.0 SDK +* Refactoring that introduces the new C# 9 `not` pattern matching syntax when a suppression operator is present +* Inline method refactoring to help replace usages of static, instance, and extension method within a single statement body +* Code fix to convert instances of `typeof` to `nameof` in C# and `GetType` to `NameOf` in Visual Basic +* C# and Visual Basic support for inline parameter name hints that inserts adornments for literals, casted literals, and object instantiations prior to each argument in function calls +* Refactoring that extracts members from a selected class to a new base class in both C# and Visual Basic +* Code cleanup has new configuration options that can apply formatting and file header preferences set in your EditorConfig file across a single file or an entire solution +* Code fix to remove the `in` keyword where the argument should not be passed by reference +* Refactoring that introduces the new C#9 pattern combinators and pattern matching suggestions such as converting `==` to use `is` where applicable +* Code fix to make a class abstract when you are trying to write an abstract method in a class that is not abstract +* IntelliSense completion in `DateTime` and `TimeSpan` string literals automatically appear when the first quote is typed +* Code fix to remove unnecessary `pragma suppressions` and unnecessary `SuppressMessageAttributes` +* `Rename` and `Find All` References understands references to symbols within the target string of global `SuppressMessageAttributes` +* _ByVal_ fades to say it's not necessary along with a code fix to remove the unnecessary _ByVal_ in Visual Basic +* Interactive window support for multiple runtimes, such as .NET Framework and .NET Core. +* Added a new _RegisterAdditionalFileAction_ API that allows analyzer authors to create an analyzer for additional files. + +### 16.7 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-v16.7)) +* There is now a warning and code fix when a suppression operator is present but has no effect +* A second code fix suggesting the correct negating expression is also available +* You can now generate properties when generating a constructor in a type +* Quick Info now displays the diagnostic ID along with a help link +* There is now a quick action to add a debugger display attribute to a class +* There is now a code fix for accidental assignments or comparisons to the same variable +* You can now generate comparison operators for types that implement IComparable +* You can now generate IEquatable operators when generating .Equals for structs +* You can now create and assign properties or fields for all unused constructor parameters +* There is now IntelliSense completion in DateTime and TimeSpan string literals +* You can now add a parameter within the Change Signature dialog +* Analyzer authors can now use CompletionProviders for IntelliSense completions when shipping their analyzers with NuGet + +### 16.6 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-v16.6)) * Add explicit cast code fix * Simplify conditional expressions refactoring * Convert regular string literals to verbatim string literals (and back) refactoring @@ -33,7 +83,7 @@ TO BE ADDED * Add file headers to existing files, projects, and solutions with EditorConfig * [Microsoft Fakes support for .NET Core](https://docs.microsoft.com/visualstudio/releases/2019/release-notes#microsoft-fakes-for-net-core-and-sdk-style-projects) and SDK-style projects -### 16.5 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-archive-v16.5)) +### 16.5 ([release notes](https://docs.microsoft.com/visualstudio/releases/2019/release-notes-v16.5)) * You can now use System.HashCode to implement the GetHashCode method when System.HashCode is available. * Refactoring to convert if statements to switch statements or switch expressions. * IntelliSense completion for unimported extension methods. You will first need to turn this option on in **Tools > Options > Text Editor > C# > Intellisense > and select Show items from unimported namespaces**. diff --git a/docs/ide/external-access.md b/docs/ide/external-access.md new file mode 100644 index 0000000000000..e09926a4a1de6 --- /dev/null +++ b/docs/ide/external-access.md @@ -0,0 +1,5 @@ +# External Access Policies + +## OmniSharp + +When a change needs to be made to an API in the ExternalAccess.OmniSharp or ExternalAccess.OmniSharp.CSharp packages, ping @333fred, @JoeRobich, @filipw, or @david-driscoll as a heads up. Breaking changes are allowed, but please wait for acknowledgement and followup questions to ensure that we don't completely break OmniSharp scenarios. diff --git a/eng/Publishing.props b/eng/Publishing.props new file mode 100644 index 0000000000000..797de4ea1decd --- /dev/null +++ b/eng/Publishing.props @@ -0,0 +1,6 @@ + + + + 3 + + \ No newline at end of file diff --git a/eng/Signing.props b/eng/Signing.props index d20e040316309..423aba828e82b 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -1,6 +1,11 @@  + + + true + + diff --git a/eng/SourceBuild.props b/eng/SourceBuild.props new file mode 100644 index 0000000000000..182f8a9cdb12b --- /dev/null +++ b/eng/SourceBuild.props @@ -0,0 +1,18 @@ + + + + roslyn + true + + + + + + $(InnerBuildArgs) /p:Projects="$(InnerSourceBuildRepoRoot)\Compilers.sln" + + + + diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml new file mode 100644 index 0000000000000..c1b6dfbf05381 --- /dev/null +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/eng/Tools.props b/eng/Tools.props new file mode 100644 index 0000000000000..6be67695ad0a4 --- /dev/null +++ b/eng/Tools.props @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5e5fcbfe58cd2..1550d473a9f8a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,19 +1,30 @@ + + https://github.com/dotnet/xliff-tasks + 7e80445ee82adbf9a8e6ae601ac5e239d982afaa + + + + https://github.com/dotnet/source-build + c06ae1212bd69e9fe52bed0b0a7d79cc8ea39054 + + - + https://github.com/dotnet/arcade - 9467b1074927ee3fe98d539d3b72f5686c861958 + 78da7776965b428ff31da8f1ff2cb073506212b7 + - + https://github.com/dotnet/roslyn - 31a3e4904f5a02ee51dbe15d7a68daaac9b5c6a8 + 5b972bceb846f5d15f991a479e285067a75103e4 - + https://github.com/dotnet/arcade - 9467b1074927ee3fe98d539d3b72f5686c861958 + 78da7776965b428ff31da8f1ff2cb073506212b7 diff --git a/eng/Versions.props b/eng/Versions.props index 3abc896c59cd3..a703120f5e1a7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -5,10 +5,10 @@ Roslyn version --> - 3 - 10 + 4 + 0 0 - 3 + 2 $(MajorVersion).$(MinorVersion).$(PatchVersion) 3.3.3-beta1.21105.3 6.0.0-preview1.21054.10 1.0.1-beta1.20623.3 - 3.8.0 - 16.9.220 + 3.9.0 + 16.10.44 5.0.0-alpha1.19409.1 5.0.0-preview.1.20112.8 - 16.10.161 - 16.9.30921.310 + 16.10.1202 + 16.10.0-preview-2-31112-292 16.5.0 0.9.3 - 0.12.1 - 0.12.1 + 0.13.0 + 0.13.0 1.4.4 $(MicrosoftVisualStudioShellPackagesVersion) $(MicrosoftVisualStudioShellPackagesVersion) @@ -63,7 +63,7 @@ $(MicrosoftBuildPackagesVersion) 5.7.0 - 16.9.63 + 16.10.14-alpha - - - - - netcoreapp2.1 - - - - - - - - - - - - - - - - - - - - - - - - - https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - https://dotnetfeed.blob.core.windows.net/arcade-validation/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json - https://dotnetfeed.blob.core.windows.net/nuget-nugetclient/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-entityframework6/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json - - - - - - - - - - - - diff --git a/eng/common/PublishToSymbolServers.proj b/eng/common/PublishToSymbolServers.proj deleted file mode 100644 index 5d55e312b0122..0000000000000 --- a/eng/common/PublishToSymbolServers.proj +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - netcoreapp2.1 - - - - - - - - - - - - - - - - 3650 - true - false - - - - - - - - - - - - - - - - - diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 83218ad7e72ce..a0b5fc37f4388 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -146,16 +146,16 @@ $userName = "dn-bot" # Insert credential nodes for Maestro's private feeds InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -Password $Password -$dotnet3Source = $sources.SelectSingleNode("add[@key='dotnet3']") -if ($dotnet3Source -ne $null) { - AddPackageSource -Sources $sources -SourceName "dotnet3-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "dotnet3-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password -} - $dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") if ($dotnet31Source -ne $null) { AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password } -$doc.Save($filename) \ No newline at end of file +$dotnet5Source = $sources.SelectSingleNode("add[@key='dotnet5']") +if ($dotnet5Source -ne $null) { + AddPackageSource -Sources $sources -SourceName "dotnet5-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet5-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password +} + +$doc.Save($filename) diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index 751863d500628..2734601c13c4b 100644 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -81,53 +81,52 @@ fi PackageSources=() -# Ensure dotnet3-internal and dotnet3-internal-transport are in the packageSources if the public dotnet3 feeds are present -grep -i "" $ConfigFile + grep -i "" + PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi - PackageSources+=('dotnet3-internal') + PackageSources+=('dotnet3.1-internal') - grep -i "" $ConfigFile if [ "$?" != "0" ]; then - echo "Adding dotnet3-internal-transport to the packageSources." + echo "Adding dotnet3.1-internal-transport to the packageSources." PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" + PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi - PackageSources+=('dotnet3-internal-transport') + PackageSources+=('dotnet3.1-internal-transport') fi -# Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present -grep -i "" + PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi - PackageSources+=('dotnet3.1-internal') + PackageSources+=('dotnet5-internal') - grep -i "" $ConfigFile + grep -i "" $ConfigFile if [ "$?" != "0" ]; then - echo "Adding dotnet3.1-internal-transport to the packageSources." + echo "Adding dotnet5-internal-transport to the packageSources." PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" + PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi - PackageSources+=('dotnet3.1-internal-transport') + PackageSources+=('dotnet5-internal-transport') fi # I want things split line by line @@ -165,4 +164,4 @@ if [ "$?" == "0" ]; then echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" fi done -fi \ No newline at end of file +fi diff --git a/eng/common/SigningValidation.proj b/eng/common/SigningValidation.proj deleted file mode 100644 index 3d0ac80af3ff5..0000000000000 --- a/eng/common/SigningValidation.proj +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - netcoreapp2.1 - - - - - - - - $(NuGetPackageRoot)Microsoft.DotNet.SignCheck\$(SignCheckVersion)\tools\Microsoft.DotNet.SignCheck.exe - - $(PackageBasePath) - signcheck.log - signcheck.errors.log - signcheck.exclusions.txt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eng/common/SourceLinkValidation.ps1 b/eng/common/SourceLinkValidation.ps1 deleted file mode 100644 index cb2d28cb99e1c..0000000000000 --- a/eng/common/SourceLinkValidation.ps1 +++ /dev/null @@ -1,184 +0,0 @@ -param( - [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored - [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation - [Parameter(Mandatory=$true)][string] $SourceLinkToolPath, # Full path to directory where dotnet SourceLink CLI was installed - [Parameter(Mandatory=$true)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade - [Parameter(Mandatory=$true)][string] $GHCommit # GitHub commit SHA used to build the packages -) - -# Cache/HashMap (File -> Exist flag) used to consult whether a file exist -# in the repository at a specific commit point. This is populated by inserting -# all files present in the repo at a specific commit point. -$global:RepoFiles = @{} - -$ValidatePackage = { - param( - [string] $PackagePath # Full path to a Symbols.NuGet package - ) - - # Ensure input file exist - if (!(Test-Path $PackagePath)) { - throw "Input file does not exist: $PackagePath" - } - - # Extensions for which we'll look for SourceLink information - # For now we'll only care about Portable & Embedded PDBs - $RelevantExtensions = @(".dll", ".exe", ".pdb") - - Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " - - $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) - $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId - $FailedFiles = 0 - - Add-Type -AssemblyName System.IO.Compression.FileSystem - - [System.IO.Directory]::CreateDirectory($ExtractPath); - - $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) - - $zip.Entries | - Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | - ForEach-Object { - $FileName = $_.FullName - $Extension = [System.IO.Path]::GetExtension($_.Name) - $FakeName = -Join((New-Guid), $Extension) - $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName - - # We ignore resource DLLs - if ($FileName.EndsWith(".resources.dll")) { - return - } - - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) - - $ValidateFile = { - param( - [string] $FullPath, # Full path to the module that has to be checked - [string] $RealPath, - [ref] $FailedFiles - ) - - # Makes easier to reference `sourcelink cli` - Push-Location $using:SourceLinkToolPath - - $SourceLinkInfos = .\sourcelink.exe print-urls $FullPath | Out-String - - if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { - $NumFailedLinks = 0 - - # We only care about Http addresses - $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches - - if ($Matches.Count -ne 0) { - $Matches.Value | - ForEach-Object { - $Link = $_ - $CommitUrl = -Join("https://raw.githubusercontent.com/", $using:GHRepoName, "/", $using:GHCommit, "/") - $FilePath = $Link.Replace($CommitUrl, "") - $Status = 200 - $Cache = $using:RepoFiles - - if ( !($Cache.ContainsKey($FilePath)) ) { - try { - $Uri = $Link -as [System.URI] - - # Only GitHub links are valid - if ($Uri.AbsoluteURI -ne $null -and $Uri.Host -match "github") { - $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode - } - else { - $Status = 0 - } - } - catch { - $Status = 0 - } - } - - if ($Status -ne 200) { - if ($NumFailedLinks -eq 0) { - if ($FailedFiles.Value -eq 0) { - Write-Host - } - - Write-Host "`tFile $RealPath has broken links:" - } - - Write-Host "`t`tFailed to retrieve $Link" - - $NumFailedLinks++ - } - } - } - - if ($NumFailedLinks -ne 0) { - $FailedFiles.value++ - $global:LASTEXITCODE = 1 - } - } - - Pop-Location - } - - &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) - } - - $zip.Dispose() - - if ($FailedFiles -eq 0) { - Write-Host "Passed." - } -} - -function ValidateSourceLinkLinks { - if (!($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { - Write-Host "GHRepoName should be in the format /" - $global:LASTEXITCODE = 1 - return - } - - if (!($GHCommit -Match "^[0-9a-fA-F]{40}$")) { - Write-Host "GHCommit should be a 40 chars hexadecimal string" - $global:LASTEXITCODE = 1 - return - } - - $RepoTreeURL = -Join("https://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") - $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") - - try { - # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash - $Data = Invoke-WebRequest $RepoTreeURL | ConvertFrom-Json | Select-Object -ExpandProperty tree - - foreach ($file in $Data) { - $Extension = [System.IO.Path]::GetExtension($file.path) - - if ($CodeExtensions.Contains($Extension)) { - $RepoFiles[$file.path] = 1 - } - } - } - catch { - Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL" - $global:LASTEXITCODE = 1 - return - } - - if (Test-Path $ExtractPath) { - Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue - } - - # Process each NuGet package in parallel - $Jobs = @() - Get-ChildItem "$InputPath\*.symbols.nupkg" | - ForEach-Object { - $Jobs += Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName - } - - foreach ($Job in $Jobs) { - Wait-Job -Id $Job.Id | Receive-Job - } -} - -Measure-Command { ValidateSourceLinkLinks } diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index a2d79b4468c61..8943da242f6e9 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -18,60 +18,69 @@ Param( [switch] $sign, [switch] $pack, [switch] $publish, + [switch] $clean, [switch][Alias('bl')]$binaryLog, + [switch][Alias('nobl')]$excludeCIBinarylog, [switch] $ci, [switch] $prepareMachine, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $excludePrereleaseVS, [switch] $help, - [string] $runtimeSourceFeed = "", - [string] $runtimeSourceFeedKey = "", [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) -. $PSScriptRoot\tools.ps1 - +# Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file +# some computer has this env var defined (e.g. Some HP) +if($env:Platform) { + $env:Platform="" +} function Print-Usage() { - Write-Host "Common settings:" - Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" - Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" - Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" - Write-Host " -binaryLog Output binary log (short: -bl)" - Write-Host " -help Print help and exit" - Write-Host "" - - Write-Host "Actions:" - Write-Host " -restore Restore dependencies (short: -r)" - Write-Host " -build Build solution (short: -b)" - Write-Host " -rebuild Rebuild solution" - Write-Host " -deploy Deploy built VSIXes" - Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" - Write-Host " -test Run all unit tests in the solution (short: -t)" - Write-Host " -integrationTest Run all integration tests in the solution" - Write-Host " -performanceTest Run all performance tests in the solution" - Write-Host " -pack Package build outputs into NuGet packages and Willow components" - Write-Host " -sign Sign build outputs" - Write-Host " -publish Publish artifacts (e.g. symbols)" - Write-Host "" - - Write-Host "Advanced settings:" - Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" - Write-Host " -ci Set when running on CI server" - Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" - Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" - Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." - Write-Host " -runtimeSourceFeed Alternate feed source for restoring .NET Runtimes and SDKs" - Write-Host " -runtimeSourceFeedKey Key value for non-public values of runtimeSourceFeed" - Write-Host "" - - Write-Host "Command line arguments not listed above are passed thru to msbuild." - Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." + Write-Host "Common settings:" + Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + Write-Host " -binaryLog Output binary log (short: -bl)" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Actions:" + Write-Host " -restore Restore dependencies (short: -r)" + Write-Host " -build Build solution (short: -b)" + Write-Host " -rebuild Rebuild solution" + Write-Host " -deploy Deploy built VSIXes" + Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" + Write-Host " -test Run all unit tests in the solution (short: -t)" + Write-Host " -integrationTest Run all integration tests in the solution" + Write-Host " -performanceTest Run all performance tests in the solution" + Write-Host " -pack Package build outputs into NuGet packages and Willow components" + Write-Host " -sign Sign build outputs" + Write-Host " -publish Publish artifacts (e.g. symbols)" + Write-Host " -clean Clean the solution" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" + Write-Host " -ci Set when running on CI server" + Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" + Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" + Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" + Write-Host "" + + Write-Host "Command line arguments not listed above are passed thru to msbuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." } +. $PSScriptRoot\tools.ps1 + function InitializeCustomToolset { if (-not $restore) { return } - $script = Join-Path $EngRoot "restore-toolset.ps1" + $script = Join-Path $EngRoot 'restore-toolset.ps1' if (Test-Path $script) { . $script @@ -79,11 +88,11 @@ function InitializeCustomToolset { } function Build { - $toolsetBuildProj = InitializeToolset -runtimeSourceFeed $runtimeSourceFeed -runtimeSourceFeedKey $runtimeSourceFeedKey + $toolsetBuildProj = InitializeToolset InitializeCustomToolset - $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "Build.binlog") } else { "" } - $platformArg = if ($platform) { "/p:Platform=$platform" } else { "" } + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } + $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. @@ -117,24 +126,27 @@ function Build { } try { - if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { + if ($clean) { + if (Test-Path $ArtifactsDir) { + Remove-Item -Recurse -Force $ArtifactsDir + Write-Host 'Artifacts directory deleted.' + } + exit 0 + } + + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($ci) { - $binaryLog = $true + if (-not $excludeCIBinarylog) { + $binaryLog = $true + } $nodeReuse = $false } - # Import custom tools configuration, if present in the repo. - # Note: Import in global scope so that the script set top-level variables without qualification. - $configureToolsetScript = Join-Path $EngRoot "configure-toolset.ps1" - if (Test-Path $configureToolsetScript) { - . $configureToolsetScript - } - - if (($restore) -and ($null -eq $env:DisableNativeToolsetInstalls)) { + if ($restore) { InitializeNativeTools } @@ -142,7 +154,7 @@ try { } catch { Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/build.sh b/eng/common/build.sh index 230e41ac2e90a..55b298f16ccd1 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -26,17 +26,16 @@ usage() echo " --pack Package build outputs into NuGet packages and Willow components" echo " --sign Sign build outputs" echo " --publish Publish artifacts (e.g. symbols)" + echo " --clean Clean the solution" echo "" echo "Advanced settings:" - echo " --projects Project or solution file(s) to build" - echo " --ci Set when running on CI server" - echo " --prepareMachine Prepare machine for CI run, clean up processes after build" - echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" - echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" - echo " --runtimeSourceFeed Alternate (fallback) source for .NET Runtime and SDK installation" - echo " --runtimeSourceFeedKey Credentials (if needed) for authenticating to runtimeSourceFeed" - + echo " --projects Project or solution file(s) to build" + echo " --ci Set when running on CI server" + echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" + echo " --prepareMachine Prepare machine for CI run, clean up processes after build" + echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" echo "" echo "Command line arguments not listed above are passed thru to msbuild." echo "Arguments can also be passed in with a single hyphen." @@ -65,29 +64,32 @@ publish=false sign=false public=false ci=false +clean=false warn_as_error=true node_reuse=true binary_log=false +exclude_ci_binary_log=false pipelines_log=false projects='' configuration='Debug' prepare_machine=false verbosity='minimal' - -runtimeSourceFeed='' -runtimeSourceFeedKey='' +runtime_source_feed='' +runtime_source_feed_key='' properties='' - while [[ $# > 0 ]]; do - opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -help|-h) usage exit 0 ;; + -clean) + clean=true + ;; -configuration|-c) configuration=$2 shift @@ -99,6 +101,9 @@ while [[ $# > 0 ]]; do -binarylog|-bl) binary_log=true ;; + -excludeCIBinarylog|-nobl) + exclude_ci_binary_log=true + ;; -pipelineslog|-pl) pipelines_log=true ;; @@ -148,12 +153,12 @@ while [[ $# > 0 ]]; do shift ;; -runtimesourcefeed) + runtime_source_feed=$2 shift - runtimeSourceFeed="$1" ;; - -runtimesourcefeedkey) + -runtimesourcefeedkey) + runtime_source_feed_key=$2 shift - runtimeSourceFeedKey="$1" ;; *) properties="$properties $1" @@ -165,8 +170,10 @@ done if [[ "$ci" == true ]]; then pipelines_log=true - binary_log=true node_reuse=false + if [[ "$exclude_ci_binary_log" == false ]]; then + binary_log=true + fi fi . "$scriptroot/tools.sh" @@ -180,7 +187,7 @@ function InitializeCustomToolset { } function Build { - InitializeToolset $runtimeSourceFeed $runtimeSourceFeedKey + InitializeToolset InitializeCustomToolset if [[ ! -z "$projects" ]]; then @@ -210,20 +217,15 @@ function Build { ExitWithExitCode 0 } -# Import custom tools configuration, if present in the repo. -configure_toolset_script="$eng_root/configure-toolset.sh" -if [[ -a "$configure_toolset_script" ]]; then - . "$configure_toolset_script" -fi - -# TODO: https://github.com/dotnet/arcade/issues/1468 -# Temporary workaround to avoid breaking change. -# Remove once repos are updated. -if [[ -n "${useInstalledDotNetCli:-}" ]]; then - use_installed_dotnet_cli="$useInstalledDotNetCli" +if [[ "$clean" == true ]]; then + if [ -d "$artifacts_dir" ]; then + rm -rf $artifacts_dir + echo "Artifacts directory deleted." + fi + exit 0 fi -if [[ "$restore" == true && -z ${DisableNativeToolsetInstalls:-} ]]; then +if [[ "$restore" == true ]]; then InitializeNativeTools fi diff --git a/eng/common/cross/android/arm/toolchain.cmake b/eng/common/cross/android/arm/toolchain.cmake deleted file mode 100644 index a7e1c73501b3e..0000000000000 --- a/eng/common/cross/android/arm/toolchain.cmake +++ /dev/null @@ -1,41 +0,0 @@ -set(CROSS_NDK_TOOLCHAIN $ENV{ROOTFS_DIR}/../) -set(CROSS_ROOTFS ${CROSS_NDK_TOOLCHAIN}/sysroot) -set(CLR_CMAKE_PLATFORM_ANDROID "Android") - -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_VERSION 1) -set(CMAKE_SYSTEM_PROCESSOR arm) - -## Specify the toolchain -set(TOOLCHAIN "arm-linux-androideabi") -set(CMAKE_PREFIX_PATH ${CROSS_NDK_TOOLCHAIN}) -set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-) - -find_program(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++) -find_program(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_LD ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) -find_program(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) - -add_compile_options(--sysroot=${CROSS_ROOTFS}) -add_compile_options(-fPIE) -add_compile_options(-mfloat-abi=soft) -include_directories(SYSTEM ${CROSS_NDK_TOOLCHAIN}/include/c++/4.9.x/) -include_directories(SYSTEM ${CROSS_NDK_TOOLCHAIN}/include/c++/4.9.x/arm-linux-androideabi/) - -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B ${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/lib/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -fPIE -pie") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) - -set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/android/arm64/toolchain.cmake b/eng/common/cross/android/arm64/toolchain.cmake deleted file mode 100644 index 29415899c1c6d..0000000000000 --- a/eng/common/cross/android/arm64/toolchain.cmake +++ /dev/null @@ -1,42 +0,0 @@ -set(CROSS_NDK_TOOLCHAIN $ENV{ROOTFS_DIR}/../) -set(CROSS_ROOTFS ${CROSS_NDK_TOOLCHAIN}/sysroot) -set(CLR_CMAKE_PLATFORM_ANDROID "Android") - -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_VERSION 1) -set(CMAKE_SYSTEM_PROCESSOR aarch64) - -## Specify the toolchain -set(TOOLCHAIN "aarch64-linux-android") -set(CMAKE_PREFIX_PATH ${CROSS_NDK_TOOLCHAIN}) -set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-) - -find_program(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++) -find_program(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_LD ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) -find_program(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) - -add_compile_options(--sysroot=${CROSS_ROOTFS}) -add_compile_options(-fPIE) - -## Needed for Android or bionic specific conditionals -add_compile_options(-D__ANDROID__) -add_compile_options(-D__BIONIC__) - -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B ${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/lib/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -fPIE -pie") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) - -set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/arm64/tizen-build-rootfs.sh b/eng/common/cross/arm64/tizen-build-rootfs.sh new file mode 100644 index 0000000000000..13bfddb5e2a70 --- /dev/null +++ b/eng/common/cross/arm64/tizen-build-rootfs.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__TIZEN_CROSSDIR="$__CrossDir/tizen" + +if [[ -z "$ROOTFS_DIR" ]]; then + echo "ROOTFS_DIR is not defined." + exit 1; +fi + +TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp +mkdir -p $TIZEN_TMP_DIR + +# Download files +echo ">>Start downloading files" +VERBOSE=1 $__CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR +echo "<>Start constructing Tizen rootfs" +TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` +cd $ROOTFS_DIR +for f in $TIZEN_RPM_FILES; do + rpm2cpio $f | cpio -idm --quiet +done +echo "<>Start configuring Tizen rootfs" +ln -sfn asm-arm64 ./usr/include/asm +patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +echo "</dev/null; then + VERBOSE=0 +fi + +Log() +{ + if [ $VERBOSE -ge $1 ]; then + echo ${@:2} + fi +} + +Inform() +{ + Log 1 -e "\x1B[0;34m$@\x1B[m" +} + +Debug() +{ + Log 2 -e "\x1B[0;32m$@\x1B[m" +} + +Error() +{ + >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" +} + +Fetch() +{ + URL=$1 + FILE=$2 + PROGRESS=$3 + if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then + CURL_OPT="--progress-bar" + else + CURL_OPT="--silent" + fi + curl $CURL_OPT $URL > $FILE +} + +hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } +hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } +hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } + +TMPDIR=$1 +if [ ! -d $TMPDIR ]; then + TMPDIR=./tizen_tmp + Debug "Create temporary directory : $TMPDIR" + mkdir -p $TMPDIR +fi + +TIZEN_URL=http://download.tizen.org/snapshots/tizen/ +BUILD_XML=build.xml +REPOMD_XML=repomd.xml +PRIMARY_XML=primary.xml +TARGET_URL="http://__not_initialized" + +Xpath_get() +{ + XPATH_RESULT='' + XPATH=$1 + XML_FILE=$2 + RESULT=$(xmllint --xpath $XPATH $XML_FILE) + if [[ -z ${RESULT// } ]]; then + Error "Can not find target from $XML_FILE" + Debug "Xpath = $XPATH" + exit 1 + fi + XPATH_RESULT=$RESULT +} + +fetch_tizen_pkgs_init() +{ + TARGET=$1 + PROFILE=$2 + Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" + + TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs + if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi + mkdir -p $TMP_PKG_DIR + + PKG_URL=$TIZEN_URL/$PROFILE/latest + + BUILD_XML_URL=$PKG_URL/$BUILD_XML + TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML + TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML + TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML + TMP_PRIMARYGZ=${TMP_PRIMARY}.gz + + Fetch $BUILD_XML_URL $TMP_BUILD + + Debug "fetch $BUILD_XML_URL to $TMP_BUILD" + + TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" + Xpath_get $TARGET_XPATH $TMP_BUILD + TARGET_PATH=$XPATH_RESULT + TARGET_URL=$PKG_URL/$TARGET_PATH + + REPOMD_URL=$TARGET_URL/repodata/repomd.xml + PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' + + Fetch $REPOMD_URL $TMP_REPOMD + + Debug "fetch $REPOMD_URL to $TMP_REPOMD" + + Xpath_get $PRIMARY_XPATH $TMP_REPOMD + PRIMARY_XML_PATH=$XPATH_RESULT + PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH + + Fetch $PRIMARY_URL $TMP_PRIMARYGZ + + Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" + + gunzip $TMP_PRIMARYGZ + + Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" +} + +fetch_tizen_pkgs() +{ + ARCH=$1 + PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' + + PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' + + for pkg in ${@:2} + do + Inform "Fetching... $pkg" + XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + PKG_PATH=$XPATH_RESULT + + XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + CHECKSUM=$XPATH_RESULT + + PKG_URL=$TARGET_URL/$PKG_PATH + PKG_FILE=$(basename $PKG_PATH) + PKG_PATH=$TMPDIR/$PKG_FILE + + Debug "Download $PKG_URL to $PKG_PATH" + Fetch $PKG_URL $PKG_PATH true + + echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null + if [ $? -ne 0 ]; then + Error "Fail to fetch $PKG_URL to $PKG_PATH" + Debug "Checksum = $CHECKSUM" + exit 1 + fi + done +} + +Inform "Initialize arm base" +fetch_tizen_pkgs_init standard base +Inform "fetch common packages" +fetch_tizen_pkgs aarch64 gcc glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils +Inform "fetch coreclr packages" +fetch_tizen_pkgs aarch64 lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +Inform "fetch corefx packages" +fetch_tizen_pkgs aarch64 libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel + +Inform "Initialize standard unified" +fetch_tizen_pkgs_init standard unified +Inform "fetch corefx packages" +fetch_tizen_pkgs aarch64 gssdp gssdp-devel tizen-release + diff --git a/eng/common/cross/arm64/tizen/tizen.patch b/eng/common/cross/arm64/tizen/tizen.patch new file mode 100644 index 0000000000000..af7c8be059068 --- /dev/null +++ b/eng/common/cross/arm64/tizen/tizen.patch @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf64-littleaarch64) +-GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-aarch64.so.1 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-aarch64.so.1 ) ) diff --git a/eng/common/cross/armel/armel.jessie.patch b/eng/common/cross/armel/armel.jessie.patch new file mode 100644 index 0000000000000..2d2615619351f --- /dev/null +++ b/eng/common/cross/armel/armel.jessie.patch @@ -0,0 +1,43 @@ +diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h +--- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 ++++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 +@@ -69,10 +69,10 @@ + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_val_compare_and_swap_2(addr, old, _new); ++ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); + #endif + case 4: +- return __sync_val_compare_and_swap_4(addr, old, _new); ++ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_val_compare_and_swap_8(addr, old, _new); +@@ -109,7 +109,7 @@ + return; + #endif + case 4: +- __sync_and_and_fetch_4(addr, val); ++ __sync_and_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -148,7 +148,7 @@ + return; + #endif + case 4: +- __sync_or_and_fetch_4(addr, val); ++ __sync_or_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -187,7 +187,7 @@ + return __sync_add_and_fetch_2(addr, val); + #endif + case 4: +- return __sync_add_and_fetch_4(addr, val); ++ return __sync_add_and_fetch_4((uint32_t*) addr, val); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_add_and_fetch_8(addr, val); diff --git a/eng/common/cross/armel/tizen-build-rootfs.sh b/eng/common/cross/armel/tizen-build-rootfs.sh index 87c48e78fbb47..9a4438af61c21 100755 --- a/eng/common/cross/armel/tizen-build-rootfs.sh +++ b/eng/common/cross/armel/tizen-build-rootfs.sh @@ -9,13 +9,6 @@ if [[ -z "$ROOTFS_DIR" ]]; then exit 1; fi -# Clean-up (TODO-Cleanup: We may already delete $ROOTFS_DIR at ./cross/build-rootfs.sh.) -# hk0110 -if [ -d "$ROOTFS_DIR" ]; then - umount $ROOTFS_DIR/* - rm -rf $ROOTFS_DIR -fi - TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp mkdir -p $TIZEN_TMP_DIR @@ -37,8 +30,6 @@ rm -rf $TIZEN_TMP_DIR # Configure Tizen rootfs echo ">>Start configuring Tizen rootfs" -rm ./usr/lib/libunwind.so -ln -s libunwind.so.8 ./usr/lib/libunwind.so ln -sfn asm-arm ./usr/include/asm patch -p1 < $__TIZEN_CROSSDIR/tizen.patch echo "< $__ToolchainDir/sysroot/android_platform -echo Now run: -echo CONFIG_DIR=\`realpath cross/android/$__BuildArch\` ROOTFS_DIR=\`realpath $__ToolchainDir/sysroot\` ./build.sh cross $__BuildArch skipgenerateversion skipnuget cmakeargs -DENABLE_LLDBPLUGIN=0 +for path in $(wget -qO- http://termux.net/dists/stable/main/binary-$__AndroidArch/Packages |\ + grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do + if [[ "$path" != "Filename:" ]]; then + echo "Working on: $path" + wget -qO- http://termux.net/$path | dpkg -x - "$__TmpDir" + fi +done + +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" + +# Generate platform file for build.sh script to assign to __DistroRid +echo "Generating platform file..." +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform + +echo "Now to build coreclr, libraries and installers; run:" +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory coreclr +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory libraries +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory installer diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index d7d5d7d5f449d..591d8666a84ef 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -1,19 +1,26 @@ #!/usr/bin/env bash +set -e + usage() { - echo "Usage: $0 [BuildArch] [LinuxCodeName] [lldbx.y] [--skipunmount] --rootfsdir ]" + echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [--skipunmount] --rootfsdir ]" echo "BuildArch can be: arm(default), armel, arm64, x86" - echo "LinuxCodeName - optional, Code name for Linux, can be: trusty, xenial(default), zesty, bionic, alpine. If BuildArch is armel, LinuxCodeName is jessie(default) or tizen." - echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine" + echo "CodeName - optional, Code name for Linux, can be: trusty, xenial(default), zesty, bionic, alpine, alpine3.9 or alpine3.13. If BuildArch is armel, LinuxCodeName is jessie(default) or tizen." + echo " for FreeBSD can be: freebsd11 or freebsd12." + echo " for illumos can be: illumos." + echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FReeBSD" echo "--skipunmount - optional, will skip the unmount of rootfs folder." + echo "--use-mirror - optional, use mirror URL to fetch resources, when available." exit 1 } -__LinuxCodeName=xenial +__CodeName=xenial __CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) __InitialDir=$PWD __BuildArch=arm +__AlpineArch=armv7 +__QEMUArch=arm __UbuntuArch=armhf __UbuntuRepo="http://ports.ubuntu.com/" __LLDB_Package="liblldb-3.9-dev" @@ -25,8 +32,10 @@ __UbuntuPackages="build-essential" __AlpinePackages="alpine-base" __AlpinePackages+=" build-base" __AlpinePackages+=" linux-headers" -__AlpinePackages+=" lldb-dev" -__AlpinePackages+=" llvm-dev" +__AlpinePackagesEdgeCommunity=" lldb-dev" +__AlpinePackagesEdgeMain=" llvm10-libs" +__AlpinePackagesEdgeMain+=" python3" +__AlpinePackagesEdgeMain+=" libedit" # symlinks fixer __UbuntuPackages+=" symlinks" @@ -52,13 +61,32 @@ __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" +__FreeBSDBase="12.1-RELEASE" +__FreeBSDPkg="1.12.0" +__FreeBSDPackages="libunwind" +__FreeBSDPackages+=" icu" +__FreeBSDPackages+=" libinotify" +__FreeBSDPackages+=" lttng-ust" +__FreeBSDPackages+=" krb5" + +__IllumosPackages="icu-64.2nb2" +__IllumosPackages+=" mit-krb5-1.16.2nb4" +__IllumosPackages+=" openssl-1.1.1e" +__IllumosPackages+=" zlib-1.2.11" + +# ML.NET dependencies +__UbuntuPackages+=" libomp5" +__UbuntuPackages+=" libomp-dev" + +__UseMirror=0 + __UnprocessedBuildArgs= while :; do if [ $# -le 0 ]; then break fi - lowerI="$(echo $1 | awk '{print tolower($0)}')" + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in -?|-h|--help) usage @@ -67,7 +95,7 @@ while :; do arm) __BuildArch=arm __UbuntuArch=armhf - __AlpineArch=armhf + __AlpineArch=armv7 __QEMUArch=arm ;; arm64) @@ -80,7 +108,14 @@ while :; do __BuildArch=armel __UbuntuArch=armel __UbuntuRepo="http://ftp.debian.org/debian/" - __LinuxCodeName=jessie + __CodeName=jessie + ;; + s390x) + __BuildArch=s390x + __UbuntuArch=s390x + __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuPackages=$(echo ${__UbuntuPackages} | sed 's/ libunwind8-dev//') + unset __LLDB_Package ;; x86) __BuildArch=x86 @@ -109,52 +144,76 @@ while :; do unset __LLDB_Package ;; trusty) # Ubuntu 14.04 - if [ "$__LinuxCodeName" != "jessie" ]; then - __LinuxCodeName=trusty + if [ "$__CodeName" != "jessie" ]; then + __CodeName=trusty fi ;; xenial) # Ubuntu 16.04 - if [ "$__LinuxCodeName" != "jessie" ]; then - __LinuxCodeName=xenial + if [ "$__CodeName" != "jessie" ]; then + __CodeName=xenial fi ;; zesty) # Ubuntu 17.04 - if [ "$__LinuxCodeName" != "jessie" ]; then - __LinuxCodeName=zesty + if [ "$__CodeName" != "jessie" ]; then + __CodeName=zesty fi ;; bionic) # Ubuntu 18.04 - if [ "$__LinuxCodeName" != "jessie" ]; then - __LinuxCodeName=bionic + if [ "$__CodeName" != "jessie" ]; then + __CodeName=bionic fi ;; jessie) # Debian 8 - __LinuxCodeName=jessie + __CodeName=jessie __UbuntuRepo="http://ftp.debian.org/debian/" ;; stretch) # Debian 9 - __LinuxCodeName=stretch + __CodeName=stretch __UbuntuRepo="http://ftp.debian.org/debian/" __LLDB_Package="liblldb-6.0-dev" ;; buster) # Debian 10 - __LinuxCodeName=buster + __CodeName=buster __UbuntuRepo="http://ftp.debian.org/debian/" __LLDB_Package="liblldb-6.0-dev" ;; tizen) - if [ "$__BuildArch" != "armel" ]; then - echo "Tizen is available only for armel." + if [ "$__BuildArch" != "armel" ] && [ "$__BuildArch" != "arm64" ]; then + echo "Tizen is available only for armel and arm64." usage; exit 1; fi - __LinuxCodeName= + __CodeName= __UbuntuRepo= __Tizen=tizen ;; - alpine) - __LinuxCodeName=alpine + alpine|alpine3.9) + __CodeName=alpine __UbuntuRepo= + __AlpineVersion=3.9 + ;; + alpine3.13) + __CodeName=alpine + __UbuntuRepo= + __AlpineVersion=3.13 + # Alpine 3.13 has all the packages we need in the 3.13 repository + __AlpinePackages+=$__AlpinePackagesEdgeCommunity + __AlpinePackagesEdgeCommunity= + __AlpinePackages+=$__AlpinePackagesEdgeMain + __AlpinePackagesEdgeMain= + ;; + freebsd11) + __FreeBSDBase="11.3-RELEASE" + ;& + freebsd12) + __CodeName=freebsd + __BuildArch=x64 + __SkipUnmount=1 + ;; + illumos) + __CodeName=illumos + __BuildArch=x64 + __SkipUnmount=1 ;; --skipunmount) __SkipUnmount=1 @@ -163,6 +222,9 @@ while :; do shift __RootfsDir=$1 ;; + --use-mirror) + __UseMirror=1 + ;; *) __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" ;; @@ -186,46 +248,131 @@ fi if [ -d "$__RootfsDir" ]; then if [ $__SkipUnmount == 0 ]; then - umount $__RootfsDir/* + umount $__RootfsDir/* || true fi rm -rf $__RootfsDir fi -if [[ "$__LinuxCodeName" == "alpine" ]]; then +mkdir -p $__RootfsDir +__RootfsDir="$( cd "$__RootfsDir" && pwd )" + +if [[ "$__CodeName" == "alpine" ]]; then __ApkToolsVersion=2.9.1 - __AlpineVersion=3.7 __ApkToolsDir=$(mktemp -d) wget https://github.com/alpinelinux/apk-tools/releases/download/v$__ApkToolsVersion/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -P $__ApkToolsDir tar -xf $__ApkToolsDir/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -C $__ApkToolsDir mkdir -p $__RootfsDir/usr/bin cp -v /usr/bin/qemu-$__QEMUArch-static $__RootfsDir/usr/bin + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/main \ -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/community \ - -X http://dl-cdn.alpinelinux.org/alpine/edge/testing \ - -X http://dl-cdn.alpinelinux.org/alpine/edge/main \ -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ add $__AlpinePackages + + if [[ -n "$__AlpinePackagesEdgeMain" ]]; then + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/main \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackagesEdgeMain + fi + + if [[ -n "$__AlpinePackagesEdgeCommunity" ]]; then + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/community \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackagesEdgeCommunity + fi + rm -r $__ApkToolsDir -elif [[ -n $__LinuxCodeName ]]; then - qemu-debootstrap --arch $__UbuntuArch $__LinuxCodeName $__RootfsDir $__UbuntuRepo - cp $__CrossDir/$__BuildArch/sources.list.$__LinuxCodeName $__RootfsDir/etc/apt/sources.list +elif [[ "$__CodeName" == "freebsd" ]]; then + mkdir -p $__RootfsDir/usr/local/etc + wget -O - https://download.freebsd.org/ftp/releases/amd64/${__FreeBSDBase}/base.txz | tar -C $__RootfsDir -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version + # For now, ask for 11 ABI even on 12. This can be revisited later. + echo "ABI = \"FreeBSD:11:amd64\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > ${__RootfsDir}/usr/local/etc/pkg.conf + echo "FreeBSD: { url: "pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"${__RootfsDir}/usr/share/keys/pkg\", enabled: yes }" > ${__RootfsDir}/etc/pkg/FreeBSD.conf + mkdir -p $__RootfsDir/tmp + # get and build package manager + wget -O - https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz | tar -C $__RootfsDir/tmp -zxf - + cd $__RootfsDir/tmp/pkg-${__FreeBSDPkg} + # needed for install to succeed + mkdir -p $__RootfsDir/host/etc + ./autogen.sh && ./configure --prefix=$__RootfsDir/host && make && make install + rm -rf $__RootfsDir/tmp/pkg-${__FreeBSDPkg} + # install packages we need. + INSTALL_AS_USER=$(whoami) $__RootfsDir/host/sbin/pkg -r $__RootfsDir -C $__RootfsDir/usr/local/etc/pkg.conf update + INSTALL_AS_USER=$(whoami) $__RootfsDir/host/sbin/pkg -r $__RootfsDir -C $__RootfsDir/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "illumos" ]]; then + mkdir "$__RootfsDir/tmp" + pushd "$__RootfsDir/tmp" + JOBS="$(getconf _NPROCESSORS_ONLN)" + echo "Downloading sysroot." + wget -O - https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - + echo "Building binutils. Please wait.." + wget -O - https://ftp.gnu.org/gnu/binutils/binutils-2.33.1.tar.bz2 | tar -xjf - + mkdir build-binutils && cd build-binutils + ../binutils-2.33.1/configure --prefix="$__RootfsDir" --target="x86_64-sun-solaris2.10" --program-prefix="x86_64-illumos-" --with-sysroot="$__RootfsDir" + make -j "$JOBS" && make install && cd .. + echo "Building gcc. Please wait.." + wget -O - https://ftp.gnu.org/gnu/gcc/gcc-8.4.0/gcc-8.4.0.tar.xz | tar -xJf - + CFLAGS="-fPIC" + CXXFLAGS="-fPIC" + CXXFLAGS_FOR_TARGET="-fPIC" + CFLAGS_FOR_TARGET="-fPIC" + export CFLAGS CXXFLAGS CXXFLAGS_FOR_TARGET CFLAGS_FOR_TARGET + mkdir build-gcc && cd build-gcc + ../gcc-8.4.0/configure --prefix="$__RootfsDir" --target="x86_64-sun-solaris2.10" --program-prefix="x86_64-illumos-" --with-sysroot="$__RootfsDir" --with-gnu-as \ + --with-gnu-ld --disable-nls --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libada --disable-libsanitizer \ + --disable-libquadmath-support --disable-shared --enable-tls + make -j "$JOBS" && make install && cd .. + BaseUrl=https://pkgsrc.joyent.com + if [[ "$__UseMirror" == 1 ]]; then + BaseUrl=http://pkgsrc.smartos.skylime.net + fi + BaseUrl="$BaseUrl"/packages/SmartOS/2020Q1/x86_64/All + echo "Downloading dependencies." + read -ra array <<<"$__IllumosPackages" + for package in "${array[@]}"; do + echo "Installing $package..." + wget "$BaseUrl"/"$package".tgz + ar -x "$package".tgz + tar --skip-old-files -xzf "$package".tmp.tgz -C "$__RootfsDir" 2>/dev/null + done + echo "Cleaning up temporary files." + popd + rm -rf "$__RootfsDir"/{tmp,+*} + mkdir -p "$__RootfsDir"/usr/include/net + mkdir -p "$__RootfsDir"/usr/include/netpacket + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h + wget -P "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h + wget -P "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h +elif [[ -n $__CodeName ]]; then + qemu-debootstrap --arch $__UbuntuArch $__CodeName $__RootfsDir $__UbuntuRepo + cp $__CrossDir/$__BuildArch/sources.list.$__CodeName $__RootfsDir/etc/apt/sources.list chroot $__RootfsDir apt-get update chroot $__RootfsDir apt-get -f -y install chroot $__RootfsDir apt-get -y install $__UbuntuPackages chroot $__RootfsDir symlinks -cr /usr + chroot $__RootfsDir apt-get clean if [ $__SkipUnmount == 0 ]; then - umount $__RootfsDir/* + umount $__RootfsDir/* || true fi - if [[ "$__BuildArch" == "arm" && "$__LinuxCodeName" == "trusty" ]]; then + if [[ "$__BuildArch" == "arm" && "$__CodeName" == "trusty" ]]; then pushd $__RootfsDir patch -p1 < $__CrossDir/$__BuildArch/trusty.patch patch -p1 < $__CrossDir/$__BuildArch/trusty-lttng-2.4.patch popd fi -elif [ "$__Tizen" == "tizen" ]; then + + if [[ "$__BuildArch" == "armel" && "$__CodeName" == "jessie" ]]; then + pushd $__RootfsDir + patch -p1 < $__CrossDir/$__BuildArch/armel.jessie.patch + popd + fi +elif [[ "$__Tizen" == "tizen" ]]; then ROOTFS_DIR=$__RootfsDir $__CrossDir/$__BuildArch/tizen-build-rootfs.sh else echo "Unsupported target platform." diff --git a/eng/common/cross/s390x/sources.list.bionic b/eng/common/cross/s390x/sources.list.bionic new file mode 100644 index 0000000000000..2109557409576 --- /dev/null +++ b/eng/common/cross/s390x/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 071d4112419b8..fc11001aa76c8 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -1,18 +1,27 @@ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) -set(CMAKE_SYSTEM_NAME Linux) +if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) + set(CMAKE_SYSTEM_NAME FreeBSD) +elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) + set(CMAKE_SYSTEM_NAME SunOS) + set(ILLUMOS 1) +else() + set(CMAKE_SYSTEM_NAME Linux) +endif() set(CMAKE_SYSTEM_VERSION 1) if(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) set(TOOLCHAIN "arm-linux-gnueabi") if("$ENV{__DistroRid}" MATCHES "tizen.*") - set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/6.2.1") + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/9.2.0") endif() elseif(TARGET_ARCH_NAME STREQUAL "arm") set(CMAKE_SYSTEM_PROCESSOR armv7l) - if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf) + set(TOOLCHAIN "armv7-alpine-linux-musleabihf") + elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) set(TOOLCHAIN "armv6-alpine-linux-musleabihf") else() set(TOOLCHAIN "arm-linux-gnueabihf") @@ -24,65 +33,148 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") else() set(TOOLCHAIN "aarch64-linux-gnu") endif() + if("$ENV{__DistroRid}" MATCHES "tizen.*") + set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu/9.2.0") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "s390x") + set(CMAKE_SYSTEM_PROCESSOR s390x) + set(TOOLCHAIN "s390x-linux-gnu") elseif(TARGET_ARCH_NAME STREQUAL "x86") set(CMAKE_SYSTEM_PROCESSOR i686) set(TOOLCHAIN "i686-linux-gnu") +elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + set(triple "x86_64-unknown-freebsd11") +elseif (ILLUMOS) + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + set(TOOLCHAIN "x86_64-illumos") else() - message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only armel, arm, arm64 and x86 are supported!") + message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only armel, arm, arm64, s390x and x86 are supported!") +endif() + +if(DEFINED ENV{TOOLCHAIN}) + set(TOOLCHAIN $ENV{TOOLCHAIN}) endif() # Specify include paths -if(TARGET_ARCH_NAME STREQUAL "armel") - if(DEFINED TIZEN_TOOLCHAIN) +if(DEFINED TIZEN_TOOLCHAIN) + if(TARGET_ARCH_NAME STREQUAL "armel") include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi) endif() + if(TARGET_ARCH_NAME STREQUAL "arm64") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu) + endif() endif() -# add_compile_param - adds only new options without duplicates. -# arg0 - list with result options, arg1 - list with new options. -# arg2 - optional argument, quick summary string for optional using CACHE FORCE mode. -macro(add_compile_param) - if(NOT ${ARGC} MATCHES "^(2|3)$") - message(FATAL_ERROR "Wrong using add_compile_param! Two or three parameters must be given! See add_compile_param description.") - endif() - foreach(OPTION ${ARGV1}) - if(NOT ${ARGV0} MATCHES "${OPTION}($| )") - set(${ARGV0} "${${ARGV0}} ${OPTION}") - if(${ARGC} EQUAL "3") # CACHE FORCE mode - set(${ARGV0} "${${ARGV0}}" CACHE STRING "${ARGV2}" FORCE) - endif() +if("$ENV{__DistroRid}" MATCHES "android.*") + if(TARGET_ARCH_NAME STREQUAL "arm") + set(ANDROID_ABI armeabi-v7a) + elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(ANDROID_ABI arm64-v8a) endif() - endforeach() -endmacro() + + # extract platform number required by the NDK's toolchain + string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "$ENV{__DistroRid}") + + set(ANDROID_TOOLCHAIN clang) + set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository + set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") + set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") + + # include official NDK toolchain script + include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + # we cross-compile by instructing clang + set(CMAKE_C_COMPILER_TARGET ${triple}) + set(CMAKE_CXX_COMPILER_TARGET ${triple}) + set(CMAKE_ASM_COMPILER_TARGET ${triple}) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") +elseif(ILLUMOS) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + include_directories(SYSTEM ${CROSS_ROOTFS}/include) + + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + function(locate_toolchain_exec exec var) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) + endfunction() + + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") +else() + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") +endif() # Specify link flags -add_compile_param(CROSS_LINK_FLAGS "--sysroot=${CROSS_ROOTFS}") -add_compile_param(CROSS_LINK_FLAGS "--gcc-toolchain=${CROSS_ROOTFS}/usr") -add_compile_param(CROSS_LINK_FLAGS "--target=${TOOLCHAIN}") -add_compile_param(CROSS_LINK_FLAGS "-fuse-ld=gold") + +function(add_toolchain_linker_flag Flag) + set(Config "${ARGV1}") + set(CONFIG_SUFFIX "") + if (NOT Config STREQUAL "") + set(CONFIG_SUFFIX "_${Config}") + endif() + set("CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}" "${CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}} ${Flag}" PARENT_SCOPE) + set("CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}" "${CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}} ${Flag}" PARENT_SCOPE) +endfunction() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib/${TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}") +endif() if(TARGET_ARCH_NAME STREQUAL "armel") if(DEFINED TIZEN_TOOLCHAIN) # For Tizen only - add_compile_param(CROSS_LINK_FLAGS "-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") - add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/lib") - add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/usr/lib") - add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm64") + if(DEFINED TIZEN_TOOLCHAIN) # For Tizen only + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() elseif(TARGET_ARCH_NAME STREQUAL "x86") - add_compile_param(CROSS_LINK_FLAGS "-m32") + add_toolchain_linker_flag(-m32) +elseif(ILLUMOS) + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") endif() -add_compile_param(CMAKE_EXE_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") -add_compile_param(CMAKE_SHARED_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") -add_compile_param(CMAKE_MODULE_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") - # Specify compile options -add_compile_options("--sysroot=${CROSS_ROOTFS}") -add_compile_options("--target=${TOOLCHAIN}") -add_compile_options("--gcc-toolchain=${CROSS_ROOTFS}/usr") -if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64)$") +if((TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|s390x)$" AND NOT "$ENV{__DistroRid}" MATCHES "android.*") OR ILLUMOS) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) @@ -90,20 +182,33 @@ endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") add_compile_options(-mthumb) - add_compile_options(-mfpu=vfpv3) + if (NOT DEFINED CLR_ARM_FPU_TYPE) + set (CLR_ARM_FPU_TYPE vfpv3) + endif (NOT DEFINED CLR_ARM_FPU_TYPE) + + add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) + if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + set (CLR_ARM_FPU_CAPABILITY 0x7) + endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + + add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) + if(TARGET_ARCH_NAME STREQUAL "armel") add_compile_options(-mfloat-abi=softfp) - if(DEFINED TIZEN_TOOLCHAIN) - add_compile_options(-Wno-deprecated-declarations) # compile-time option - add_compile_options(-D__extern_always_inline=inline) # compile-time option - endif() endif() elseif(TARGET_ARCH_NAME STREQUAL "x86") add_compile_options(-m32) add_compile_options(-Wno-error=unused-command-line-argument) endif() -# Set LLDB include and library paths +if(DEFINED TIZEN_TOOLCHAIN) + if(TARGET_ARCH_NAME MATCHES "^(armel|arm64)$") + add_compile_options(-Wno-deprecated-declarations) # compile-time option + add_compile_options(-D__extern_always_inline=inline) # compile-time option + endif() +endif() + +# Set LLDB include and library paths for builds that need lldb. if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") if(TARGET_ARCH_NAME STREQUAL "x86") set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") @@ -131,7 +236,6 @@ if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") endif() endif() -set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index 06b65342528df..39abdbecdcf11 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -6,7 +6,7 @@ versionEndpoint='https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc verbosity='minimal' while [[ $# > 0 ]]; do - opt="$(echo "$1" | awk '{print tolower($0)}')" + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --darcversion) darcVersion=$2 @@ -68,7 +68,7 @@ function InstallDarcCli { fi fi - local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 index ec3e739fe8367..811f0f717f736 100644 --- a/eng/common/dotnet-install.ps1 +++ b/eng/common/dotnet-install.ps1 @@ -1,28 +1,27 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $verbosity = "minimal", - [string] $architecture = "", - [string] $version = "Latest", - [string] $runtime = "dotnet", - [string] $RuntimeSourceFeed = "", - [string] $RuntimeSourceFeedKey = "" + [string] $verbosity = 'minimal', + [string] $architecture = '', + [string] $version = 'Latest', + [string] $runtime = 'dotnet', + [string] $RuntimeSourceFeed = '', + [string] $RuntimeSourceFeedKey = '' ) . $PSScriptRoot\tools.ps1 -$dotnetRoot = Join-Path $RepoRoot ".dotnet" +$dotnetRoot = Join-Path $RepoRoot '.dotnet' $installdir = $dotnetRoot try { - if ($architecture -and $architecture.Trim() -eq "x86") { - $installdir = Join-Path $installdir "x86" + if ($architecture -and $architecture.Trim() -eq 'x86') { + $installdir = Join-Path $installdir 'x86' } - InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey -} + InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey +} catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index d259a274c780d..d6efeb44340ba 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -11,13 +11,15 @@ while [[ -h "$source" ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +. "$scriptroot/tools.sh" + version='Latest' architecture='' runtime='dotnet' runtimeSourceFeed='' runtimeSourceFeedKey='' while [[ $# > 0 ]]; do - opt="$(echo "$1" | awk '{print tolower($0)}')" + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -version|-v) shift @@ -40,18 +42,42 @@ while [[ $# > 0 ]]; do runtimeSourceFeedKey="$1" ;; *) - echo "Invalid argument: $1" + Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" exit 1 ;; esac shift done -. "$scriptroot/tools.sh" +# Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples +cpuname=$(uname -m) +case $cpuname in + aarch64) + buildarch=arm64 + ;; + amd64|x86_64) + buildarch=x64 + ;; + armv*l) + buildarch=arm + ;; + i686) + buildarch=x86 + ;; + *) + echo "Unknown CPU $cpuname detected, treating it as x64" + buildarch=x64 + ;; +esac + dotnetRoot="$repo_root/.dotnet" +if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then + dotnetRoot="$dotnetRoot/$architecture" +fi + InstallDotNet $dotnetRoot $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { local exit_code=$? - echo "dotnet-install.sh failed (exit code '$exit_code')." >&2 + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 ExitWithExitCode $exit_code } diff --git a/eng/common/enable-cross-org-publishing.ps1 b/eng/common/enable-cross-org-publishing.ps1 index eccbf9f1b16db..da09da4f1fc44 100644 --- a/eng/common/enable-cross-org-publishing.ps1 +++ b/eng/common/enable-cross-org-publishing.ps1 @@ -2,5 +2,12 @@ param( [string] $token ) -Write-Host "##vso[task.setvariable variable=VSS_NUGET_ACCESSTOKEN]$token" -Write-Host "##vso[task.setvariable variable=VSS_NUGET_URI_PREFIXES]https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/" + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +# Write-PipelineSetVariable will no-op if a variable named $ci is not defined +# Since this script is only ever called in AzDO builds, just universally set it +$ci = $true + +Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false +Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false diff --git a/eng/common/generate-graph-files.ps1 b/eng/common/generate-graph-files.ps1 index b056e4c1ac2a3..0728b1a8b570d 100644 --- a/eng/common/generate-graph-files.ps1 +++ b/eng/common/generate-graph-files.ps1 @@ -3,39 +3,39 @@ Param( [Parameter(Mandatory=$true)][string] $gitHubPat, # GitHub personal access token from https://github.com/settings/tokens (no auth scopes needed) [Parameter(Mandatory=$true)][string] $azdoPat, # Azure Dev Ops tokens from https://dev.azure.com/dnceng/_details/security/tokens (code read scope needed) [Parameter(Mandatory=$true)][string] $outputFolder, # Where the graphviz.txt file will be created - [string] $darcVersion = '1.1.0-beta.19175.6', # darc's version + [string] $darcVersion, # darc's version [string] $graphvizVersion = '2.38', # GraphViz version [switch] $includeToolset # Whether the graph should include toolset dependencies or not. i.e. arcade, optimization. For more about # toolset dependencies see https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#toolset-vs-product-dependencies ) -$ErrorActionPreference = "Stop" -. $PSScriptRoot\tools.ps1 - -Import-Module -Name (Join-Path $PSScriptRoot "native\CommonLibrary.psm1") - function CheckExitCode ([string]$stage) { $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { - Write-Host "Something failed in stage: '$stage'. Check for errors above. Exiting now..." + Write-PipelineTelemetryError -Category 'Arcade' -Message "Something failed in stage: '$stage'. Check for errors above. Exiting now..." ExitWithExitCode $exitCode } } try { + $ErrorActionPreference = 'Stop' + . $PSScriptRoot\tools.ps1 + + Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + Push-Location $PSScriptRoot - Write-Host "Installing darc..." + Write-Host 'Installing darc...' . .\darc-init.ps1 -darcVersion $darcVersion - CheckExitCode "Running darc-init" + CheckExitCode 'Running darc-init' - $engCommonBaseDir = Join-Path $PSScriptRoot "native\" + $engCommonBaseDir = Join-Path $PSScriptRoot 'native\' $graphvizInstallDir = CommonLibrary\Get-NativeInstallDirectory - $nativeToolBaseUri = "https://netcorenativeassets.blob.core.windows.net/resource-packages/external" - $installBin = Join-Path $graphvizInstallDir "bin" + $nativeToolBaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external' + $installBin = Join-Path $graphvizInstallDir 'bin' - Write-Host "Installing dot..." + Write-Host 'Installing dot...' .\native\install-tool.ps1 -ToolName graphviz -InstallPath $installBin -BaseUri $nativeToolBaseUri -CommonLibraryDirectory $engCommonBaseDir -Version $graphvizVersion -Verbose $darcExe = "$env:USERPROFILE\.dotnet\tools" @@ -51,37 +51,36 @@ try { $graphVizImageFilePath = "$outputFolder\graph.png" $normalGraphFilePath = "$outputFolder\graph-full.txt" $flatGraphFilePath = "$outputFolder\graph-flat.txt" - $baseOptions = @( "--github-pat", "$gitHubPat", "--azdev-pat", "$azdoPat", "--password", "$barToken" ) + $baseOptions = @( '--github-pat', "$gitHubPat", '--azdev-pat', "$azdoPat", '--password', "$barToken" ) if ($includeToolset) { - Write-Host "Toolsets will be included in the graph..." - $baseOptions += @( "--include-toolset" ) + Write-Host 'Toolsets will be included in the graph...' + $baseOptions += @( '--include-toolset' ) } - Write-Host "Generating standard dependency graph..." + Write-Host 'Generating standard dependency graph...' & "$darcExe" get-dependency-graph @baseOptions --output-file $normalGraphFilePath - CheckExitCode "Generating normal dependency graph" + CheckExitCode 'Generating normal dependency graph' - Write-Host "Generating flat dependency graph and graphviz file..." + Write-Host 'Generating flat dependency graph and graphviz file...' & "$darcExe" get-dependency-graph @baseOptions --flat --coherency --graphviz $graphVizFilePath --output-file $flatGraphFilePath - CheckExitCode "Generating flat and graphviz dependency graph" + CheckExitCode 'Generating flat and graphviz dependency graph' Write-Host "Generating graph image $graphVizFilePath" $dotFilePath = Join-Path $installBin "graphviz\$graphvizVersion\release\bin\dot.exe" & "$dotFilePath" -Tpng -o"$graphVizImageFilePath" "$graphVizFilePath" - CheckExitCode "Generating graphviz image" + CheckExitCode 'Generating graphviz image' Write-Host "'$graphVizFilePath', '$flatGraphFilePath', '$normalGraphFilePath' and '$graphVizImageFilePath' created!" } catch { if (!$includeToolset) { - Write-Host "This might be a toolset repo which includes only toolset dependencies. " -NoNewline -ForegroundColor Yellow - Write-Host "Since -includeToolset is not set there is no graph to create. Include -includeToolset and try again..." -ForegroundColor Yellow + Write-Host 'This might be a toolset repo which includes only toolset dependencies. ' -NoNewline -ForegroundColor Yellow + Write-Host 'Since -includeToolset is not set there is no graph to create. Include -includeToolset and try again...' -ForegroundColor Yellow } - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ ExitWithExitCode 1 } finally { - Pop-Location + Pop-Location } \ No newline at end of file diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 new file mode 100644 index 0000000000000..de348a2e225c0 --- /dev/null +++ b/eng/common/generate-locproject.ps1 @@ -0,0 +1,110 @@ +Param( + [Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here + [string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json + [switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one + [switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally +) + +# Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: +# https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = "Stop" +. $PSScriptRoot\tools.ps1 + +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + +$exclusionsFilePath = "$SourcesDirectory\eng\Localize\LocExclusions.json" +$exclusions = @{ Exclusions = @() } +if (Test-Path -Path $exclusionsFilePath) +{ + $exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json +} + +Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work + +# Template files +$jsonFiles = @() +$jsonFiles += Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\en\..+\.json" } # .NET templating pattern +$jsonFiles += Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern + +$xlfFiles = @() + +$allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" +$langXlfFiles = @() +if ($allXlfFiles) { + $null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' + $firstLangCode = $Matches.1 + $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" +} +$langXlfFiles | ForEach-Object { + $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf + + $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" + $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru +} + +$locFiles = $jsonFiles + $xlfFiles + +$locJson = @{ + Projects = @( + @{ + LanguageSet = $LanguageSet + LocItems = @( + $locFiles | ForEach-Object { + $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($outputPath.Contains($exclusion)) + { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { + Remove-Item -Path $sourceFile + } + if ($continue) + { + if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" + } + } + else { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnName" + OutputPath = $outputPath + } + } + } + } + ) + } + ) +} + +$json = ConvertTo-Json $locJson -Depth 5 +Write-Host "LocProject.json generated:`n`n$json`n`n" +Pop-Location + +if (!$UseCheckedInLocProjectJson) { + New-Item "$SourcesDirectory\eng\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject.json" $json +} +else { + New-Item "$SourcesDirectory\eng\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject-generated.json" $json + + if ((Get-FileHash "$SourcesDirectory\eng\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\eng\Localize\LocProject.json").Hash) { + Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." + + exit 1 + } + else { + Write-Host "Generated LocProject.json and current LocProject.json are identical." + } +} \ No newline at end of file diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index 8cf18bcfebae0..db830c00a6f8d 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -35,7 +35,7 @@ File path to global.json file #> [CmdletBinding(PositionalBinding=$false)] Param ( - [string] $BaseUri = "https://netcorenativeassets.blob.core.windows.net/resource-packages/external", + [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', [string] $InstallDirectory, [switch] $Clean = $False, [switch] $Force = $False, @@ -45,26 +45,27 @@ Param ( ) if (!$GlobalJsonFile) { - $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName "global.json" + $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' } Set-StrictMode -version 2.0 -$ErrorActionPreference="Stop" +$ErrorActionPreference='Stop' -Import-Module -Name (Join-Path $PSScriptRoot "native\CommonLibrary.psm1") +. $PSScriptRoot\pipeline-logging-functions.ps1 +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') try { # Define verbose switch if undefined - $Verbose = $VerbosePreference -Eq "Continue" + $Verbose = $VerbosePreference -Eq 'Continue' - $EngCommonBaseDir = Join-Path $PSScriptRoot "native\" + $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' $NativeBaseDir = $InstallDirectory if (!$NativeBaseDir) { $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory } $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir - $InstallBin = Join-Path $NativeBaseDir "bin" - $InstallerPath = Join-Path $EngCommonBaseDir "install-tool.ps1" + $InstallBin = Join-Path $NativeBaseDir 'bin' + $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' # Process tools list Write-Host "Processing $GlobalJsonFile" @@ -74,7 +75,7 @@ try { } $NativeTools = Get-Content($GlobalJsonFile) -Raw | ConvertFrom-Json | - Select-Object -Expand "native-tools" -ErrorAction SilentlyContinue + Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue if ($NativeTools) { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name @@ -112,18 +113,21 @@ try { } $toolInstallationFailure = $true } else { - Write-Error $errMsg + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg exit 1 } } } if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' exit 1 } } else { - Write-Host "No native tools defined in global.json" + Write-Host 'No native tools defined in global.json' exit 0 } @@ -131,17 +135,18 @@ try { exit 0 } if (Test-Path $InstallBin) { - Write-Host "Native tools are available from" (Convert-Path -Path $InstallBin) + Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" + return $InstallBin } else { - Write-Error "Native tools install directory does not exist, installation failed" + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' exit 1 } exit 0 } catch { - Write-Host $_ - Write-Host $_.Exception - exit 1 + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ + ExitWithExitCode 1 } diff --git a/eng/common/init-tools-native.sh b/eng/common/init-tools-native.sh index 4dafaaca130f2..5bd205b5da3b7 100755 --- a/eng/common/init-tools-native.sh +++ b/eng/common/init-tools-native.sh @@ -12,10 +12,11 @@ retry_wait_time_seconds=30 global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" declare -A native_assets +. $scriptroot/pipeline-logging-functions.sh . $scriptroot/native/common-library.sh while (($# > 0)); do - lowerI="$(echo $1 | awk '{print tolower($0)}')" + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 @@ -33,6 +34,14 @@ while (($# > 0)); do force=true shift 1 ;; + --donotabortonfailure) + donotabortonfailure=true + shift 1 + ;; + --donotdisplaywarnings) + donotdisplaywarnings=true + shift 1 + ;; --downloadretries) download_retries=$2 shift 2 @@ -51,6 +60,8 @@ while (($# > 0)); do echo " - (default) %USERPROFILE%/.netcoreeng/native" echo "" echo " --clean Switch specifying not to install anything, but cleanup native asset folders" + echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" + echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" echo " --force Clean and then install tools" echo " --help Print help and exit" echo "" @@ -65,24 +76,89 @@ while (($# > 0)); do done function ReadGlobalJsonNativeTools { - # Get the native-tools section from the global.json. - local native_tools_section=$(cat $global_json_file | awk '/"native-tools"/,/}/') - # Only extract the contents of the object. - local native_tools_list=$(echo $native_tools_section | awk -F"[{}]" '{print $2}') - native_tools_list=${native_tools_list//[\" ]/} - native_tools_list=$( echo "$native_tools_list" | sed 's/\s//g' | sed 's/,/\n/g' ) - - local old_IFS=$IFS - while read -r line; do - # Lines are of the form: 'tool:version' - IFS=: - while read -r key value; do - native_assets[$key]=$value - done <<< "$line" - done <<< "$native_tools_list" - IFS=$old_IFS - - return 0; + # happy path: we have a proper JSON parsing tool `jq(1)` in PATH! + if command -v jq &> /dev/null; then + + # jq: read each key/value pair under "native-tools" entry and emit: + # KEY="" VALUE="" + # followed by a null byte. + # + # bash: read line with null byte delimeter and push to array (for later `eval`uation). + + while IFS= read -rd '' line; do + native_assets+=("$line") + done < <(jq -r '. | + select(has("native-tools")) | + ."native-tools" | + keys[] as $k | + @sh "KEY=\($k) VALUE=\(.[$k])\u0000"' "$global_json_file") + + return + fi + + # Warning: falling back to manually parsing JSON, which is not recommended. + + # Following routine matches the output and escaping logic of jq(1)'s @sh formatter used above. + # It has been tested with several weird strings with escaped characters in entries (key and value) + # and results were compared with the output of jq(1) in binary representation using xxd(1); + # just before the assignment to 'native_assets' array (above and below). + + # try to capture the section under "native-tools". + if [[ ! "$(cat "$global_json_file")" =~ \"native-tools\"[[:space:]\:\{]*([^\}]+) ]]; then + return + fi + + section="${BASH_REMATCH[1]}" + + parseStarted=0 + possibleEnd=0 + escaping=0 + escaped=0 + isKey=1 + + for (( i=0; i<${#section}; i++ )); do + char="${section:$i:1}" + if ! ((parseStarted)) && [[ "$char" =~ [[:space:],:] ]]; then continue; fi + + if ! ((escaping)) && [[ "$char" == "\\" ]]; then + escaping=1 + elif ((escaping)) && ! ((escaped)); then + escaped=1 + fi + + if ! ((parseStarted)) && [[ "$char" == "\"" ]]; then + parseStarted=1 + possibleEnd=0 + elif [[ "$char" == "'" ]]; then + token="$token'\\\''" + possibleEnd=0 + elif ((escaping)) || [[ "$char" != "\"" ]]; then + token="$token$char" + possibleEnd=1 + fi + + if ((possibleEnd)) && ! ((escaping)) && [[ "$char" == "\"" ]]; then + # Use printf to unescape token to match jq(1)'s @sh formatting rules. + # do not use 'token="$(printf "$token")"' syntax, as $() eats the trailing linefeed. + printf -v token "'$token'" + + if ((isKey)); then + KEY="$token" + isKey=0 + else + line="KEY=$KEY VALUE=$token" + native_assets+=("$line") + isKey=1 + fi + + # reset for next token + parseStarted=0 + token= + elif ((escaping)) && ((escaped)); then + escaping=0 + escaped=0 + fi + done } native_base_dir=$install_directory @@ -91,6 +167,7 @@ if [[ -z $install_directory ]]; then fi install_bin="${native_base_dir}/bin" +installed_any=false ReadGlobalJsonNativeTools @@ -99,14 +176,14 @@ if [[ ${#native_assets[@]} -eq 0 ]]; then exit 0; else native_installer_dir="$scriptroot/native" - for tool in "${!native_assets[@]}" - do - tool_version=${native_assets[$tool]} - installer_name="install-$tool.sh" - installer_command="$native_installer_dir/$installer_name" + for index in "${!native_assets[@]}"; do + eval "${native_assets["$index"]}" + + installer_path="$native_installer_dir/install-$KEY.sh" + installer_command="$installer_path" installer_command+=" --baseuri $base_uri" installer_command+=" --installpath $install_bin" - installer_command+=" --version $tool_version" + installer_command+=" --version $VALUE" echo $installer_command if [[ $force = true ]]; then @@ -117,11 +194,29 @@ else installer_command+=" --clean" fi - $installer_command - - if [[ $? != 0 ]]; then - echo "Execution Failed" >&2 - exit 1 + if [[ -a $installer_path ]]; then + $installer_command + if [[ $? != 0 ]]; then + if [[ $donotabortonfailure = true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + exit 1 + fi + else + $installed_any = true + fi + else + if [[ $donotabortonfailure == true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + exit 1 + fi fi done fi @@ -134,8 +229,10 @@ if [[ -d $install_bin ]]; then echo "Native tools are available from $install_bin" echo "##vso[task.prependpath]$install_bin" else - echo "Native tools install directory does not exist, installation failed" >&2 - exit 1 + if [[ $installed_any = true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" + exit 1 + fi fi exit 0 diff --git a/eng/common/internal-feed-operations.ps1 b/eng/common/internal-feed-operations.ps1 index 66a4b754d4d15..418c09930cf16 100644 --- a/eng/common/internal-feed-operations.ps1 +++ b/eng/common/internal-feed-operations.ps1 @@ -6,9 +6,8 @@ param( [switch] $IsFeedPrivate ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 - . $PSScriptRoot\tools.ps1 # Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed @@ -21,7 +20,7 @@ function SetupCredProvider { ) # Install the Cred Provider NuGet plugin - Write-Host "Setting up Cred Provider NuGet plugin in the agent..." + Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' @@ -29,18 +28,18 @@ function SetupCredProvider { Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." Invoke-WebRequest $url -OutFile installcredprovider.ps1 - Write-Host "Installing plugin..." + Write-Host 'Installing plugin...' .\installcredprovider.ps1 -Force Write-Host "Deleting local copy of 'installcredprovider.ps1'..." Remove-Item .\installcredprovider.ps1 if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { - Write-Host "CredProvider plugin was not installed correctly!" + Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 } else { - Write-Host "CredProvider plugin was installed correctly!" + Write-Host 'CredProvider plugin was installed correctly!' } # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable @@ -49,7 +48,7 @@ function SetupCredProvider { $nugetConfigPath = "$RepoRoot\NuGet.config" if (-Not (Test-Path -Path $nugetConfigPath)) { - Write-Host "NuGet.config file not found in repo's root!" + Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' ExitWithExitCode 1 } @@ -80,7 +79,7 @@ function SetupCredProvider { } else { - Write-Host "No internal endpoints found in NuGet.config" + Write-Host 'No internal endpoints found in NuGet.config' } } @@ -98,7 +97,7 @@ function InstallDotNetSdkAndRestoreArcade { & $dotnet restore $restoreProjPath - Write-Host "Arcade SDK restored!" + Write-Host 'Arcade SDK restored!' if (Test-Path -Path $restoreProjPath) { Remove-Item $restoreProjPath @@ -112,23 +111,22 @@ function InstallDotNetSdkAndRestoreArcade { try { Push-Location $PSScriptRoot - if ($Operation -like "setup") { + if ($Operation -like 'setup') { SetupCredProvider $AuthToken } - elseif ($Operation -like "install-restore") { + elseif ($Operation -like 'install-restore') { InstallDotNetSdkAndRestoreArcade } else { - Write-Host "Unknown operation '$Operation'!" + Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" ExitWithExitCode 1 } } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ ExitWithExitCode 1 } finally { - Pop-Location + Pop-Location } diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh index 5eb546990dec8..e2233e781220f 100755 --- a/eng/common/internal-feed-operations.sh +++ b/eng/common/internal-feed-operations.sh @@ -30,7 +30,7 @@ function SetupCredProvider { rm installcredprovider.sh if [ ! -d "$HOME/.nuget/plugins" ]; then - echo "CredProvider plugin was not installed correctly!" + Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 else echo "CredProvider plugin was installed correctly!" @@ -42,7 +42,7 @@ function SetupCredProvider { local nugetConfigPath="$repo_root/NuGet.config" if [ ! "$nugetConfigPath" ]; then - echo "NuGet.config file not found in repo's root!" + Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" ExitWithExitCode 1 fi @@ -101,7 +101,7 @@ authToken='' repoName='' while [[ $# > 0 ]]; do - opt="$(echo "$1" | awk '{print tolower($0)}')" + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --operation) operation=$2 diff --git a/eng/common/internal/Directory.Build.props b/eng/common/internal/Directory.Build.props index e33179ef37344..dbf99d82a5c2e 100644 --- a/eng/common/internal/Directory.Build.props +++ b/eng/common/internal/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj index 1a39a7ef3f67b..f46d5efe2e32a 100644 --- a/eng/common/internal/Tools.csproj +++ b/eng/common/internal/Tools.csproj @@ -4,6 +4,7 @@ net472 false + false diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 index b37fd3d5e9779..eea19cd8452fd 100644 --- a/eng/common/msbuild.ps1 +++ b/eng/common/msbuild.ps1 @@ -1,10 +1,11 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $verbosity = "minimal", + [string] $verbosity = 'minimal', [bool] $warnAsError = $true, [bool] $nodeReuse = $true, [switch] $ci, [switch] $prepareMachine, + [switch] $excludePrereleaseVS, [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs ) @@ -18,9 +19,8 @@ try { MSBuild @extraArgs } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/msbuild.sh b/eng/common/msbuild.sh index 8160cd5a59d17..20d3dad543520 100755 --- a/eng/common/msbuild.sh +++ b/eng/common/msbuild.sh @@ -19,7 +19,7 @@ prepare_machine=false extra_args='' while (($# > 0)); do - lowerI="$(echo $1 | awk '{print tolower($0)}')" + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --verbosity) verbosity=$2 diff --git a/eng/common/native/CommonLibrary.psm1 b/eng/common/native/CommonLibrary.psm1 index 41416862d9132..adf707c8fe700 100644 --- a/eng/common/native/CommonLibrary.psm1 +++ b/eng/common/native/CommonLibrary.psm1 @@ -48,7 +48,7 @@ function DownloadAndExtract { -Verbose:$Verbose if ($DownloadStatus -Eq $False) { - Write-Error "Download failed" + Write-Error "Download failed from $Uri" return $False } @@ -145,9 +145,12 @@ function Get-File { New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null } + $TempPath = "$Path.tmp" if (Test-Path -IsValid -Path $Uri) { - Write-Verbose "'$Uri' is a file path, copying file to '$Path'" - Copy-Item -Path $Uri -Destination $Path + Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" + Copy-Item -Path $Uri -Destination $TempPath + Write-Verbose "Moving temporary file to '$Path'" + Move-Item -Path $TempPath -Destination $Path return $? } else { @@ -157,8 +160,10 @@ function Get-File { while($Attempt -Lt $DownloadRetries) { try { - Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Path - Write-Verbose "Downloaded to '$Path'" + Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath + Write-Verbose "Downloaded to temporary location '$TempPath'" + Move-Item -Path $TempPath -Destination $Path + Write-Verbose "Moved temporary file to '$Path'" return $True } catch { @@ -359,16 +364,21 @@ function Expand-Zip { return $False } } - if (-Not (Test-Path $OutputDirectory)) { - New-Item -path $OutputDirectory -Force -itemType "Directory" | Out-Null + + $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" + if (Test-Path $TempOutputDirectory) { + Remove-Item $TempOutputDirectory -Force -Recurse } + New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null Add-Type -assembly "system.io.compression.filesystem" - [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$OutputDirectory") + [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") if ($? -Eq $False) { Write-Error "Unable to extract '$ZipPath'" return $False } + + Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory } catch { Write-Host $_ diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh index 271bddfac5a9b..bf272dcf55a53 100755 --- a/eng/common/native/common-library.sh +++ b/eng/common/native/common-library.sh @@ -34,7 +34,7 @@ function ExpandZip { echo "'Force flag enabled, but '$output_directory' exists. Removing directory" rm -rf $output_directory if [[ $? != 0 ]]; then - echo Unable to remove '$output_directory'>&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" return 1 fi fi @@ -45,7 +45,7 @@ function ExpandZip { echo "Extracting archive" tar -xf $zip_path -C $output_directory if [[ $? != 0 ]]; then - echo "Unable to extract '$zip_path'" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" return 1 fi @@ -117,7 +117,7 @@ function DownloadAndExtract { # Download file GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Failed to download '$uri' to '$temp_tool_path'." >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." return 1 fi @@ -125,7 +125,7 @@ function DownloadAndExtract { echo "extracting from $temp_tool_path to $installDir" ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Failed to extract '$temp_tool_path' to '$installDir'." >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." return 1 fi @@ -148,7 +148,7 @@ function NewScriptShim { fi if [[ ! -f $tool_file_path ]]; then - echo "Specified tool file path:'$tool_file_path' does not exist" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" return 1 fi diff --git a/eng/common/native/find-native-compiler.sh b/eng/common/native/find-native-compiler.sh new file mode 100644 index 0000000000000..aed19d07d506f --- /dev/null +++ b/eng/common/native/find-native-compiler.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# This file locates the native compiler with the given name and version and sets the environment variables to locate it. +# + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +if [ $# -lt 0 ] +then + echo "Usage..." + echo "find-native-compiler.sh " + echo "Specify the name of compiler (clang or gcc)." + echo "Specify the major version of compiler." + echo "Specify the minor version of compiler." + exit 1 +fi + +. $scriptroot/../pipeline-logging-functions.sh + +compiler="$1" +cxxCompiler="$compiler++" +majorVersion="$2" +minorVersion="$3" + +if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi + +check_version_exists() { + desired_version=-1 + + # Set up the environment to be used for building with the desired compiler. + if command -v "$compiler-$1.$2" > /dev/null; then + desired_version="-$1.$2" + elif command -v "$compiler$1$2" > /dev/null; then + desired_version="$1$2" + elif command -v "$compiler-$1$2" > /dev/null; then + desired_version="-$1$2" + fi + + echo "$desired_version" +} + +if [ -z "$CLR_CC" ]; then + + # Set default versions + if [ -z "$majorVersion" ]; then + # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. + if [ "$compiler" = "clang" ]; then versions=( 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) + elif [ "$compiler" = "gcc" ]; then versions=( 9 8 7 6 5 4.9 ); fi + + for version in "${versions[@]}"; do + parts=(${version//./ }) + desired_version="$(check_version_exists "${parts[0]}" "${parts[1]}")" + if [ "$desired_version" != "-1" ]; then majorVersion="${parts[0]}"; break; fi + done + + if [ -z "$majorVersion" ]; then + if command -v "$compiler" > /dev/null; then + if [ "$(uname)" != "Darwin" ]; then + Write-PipelineTelemetryError -category "Build" -type "warning" "Specific version of $compiler not found, falling back to use the one in PATH." + fi + export CC="$(command -v "$compiler")" + export CXX="$(command -v "$cxxCompiler")" + else + Write-PipelineTelemetryError -category "Build" "No usable version of $compiler found." + exit 1 + fi + else + if [ "$compiler" = "clang" ] && [ "$majorVersion" -lt 5 ]; then + if [ "$build_arch" = "arm" ] || [ "$build_arch" = "armel" ]; then + if command -v "$compiler" > /dev/null; then + Write-PipelineTelemetryError -category "Build" -type "warning" "Found clang version $majorVersion which is not supported on arm/armel architectures, falling back to use clang from PATH." + export CC="$(command -v "$compiler")" + export CXX="$(command -v "$cxxCompiler")" + else + Write-PipelineTelemetryError -category "Build" "Found clang version $majorVersion which is not supported on arm/armel architectures, and there is no clang in PATH." + exit 1 + fi + fi + fi + fi + else + desired_version="$(check_version_exists "$majorVersion" "$minorVersion")" + if [ "$desired_version" = "-1" ]; then + Write-PipelineTelemetryError -category "Build" "Could not find specific version of $compiler: $majorVersion $minorVersion." + exit 1 + fi + fi + + if [ -z "$CC" ]; then + export CC="$(command -v "$compiler$desired_version")" + export CXX="$(command -v "$cxxCompiler$desired_version")" + if [ -z "$CXX" ]; then export CXX="$(command -v "$cxxCompiler")"; fi + fi +else + if [ ! -f "$CLR_CC" ]; then + Write-PipelineTelemetryError -category "Build" "CLR_CC is set but path '$CLR_CC' does not exist" + exit 1 + fi + export CC="$CLR_CC" + export CXX="$CLR_CXX" +fi + +if [ -z "$CC" ]; then + Write-PipelineTelemetryError -category "Build" "Unable to find $compiler." + exit 1 +fi + +export CCC_CC="$CC" +export CCC_CXX="$CXX" +export SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version")" diff --git a/eng/common/native/install-cmake-test.sh b/eng/common/native/install-cmake-test.sh index 53ddf4e68601b..8a5e7cf0db5a9 100755 --- a/eng/common/native/install-cmake-test.sh +++ b/eng/common/native/install-cmake-test.sh @@ -14,7 +14,7 @@ download_retries=5 retry_wait_time_seconds=30 while (($# > 0)); do - lowerI="$(echo $1 | awk '{print tolower($0)}')" + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 @@ -63,7 +63,7 @@ done tool_name="cmake-test" tool_os=$(GetCurrentOS) -tool_folder=$(echo $tool_os | awk '{print tolower($0)}') +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" tool_arch="x86_64" tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" tool_install_directory="$install_path/$tool_name/$version" @@ -101,7 +101,7 @@ fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Installation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi @@ -110,8 +110,8 @@ fi NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then - echo "Shim generation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi -exit 0 \ No newline at end of file +exit 0 diff --git a/eng/common/native/install-cmake.sh b/eng/common/native/install-cmake.sh index 5f1a182fa9f72..de496beebc5ac 100755 --- a/eng/common/native/install-cmake.sh +++ b/eng/common/native/install-cmake.sh @@ -14,7 +14,7 @@ download_retries=5 retry_wait_time_seconds=30 while (($# > 0)); do - lowerI="$(echo $1 | awk '{print tolower($0)}')" + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 @@ -63,7 +63,7 @@ done tool_name="cmake" tool_os=$(GetCurrentOS) -tool_folder=$(echo $tool_os | awk '{print tolower($0)}') +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" tool_arch="x86_64" tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" tool_install_directory="$install_path/$tool_name/$version" @@ -101,7 +101,7 @@ fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Installation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi @@ -110,8 +110,8 @@ fi NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then - echo "Shim generation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi -exit 0 \ No newline at end of file +exit 0 diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 index 635ab3fd414be..78f2d84a4e4b1 100644 --- a/eng/common/native/install-tool.ps1 +++ b/eng/common/native/install-tool.ps1 @@ -46,6 +46,8 @@ Param ( [int] $RetryWaitTimeInSeconds = 30 ) +. $PSScriptRoot\..\pipeline-logging-functions.ps1 + # Import common library modules Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") @@ -93,7 +95,7 @@ try { -Verbose:$Verbose if ($InstallStatus -Eq $False) { - Write-Error "Installation failed" + Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" exit 1 } } @@ -103,7 +105,7 @@ try { Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" exit 1 } elseif (@($ToolFilePath).Length -Lt 1) { - Write-Error "$ToolName was not found in $ToolFilePath." + Write-Host "$ToolName was not found in $ToolInstallDirectory." exit 1 } @@ -117,14 +119,14 @@ try { -Verbose:$Verbose if ($GenerateShimStatus -Eq $False) { - Write-Error "Generate shim failed" + Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" return 1 } exit 0 } catch { - Write-Host $_ - Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ exit 1 } diff --git a/eng/common/performance/perfhelixpublish.proj b/eng/common/performance/perfhelixpublish.proj deleted file mode 100644 index e5826b532370b..0000000000000 --- a/eng/common/performance/perfhelixpublish.proj +++ /dev/null @@ -1,102 +0,0 @@ - - - - %HELIX_CORRELATION_PAYLOAD%\performance\scripts\benchmarks_ci.py --csproj %HELIX_CORRELATION_PAYLOAD%\performance\$(TargetCsproj) - --dotnet-versions %DOTNET_VERSION% --cli-source-info args --cli-branch %PERFLAB_BRANCH% --cli-commit-sha %PERFLAB_HASH% --cli-repository https://github.com/%PERFLAB_REPO% --cli-source-timestamp %PERFLAB_BUILDTIMESTAMP% - py -3 - %HELIX_CORRELATION_PAYLOAD%\Core_Root\CoreRun.exe - %HELIX_CORRELATION_PAYLOAD%\Baseline_Core_Root\CoreRun.exe - $(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd - %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts - %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts_Baseline - %HELIX_CORRELATION_PAYLOAD%\performance\src\tools\ResultsComparer\ResultsComparer.csproj - %HELIX_CORRELATION_PAYLOAD%\performance\tools\dotnet\$(Architecture)\dotnet.exe - %25%25 - %HELIX_WORKITEM_ROOT%\testResults.xml - - - - $HELIX_CORRELATION_PAYLOAD - $(BaseDirectory)/performance - - - - $HELIX_WORKITEM_PAYLOAD - $(BaseDirectory) - - - - $(PerformanceDirectory)/scripts/benchmarks_ci.py --csproj $(PerformanceDirectory)/$(TargetCsproj) - --dotnet-versions $DOTNET_VERSION --cli-source-info args --cli-branch $PERFLAB_BRANCH --cli-commit-sha $PERFLAB_HASH --cli-repository https://github.com/$PERFLAB_REPO --cli-source-timestamp $PERFLAB_BUILDTIMESTAMP - python3 - $(BaseDirectory)/Core_Root/corerun - $(BaseDirectory)/Baseline_Core_Root/corerun - $(HelixPreCommands);chmod +x $(PerformanceDirectory)/tools/machine-setup.sh;. $(PerformanceDirectory)/tools/machine-setup.sh - $(BaseDirectory)/artifacts/BenchmarkDotNet.Artifacts - $(BaseDirectory)/artifacts/BenchmarkDotNet.Artifacts_Baseline - $(PerformanceDirectory)/src/tools/ResultsComparer/ResultsComparer.csproj - $(PerformanceDirectory)/tools/dotnet/$(Architecture)/dotnet - %25 - $HELIX_WORKITEM_ROOT/testResults.xml - - - - --corerun $(CoreRun) - - - - --corerun $(BaselineCoreRun) - - - - $(Python) $(WorkItemCommand) --incremental no --architecture $(Architecture) -f $(_Framework) $(PerfLabArguments) - - - - $(WorkItemCommand) $(CliArguments) - - - - - %(Identity) - - - - - 5 - - - - - - - - - - - false - - - - - - $(WorkItemDirectory) - $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - $(DotnetExe) run -f $(_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults);$(FinalCommand) - 4:00 - - - - - - $(WorkItemDirectory) - $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument)" - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)" - $(DotnetExe) run -f $(_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults) - 4:00 - - - \ No newline at end of file diff --git a/eng/common/performance/performance-setup.ps1 b/eng/common/performance/performance-setup.ps1 deleted file mode 100644 index ec41965fc895d..0000000000000 --- a/eng/common/performance/performance-setup.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -Param( - [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, - [string] $CoreRootDirectory, - [string] $BaselineCoreRootDirectory, - [string] $Architecture="x64", - [string] $Framework="netcoreapp5.0", - [string] $CompilationMode="Tiered", - [string] $Repository=$env:BUILD_REPOSITORY_NAME, - [string] $Branch=$env:BUILD_SOURCEBRANCH, - [string] $CommitSha=$env:BUILD_SOURCEVERSION, - [string] $BuildNumber=$env:BUILD_BUILDNUMBER, - [string] $RunCategories="coreclr corefx", - [string] $Csproj="src\benchmarks\micro\MicroBenchmarks.csproj", - [string] $Kind="micro", - [switch] $Internal, - [switch] $Compare, - [string] $Configurations="CompilationMode=$CompilationMode" -) - -$RunFromPerformanceRepo = ($Repository -eq "dotnet/performance") -or ($Repository -eq "dotnet-performance") -$UseCoreRun = ($CoreRootDirectory -ne [string]::Empty) -$UseBaselineCoreRun = ($BaselineCoreRootDirectory -ne [string]::Empty) - -$PayloadDirectory = (Join-Path $SourceDirectory "Payload") -$PerformanceDirectory = (Join-Path $PayloadDirectory "performance") -$WorkItemDirectory = (Join-Path $SourceDirectory "workitem") -$ExtraBenchmarkDotNetArguments = "--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true" -$Creator = $env:BUILD_DEFINITIONNAME -$PerfLabArguments = "" -$HelixSourcePrefix = "pr" - -$Queue = "Windows.10.Amd64.ClientRS4.DevEx.15.8.Open" - -if ($Framework.StartsWith("netcoreapp")) { - $Queue = "Windows.10.Amd64.ClientRS5.Open" -} - -if ($Compare) { - $Queue = "Windows.10.Amd64.19H1.Tiger.Perf.Open" - $PerfLabArguments = "" - $ExtraBenchmarkDotNetArguments = "" -} - -if ($Internal) { - $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" - $PerfLabArguments = "--upload-to-perflab-container" - $ExtraBenchmarkDotNetArguments = "" - $Creator = "" - $HelixSourcePrefix = "official" -} - -$CommonSetupArguments="--frameworks $Framework --queue $Queue --build-number $BuildNumber --build-configs $Configurations" -$SetupArguments = "--repository https://github.com/$Repository --branch $Branch --get-perf-hash --commit-sha $CommitSha $CommonSetupArguments" - -if ($RunFromPerformanceRepo) { - $SetupArguments = "--perf-hash $CommitSha $CommonSetupArguments" - - robocopy $SourceDirectory $PerformanceDirectory /E /XD $PayloadDirectory $SourceDirectory\artifacts $SourceDirectory\.git -} -else { - git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory -} - -if ($UseCoreRun) { - $NewCoreRoot = (Join-Path $PayloadDirectory "Core_Root") - Move-Item -Path $CoreRootDirectory -Destination $NewCoreRoot -} -if ($UseBaselineCoreRun) { - $NewBaselineCoreRoot = (Join-Path $PayloadDirectory "Baseline_Core_Root") - Move-Item -Path $BaselineCoreRootDirectory -Destination $NewBaselineCoreRoot -} - -$DocsDir = (Join-Path $PerformanceDirectory "docs") -robocopy $DocsDir $WorkItemDirectory - -# Set variables that we will need to have in future steps -$ci = $true - -. "$PSScriptRoot\..\pipeline-logging-functions.ps1" - -# Directories -Write-PipelineSetVariable -Name 'PayloadDirectory' -Value "$PayloadDirectory" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'PerformanceDirectory' -Value "$PerformanceDirectory" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'WorkItemDirectory' -Value "$WorkItemDirectory" -IsMultiJobVariable $false - -# Script Arguments -Write-PipelineSetVariable -Name 'Python' -Value "py -3" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'ExtraBenchmarkDotNetArguments' -Value "$ExtraBenchmarkDotNetArguments" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'SetupArguments' -Value "$SetupArguments" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'PerfLabArguments' -Value "$PerfLabArguments" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'BDNCategories' -Value "$RunCategories" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'TargetCsproj' -Value "$Csproj" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Kind' -Value "$Kind" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Architecture' -Value "$Architecture" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'UseCoreRun' -Value "$UseCoreRun" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'UseBaselineCoreRun' -Value "$UseBaselineCoreRun" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'RunFromPerfRepo' -Value "$RunFromPerformanceRepo" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Compare' -Value "$Compare" -IsMultiJobVariable $false - -# Helix Arguments -Write-PipelineSetVariable -Name 'Creator' -Value "$Creator" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Queue' -Value "$Queue" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'HelixSourcePrefix' -Value "$HelixSourcePrefix" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name '_BuildConfig' -Value "$Architecture.$Kind.$Framework" -IsMultiJobVariable $false - -exit 0 \ No newline at end of file diff --git a/eng/common/performance/performance-setup.sh b/eng/common/performance/performance-setup.sh deleted file mode 100755 index 2f2092166e43f..0000000000000 --- a/eng/common/performance/performance-setup.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env bash - -source_directory=$BUILD_SOURCESDIRECTORY -core_root_directory= -baseline_core_root_directory= -architecture=x64 -framework=netcoreapp5.0 -compilation_mode=tiered -repository=$BUILD_REPOSITORY_NAME -branch=$BUILD_SOURCEBRANCH -commit_sha=$BUILD_SOURCEVERSION -build_number=$BUILD_BUILDNUMBER -internal=false -compare=false -kind="micro" -run_categories="coreclr corefx" -csproj="src\benchmarks\micro\MicroBenchmarks.csproj" -configurations= -run_from_perf_repo=false -use_core_run=true -use_baseline_core_run=true - -while (($# > 0)); do - lowerI="$(echo $1 | awk '{print tolower($0)}')" - case $lowerI in - --sourcedirectory) - source_directory=$2 - shift 2 - ;; - --corerootdirectory) - core_root_directory=$2 - shift 2 - ;; - --baselinecorerootdirectory) - baseline_core_root_directory=$2 - shift 2 - ;; - --architecture) - architecture=$2 - shift 2 - ;; - --framework) - framework=$2 - shift 2 - ;; - --compilationmode) - compilation_mode=$2 - shift 2 - ;; - --repository) - repository=$2 - shift 2 - ;; - --branch) - branch=$2 - shift 2 - ;; - --commitsha) - commit_sha=$2 - shift 2 - ;; - --buildnumber) - build_number=$2 - shift 2 - ;; - --kind) - kind=$2 - shift 2 - ;; - --runcategories) - run_categories=$2 - shift 2 - ;; - --csproj) - csproj=$2 - shift 2 - ;; - --internal) - internal=true - shift 1 - ;; - --compare) - compare=true - shift 1 - ;; - --configurations) - configurations=$2 - shift 2 - ;; - --help) - echo "Common settings:" - echo " --corerootdirectory Directory where Core_Root exists, if running perf testing with --corerun" - echo " --architecture Architecture of the testing being run" - echo " --configurations List of key=value pairs that will be passed to perf testing infrastructure." - echo " ex: --configurations \"CompilationMode=Tiered OptimzationLevel=PGO\"" - echo " --help Print help and exit" - echo "" - echo "Advanced settings:" - echo " --framework The framework to run, if not running in master" - echo " --compliationmode The compilation mode if not passing --configurations" - echo " --sourcedirectory The directory of the sources. Defaults to env:BUILD_SOURCESDIRECTORY" - echo " --repository The name of the repository in the / format. Defaults to env:BUILD_REPOSITORY_NAME" - echo " --branch The name of the branch. Defaults to env:BUILD_SOURCEBRANCH" - echo " --commitsha The commit sha1 to run against. Defaults to env:BUILD_SOURCEVERSION" - echo " --buildnumber The build number currently running. Defaults to env:BUILD_BUILDNUMBER" - echo " --csproj The relative path to the benchmark csproj whose tests should be run. Defaults to src\benchmarks\micro\MicroBenchmarks.csproj" - echo " --kind Related to csproj. The kind of benchmarks that should be run. Defaults to micro" - echo " --runcategories Related to csproj. Categories of benchmarks to run. Defaults to \"coreclr corefx\"" - echo " --internal If the benchmarks are running as an official job." - echo "" - exit 0 - ;; - esac -done - -if [ "$repository" == "dotnet/performance" ] || [ "$repository" == "dotnet-performance" ]; then - run_from_perf_repo=true -fi - -if [ -z "$configurations" ]; then - configurations="CompliationMode=$compilation_mode" -fi - -if [ -z "$core_root_directory" ]; then - use_core_run=false -fi - -if [ -z "$baseline_core_root_directory" ]; then - use_baseline_core_run=false -fi - -payload_directory=$source_directory/Payload -performance_directory=$payload_directory/performance -workitem_directory=$source_directory/workitem -extra_benchmark_dotnet_arguments="--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true" -perflab_arguments= -queue=Ubuntu.1804.Amd64.Open -creator=$BUILD_DEFINITIONNAME -helix_source_prefix="pr" - -if [[ "$compare" == true ]]; then - extra_benchmark_dotnet_arguments= - perflab_arguments= - - # No open queues for arm64 - if [[ "$architecture" = "arm64" ]]; then - echo "Compare not available for arm64" - exit 1 - fi - - queue=Ubuntu.1804.Amd64.Tiger.Perf.Open -fi - -if [[ "$internal" == true ]]; then - perflab_arguments="--upload-to-perflab-container" - helix_source_prefix="official" - creator= - extra_benchmark_dotnet_arguments= - - if [[ "$architecture" = "arm64" ]]; then - queue=Ubuntu.1804.Arm64.Perf - else - queue=Ubuntu.1804.Amd64.Tiger.Perf - fi -fi - -common_setup_arguments="--frameworks $framework --queue $queue --build-number $build_number --build-configs $configurations" -setup_arguments="--repository https://github.com/$repository --branch $branch --get-perf-hash --commit-sha $commit_sha $common_setup_arguments" - -if [[ "$run_from_perf_repo" = true ]]; then - payload_directory= - workitem_directory=$source_directory - performance_directory=$workitem_directory - setup_arguments="--perf-hash $commit_sha $common_setup_arguments" -else - git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $performance_directory - - docs_directory=$performance_directory/docs - mv $docs_directory $workitem_directory -fi - -if [[ "$use_core_run" = true ]]; then - new_core_root=$payload_directory/Core_Root - mv $core_root_directory $new_core_root -fi - -if [[ "$use_baseline_core_run" = true ]]; then - new_baseline_core_root=$payload_directory/Baseline_Core_Root - mv $baseline_core_root_directory $new_baseline_core_root -fi - -ci=true - -_script_dir=$(pwd)/eng/common -. "$_script_dir/pipeline-logging-functions.sh" - -# Make sure all of our variables are available for future steps -Write-PipelineSetVariable -name "UseCoreRun" -value "$use_core_run" -is_multi_job_variable false -Write-PipelineSetVariable -name "UseBaselineCoreRun" -value "$use_baseline_core_run" -is_multi_job_variable false -Write-PipelineSetVariable -name "Architecture" -value "$architecture" -is_multi_job_variable false -Write-PipelineSetVariable -name "PayloadDirectory" -value "$payload_directory" -is_multi_job_variable false -Write-PipelineSetVariable -name "PerformanceDirectory" -value "$performance_directory" -is_multi_job_variable false -Write-PipelineSetVariable -name "WorkItemDirectory" -value "$workitem_directory" -is_multi_job_variable false -Write-PipelineSetVariable -name "Queue" -value "$queue" -is_multi_job_variable false -Write-PipelineSetVariable -name "SetupArguments" -value "$setup_arguments" -is_multi_job_variable false -Write-PipelineSetVariable -name "Python" -value "$python3" -is_multi_job_variable false -Write-PipelineSetVariable -name "PerfLabArguments" -value "$perflab_arguments" -is_multi_job_variable false -Write-PipelineSetVariable -name "ExtraBenchmarkDotNetArguments" -value "$extra_benchmark_dotnet_arguments" -is_multi_job_variable false -Write-PipelineSetVariable -name "BDNCategories" -value "$run_categories" -is_multi_job_variable false -Write-PipelineSetVariable -name "TargetCsproj" -value "$csproj" -is_multi_job_variable false -Write-PipelineSetVariable -name "RunFromPerfRepo" -value "$run_from_perf_repo" -is_multi_job_variable false -Write-PipelineSetVariable -name "Creator" -value "$creator" -is_multi_job_variable false -Write-PipelineSetVariable -name "HelixSourcePrefix" -value "$helix_source_prefix" -is_multi_job_variable false -Write-PipelineSetVariable -name "Kind" -value "$kind" -is_multi_job_variable false -Write-PipelineSetVariable -name "_BuildConfig" -value "$architecture.$kind.$framework" -is_multi_job_variable false -Write-PipelineSetVariable -name "Compare" -value "$compare" -is_multi_job_variable false diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 index af5f48aacebc6..8e422c561e4bd 100644 --- a/eng/common/pipeline-logging-functions.ps1 +++ b/eng/common/pipeline-logging-functions.ps1 @@ -12,6 +12,7 @@ $script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%" # TODO: BUG: Escape % ??? # TODO: Add test to verify don't need to escape "=". +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTelemetryError { [CmdletBinding()] param( @@ -25,80 +26,101 @@ function Write-PipelineTelemetryError { [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, - [switch]$AsOutput) + [switch]$AsOutput, + [switch]$Force) - $PSBoundParameters.Remove("Category") | Out-Null + $PSBoundParameters.Remove('Category') | Out-Null + if ($Force -Or ((Test-Path variable:ci) -And $ci)) { $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" - $PSBoundParameters.Remove("Message") | Out-Null - $PSBoundParameters.Add("Message", $Message) - - Write-PipelineTaskError @PSBoundParameters + } + $PSBoundParameters.Remove('Message') | Out-Null + $PSBoundParameters.Add('Message', $Message) + Write-PipelineTaskError @PSBoundParameters } +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTaskError { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] - [string]$Message, - [Parameter(Mandatory = $false)] - [string]$Type = 'error', - [string]$ErrCode, - [string]$SourcePath, - [string]$LineNumber, - [string]$ColumnNumber, - [switch]$AsOutput) - - if(!$ci) { - if($Type -eq 'error') { - Write-Host $Message -ForegroundColor Red - return + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force + ) + + if (!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { + if ($Type -eq 'error') { + Write-Host $Message -ForegroundColor Red + return } elseif ($Type -eq 'warning') { - Write-Host $Message -ForegroundColor Yellow - return + Write-Host $Message -ForegroundColor Yellow + return } - } - - if(($Type -ne 'error') -and ($Type -ne 'warning')) { + } + + if (($Type -ne 'error') -and ($Type -ne 'warning')) { Write-Host $Message return - } - if(-not $PSBoundParameters.ContainsKey('Type')) { + } + $PSBoundParameters.Remove('Force') | Out-Null + if (-not $PSBoundParameters.ContainsKey('Type')) { $PSBoundParameters.Add('Type', 'error') - } - Write-LogIssue @PSBoundParameters - } + } + Write-LogIssue @PSBoundParameters +} - function Write-PipelineSetVariable { +function Write-PipelineSetVariable { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] - [string]$Name, - [string]$Value, - [switch]$Secret, - [switch]$AsOutput, - [bool]$IsMultiJobVariable=$true) - - if($ci) { + [Parameter(Mandatory = $true)] + [string]$Name, + [string]$Value, + [switch]$Secret, + [switch]$AsOutput, + [bool]$IsMultiJobVariable = $true) + + if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ - 'variable' = $Name - 'isSecret' = $Secret - 'isOutput' = $IsMultiJobVariable + 'variable' = $Name + 'isSecret' = $Secret + 'isOutput' = $IsMultiJobVariable } -AsOutput:$AsOutput - } - } + } +} - function Write-PipelinePrependPath { +function Write-PipelinePrependPath { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] - [string]$Path, - [switch]$AsOutput) - if($ci) { + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput - } - } + } +} + +function Write-PipelineSetResult { + [CmdletBinding()] + param( + [ValidateSet("Succeeded", "SucceededWithIssues", "Failed", "Cancelled", "Skipped")] + [Parameter(Mandatory = $true)] + [string]$Result, + [string]$Message) + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'complete' -Data $Message -Properties @{ + 'result' = $Result + } + } +} <######################################## # Private functions. @@ -115,7 +137,8 @@ function Format-LoggingCommandData { foreach ($mapping in $script:loggingCommandEscapeMappings) { $Value = $Value.Replace($mapping.Token, $mapping.Replacement) } - } else { + } + else { for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { $mapping = $script:loggingCommandEscapeMappings[$i] $Value = $Value.Replace($mapping.Replacement, $mapping.Token) @@ -148,7 +171,8 @@ function Format-LoggingCommand { if ($first) { $null = $sb.Append(' ') $first = $false - } else { + } + else { $null = $sb.Append(';') } @@ -185,7 +209,8 @@ function Write-LoggingCommand { $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties if ($AsOutput) { $command - } else { + } + else { Write-Host $command } } @@ -204,12 +229,12 @@ function Write-LogIssue { [switch]$AsOutput) $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ - 'type' = $Type - 'code' = $ErrCode - 'sourcepath' = $SourcePath - 'linenumber' = $LineNumber - 'columnnumber' = $ColumnNumber - } + 'type' = $Type + 'code' = $ErrCode + 'sourcepath' = $SourcePath + 'linenumber' = $LineNumber + 'columnnumber' = $ColumnNumber + } if ($AsOutput) { return $command } @@ -221,7 +246,8 @@ function Write-LogIssue { $foregroundColor = [System.ConsoleColor]::Red $backgroundColor = [System.ConsoleColor]::Black } - } else { + } + else { $foregroundColor = $host.PrivateData.WarningForegroundColor $backgroundColor = $host.PrivateData.WarningBackgroundColor if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { @@ -231,4 +257,4 @@ function Write-LogIssue { } Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor -} \ No newline at end of file +} diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh index 1c560a5061325..6a0b2255e9118 100755 --- a/eng/common/pipeline-logging-functions.sh +++ b/eng/common/pipeline-logging-functions.sh @@ -2,15 +2,19 @@ function Write-PipelineTelemetryError { local telemetry_category='' + local force=false local function_args=() local message='' while [[ $# -gt 0 ]]; do - opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -category|-c) telemetry_category=$2 shift ;; + -force|-f) + force=true + ;; -*) function_args+=("$1 $2") shift @@ -22,31 +26,29 @@ function Write-PipelineTelemetryError { shift done - if [[ "$ci" != true ]]; then + if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$message" >&2 return fi + if [[ $force == true ]]; then + function_args+=("-force") + fi message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" function_args+=("$message") - - Write-PipelineTaskError $function_args + Write-PipelineTaskError ${function_args[@]} } function Write-PipelineTaskError { - if [[ "$ci" != true ]]; then - echo "$@" >&2 - return - fi - local message_type="error" local sourcepath='' local linenumber='' local columnnumber='' local error_code='' + local force=false while [[ $# -gt 0 ]]; do - opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -type|-t) message_type=$2 @@ -68,6 +70,9 @@ function Write-PipelineTaskError { error_code=$2 shift ;; + -force|-f) + force=true + ;; *) break ;; @@ -76,6 +81,11 @@ function Write-PipelineTaskError { shift done + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$@" >&2 + return + fi + local message="##vso[task.logissue" message="$message type=$message_type" @@ -112,7 +122,7 @@ function Write-PipelineSetVariable { local is_multi_job_variable=true while [[ $# -gt 0 ]]; do - opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -name|-n) name=$2 @@ -154,7 +164,7 @@ function Write-PipelinePrependPath { local prepend_path='' while [[ $# -gt 0 ]]; do - opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -path|-p) prepend_path=$2 @@ -169,4 +179,28 @@ function Write-PipelinePrependPath { if [[ "$ci" == true ]]; then echo "##vso[task.prependpath]$prepend_path" fi -} \ No newline at end of file +} + +function Write-PipelineSetResult { + local result='' + local message='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -result|-r) + result=$2 + shift + ;; + -message|-m) + message=$2 + shift + ;; + esac + shift + done + + if [[ "$ci" == true ]]; then + echo "##vso[task.complete result=$result;]$message" + fi +} diff --git a/eng/common/post-build/promote-build.ps1 b/eng/common/post-build/add-build-to-channel.ps1 similarity index 55% rename from eng/common/post-build/promote-build.ps1 rename to eng/common/post-build/add-build-to-channel.ps1 index e5ae85f251797..de2d957922a65 100644 --- a/eng/common/post-build/promote-build.ps1 +++ b/eng/common/post-build/add-build-to-channel.ps1 @@ -2,26 +2,26 @@ param( [Parameter(Mandatory=$true)][int] $BuildId, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' ) -. $PSScriptRoot\post-build-utils.ps1 - try { + . $PSScriptRoot\post-build-utils.ps1 + # Check that the channel we are going to promote the build to exist $channelInfo = Get-MaestroChannel -ChannelId $ChannelId if (!$channelInfo) { - Write-Host "Channel with BAR ID $ChannelId was not found in BAR!" + Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" ExitWithExitCode 1 } - # Get info about which channels the build has already been promoted to + # Get info about which channel(s) the build has already been promoted to $buildInfo = Get-MaestroBuild -BuildId $BuildId if (!$buildInfo) { - Write-Host "Build with BAR ID $BuildId was not found in BAR!" + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" ExitWithExitCode 1 } @@ -39,10 +39,10 @@ try { Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId - Write-Host "done." + Write-Host 'done.' } catch { - Write-Host "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" Write-Host $_ - Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" + ExitWithExitCode 1 } diff --git a/eng/common/post-build/check-channel-consistency.ps1 b/eng/common/post-build/check-channel-consistency.ps1 new file mode 100644 index 0000000000000..63f3464c986a7 --- /dev/null +++ b/eng/common/post-build/check-channel-consistency.ps1 @@ -0,0 +1,40 @@ +param( + [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to + [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + if ($PromoteToChannels -eq "") { + Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." + ExitWithExitCode 0 + } + + # Check that every channel that Maestro told to promote the build to + # is available in YAML + $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } + + $hasErrors = $false + + foreach ($id in $PromoteToChannelsIds) { + if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { + Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." + $hasErrors = $true + } + } + + # The `Write-PipelineTaskError` doesn't error the script and we might report several errors + # in the previous lines. The check below makes sure that we return an error state from the + # script if we reported any validation error + if ($hasErrors) { + ExitWithExitCode 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/darc-gather-drop.ps1 b/eng/common/post-build/darc-gather-drop.ps1 deleted file mode 100644 index 89854d3c1c2ac..0000000000000 --- a/eng/common/post-build/darc-gather-drop.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -param( - [Parameter(Mandatory=$true)][int] $BarBuildId, # ID of the build which assets should be downloaded - [Parameter(Mandatory=$true)][string] $DropLocation, # Where the assets should be downloaded to - [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, # Token used to access Maestro API - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", # Maestro API URL - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" # Version of Maestro API to use -) - -. $PSScriptRoot\post-build-utils.ps1 - -try { - Write-Host "Installing DARC ..." - - . $PSScriptRoot\..\darc-init.ps1 - $exitCode = $LASTEXITCODE - - if ($exitCode -ne 0) { - Write-PipelineTaskError "Something failed while running 'darc-init.ps1'. Check for errors above. Exiting now..." - ExitWithExitCode $exitCode - } - - # For now, only use a dry run. - # Ideally we would change darc to enable a quick request that - # would check whether the file exists that you can download it, - # and that it won't conflict with other files. - # https://github.com/dotnet/arcade/issues/3674 - # Right now we can't remove continue-on-error because we ocassionally will have - # dependencies that have no associated builds (e.g. an old dependency). - # We need to add an option to baseline specific dependencies away, or add them manually - # to the BAR. - darc gather-drop --non-shipping ` - --dry-run ` - --continue-on-error ` - --id $BarBuildId ` - --output-dir $DropLocation ` - --bar-uri $MaestroApiEndpoint ` - --password $MaestroApiAccessToken ` - --latest-location -} -catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - ExitWithExitCode 1 -} diff --git a/eng/common/post-build/nuget-validation.ps1 b/eng/common/post-build/nuget-validation.ps1 index 78ed0d540f50a..dab3534ab5389 100644 --- a/eng/common/post-build/nuget-validation.ps1 +++ b/eng/common/post-build/nuget-validation.ps1 @@ -6,20 +6,19 @@ param( [Parameter(Mandatory=$true)][string] $ToolDestinationPath # Where the validation tool should be downloaded to ) -. $PSScriptRoot\post-build-utils.ps1 - try { - $url = "https://raw.githubusercontent.com/NuGet/NuGetGallery/jver-verify/src/VerifyMicrosoftPackage/verify.ps1" + . $PSScriptRoot\post-build-utils.ps1 + + $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' - New-Item -ItemType "directory" -Path ${ToolDestinationPath} -Force + New-Item -ItemType 'directory' -Path ${ToolDestinationPath} -Force Invoke-WebRequest $url -OutFile ${ToolDestinationPath}\verify.ps1 & ${ToolDestinationPath}\verify.ps1 ${PackagesPath}\*.nupkg } catch { - Write-PipelineTaskError "NuGet package validation failed. Please check error logs." - Write-Host $_ Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/post-build-utils.ps1 b/eng/common/post-build/post-build-utils.ps1 index 551ae113f89fc..534f6988d5b7f 100644 --- a/eng/common/post-build/post-build-utils.ps1 +++ b/eng/common/post-build/post-build-utils.ps1 @@ -1,16 +1,17 @@ # Most of the functions in this file require the variables `MaestroApiEndPoint`, # `MaestroApiVersion` and `MaestroApiAccessToken` to be globally available. -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true +$disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 -function Create-MaestroApiRequestHeaders([string]$ContentType = "application/json") { +function Create-MaestroApiRequestHeaders([string]$ContentType = 'application/json') { Validate-MaestroVars $headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' @@ -50,40 +51,40 @@ function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { return $result } -function Trigger-Subscription([string]$SubscriptionId) { +function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { Validate-MaestroVars $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken - $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" - Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" + Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null } -function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { +function Trigger-Subscription([string]$SubscriptionId) { Validate-MaestroVars $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken - $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" - Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null } function Validate-MaestroVars { try { - Get-Variable MaestroApiEndPoint -Scope Global | Out-Null - Get-Variable MaestroApiVersion -Scope Global | Out-Null - Get-Variable MaestroApiAccessToken -Scope Global | Out-Null + Get-Variable MaestroApiEndPoint | Out-Null + Get-Variable MaestroApiVersion | Out-Null + Get-Variable MaestroApiAccessToken | Out-Null - if (!($MaestroApiEndPoint -Match "^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$")) { - Write-PipelineTaskError "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" + if (!($MaestroApiEndPoint -Match '^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" ExitWithExitCode 1 } - if (!($MaestroApiVersion -Match "^[0-9]{4}-[0-9]{2}-[0-9]{2}$")) { - Write-PipelineTaskError "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" + if (!($MaestroApiVersion -Match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" ExitWithExitCode 1 } } catch { - Write-PipelineTaskError "Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script." + Write-PipelineTelemetryError -Category 'MaestroVars' -Message 'Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script.' Write-Host $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1 new file mode 100644 index 0000000000000..2427ca6b6aec7 --- /dev/null +++ b/eng/common/post-build/publish-using-darc.ps1 @@ -0,0 +1,80 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, + [Parameter(Mandatory=$true)][string] $AzdoToken, + [Parameter(Mandatory=$true)][string] $MaestroToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, + [Parameter(Mandatory=$false)][string] $EnableSourceLinkValidation, + [Parameter(Mandatory=$false)][string] $EnableSigningValidation, + [Parameter(Mandatory=$false)][string] $EnableNugetValidation, + [Parameter(Mandatory=$false)][string] $PublishInstallersAndChecksums, + [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, + [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters, + [Parameter(Mandatory=$false)][string] $SigningValidationAdditionalParameters +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $darc = Get-Darc + + $optionalParams = [System.Collections.ArrayList]::new() + + if ("" -ne $ArtifactsPublishingAdditionalParameters) { + $optionalParams.Add("--artifact-publishing-parameters") | Out-Null + $optionalParams.Add($ArtifactsPublishingAdditionalParameters) | Out-Null + } + + if ("" -ne $SymbolPublishingAdditionalParameters) { + $optionalParams.Add("--symbol-publishing-parameters") | Out-Null + $optionalParams.Add($SymbolPublishingAdditionalParameters) | Out-Null + } + + if ("false" -eq $WaitPublishingFinish) { + $optionalParams.Add("--no-wait") | Out-Null + } + + if ("false" -ne $PublishInstallersAndChecksums) { + $optionalParams.Add("--publish-installers-and-checksums") | Out-Null + } + + if ("true" -eq $EnableNugetValidation) { + $optionalParams.Add("--validate-nuget") | Out-Null + } + + if ("true" -eq $EnableSourceLinkValidation) { + $optionalParams.Add("--validate-sourcelinkchecksums") | Out-Null + } + + if ("true" -eq $EnableSigningValidation) { + $optionalParams.Add("--validate-signingchecksums") | Out-Null + + if ("" -ne $SigningValidationAdditionalParameters) { + $optionalParams.Add("--signing-validation-parameters") | Out-Null + $optionalParams.Add($SigningValidationAdditionalParameters) | Out-Null + } + } + + & $darc add-build-to-channel ` + --id $buildId ` + --publishing-infra-version $PublishingInfraVersion ` + --default-channels ` + --source-branch main ` + --azdev-pat $AzdoToken ` + --bar-uri $MaestroApiEndPoint ` + --password $MaestroToken ` + @optionalParams + + if ($LastExitCode -ne 0) { + Write-Host "Problems using Darc to promote build ${buildId} to default channels. Stopping execution..." + exit 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/setup-maestro-vars.ps1 b/eng/common/post-build/setup-maestro-vars.ps1 deleted file mode 100644 index d7f64dc63cbc9..0000000000000 --- a/eng/common/post-build/setup-maestro-vars.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -param( - [Parameter(Mandatory=$true)][string] $ReleaseConfigsPath # Full path to ReleaseConfigs.txt asset -) - -. $PSScriptRoot\post-build-utils.ps1 - -try { - $Content = Get-Content $ReleaseConfigsPath - - $BarId = $Content | Select -Index 0 - - $Channels = "" - $Content | Select -Index 1 | ForEach-Object { $Channels += "$_ ," } - - $IsStableBuild = $Content | Select -Index 2 - - Write-PipelineSetVariable -Name 'BARBuildId' -Value $BarId - Write-PipelineSetVariable -Name 'InitialChannels' -Value "$Channels" - Write-PipelineSetVariable -Name 'IsStableBuild' -Value $IsStableBuild -} -catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - ExitWithExitCode 1 -} diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 index bbfdacca1307c..85c89861719ad 100644 --- a/eng/common/post-build/sourcelink-validation.ps1 +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -14,7 +14,9 @@ param( $global:RepoFiles = @{} # Maximum number of jobs to run in parallel -$MaxParallelJobs = 6 +$MaxParallelJobs = 16 + +$MaxRetries = 5 # Wait time between check for system load $SecondsBetweenLoadChecks = 10 @@ -29,14 +31,17 @@ $ValidatePackage = { # Ensure input file exist if (!(Test-Path $PackagePath)) { Write-Host "Input file does not exist: $PackagePath" - return 1 + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } } # Extensions for which we'll look for SourceLink information # For now we'll only care about Portable & Embedded PDBs - $RelevantExtensions = @(".dll", ".exe", ".pdb") + $RelevantExtensions = @('.dll', '.exe', '.pdb') - Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " + Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId @@ -58,8 +63,11 @@ $ValidatePackage = { $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName # We ignore resource DLLs - if ($FileName.EndsWith(".resources.dll")) { - return + if ($FileName.EndsWith('.resources.dll')) { + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } } [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) @@ -91,36 +99,49 @@ $ValidatePackage = { $Status = 200 $Cache = $using:RepoFiles - if ( !($Cache.ContainsKey($FilePath)) ) { - try { - $Uri = $Link -as [System.URI] - - # Only GitHub links are valid - if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match "github" -or $Uri.Host -match "githubusercontent")) { - $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + $totalRetries = 0 + + while ($totalRetries -lt $using:MaxRetries) { + if ( !($Cache.ContainsKey($FilePath)) ) { + try { + $Uri = $Link -as [System.URI] + + # Only GitHub links are valid + if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + } + else { + # If it's not a github link, we want to break out of the loop and not retry. + $Status = 0 + $totalRetries = $using:MaxRetries + } } - else { + catch { + Write-Host $_ $Status = 0 } } - catch { - write-host $_ - $Status = 0 - } - } - if ($Status -ne 200) { - if ($NumFailedLinks -eq 0) { - if ($FailedFiles.Value -eq 0) { - Write-Host + if ($Status -ne 200) { + $totalRetries++ + + if ($totalRetries -ge $using:MaxRetries) { + if ($NumFailedLinks -eq 0) { + if ($FailedFiles.Value -eq 0) { + Write-Host + } + + Write-Host "`tFile $RealPath has broken links:" + } + + Write-Host "`t`tFailed to retrieve $Link" + + $NumFailedLinks++ } - - Write-Host "`tFile $RealPath has broken links:" } - - Write-Host "`t`tFailed to retrieve $Link" - - $NumFailedLinks++ + else { + break + } } } } @@ -136,26 +157,45 @@ $ValidatePackage = { } } catch { - + Write-Host $_ } finally { $zip.Dispose() } if ($FailedFiles -eq 0) { - Write-Host "Passed." - return 0 + Write-Host 'Passed.' + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } } else { - Write-Host "$PackagePath has broken SourceLink links." - return 1 + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$ValidationFailures, + [switch]$logErrors) { + if ($result -ne '0') { + if ($logErrors) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$packagePath has broken SourceLink links." + } + $ValidationFailures.Value++ } } function ValidateSourceLinkLinks { - if ($GHRepoName -ne "" -and !($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { - if (!($GHRepoName -Match "^[^\s-]+-[^\s]+$")) { - Write-PipelineTaskError "GHRepoName should be in the format / or -. '$GHRepoName'" + if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { + if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" ExitWithExitCode 1 } else { @@ -163,14 +203,14 @@ function ValidateSourceLinkLinks { } } - if ($GHCommit -ne "" -and !($GHCommit -Match "^[0-9a-fA-F]{40}$")) { - Write-PipelineTaskError "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" + if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" ExitWithExitCode 1 } - if ($GHRepoName -ne "" -and $GHCommit -ne "") { - $RepoTreeURL = -Join("http://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") - $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") + if ($GHRepoName -ne '' -and $GHCommit -ne '') { + $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') + $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') try { # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash @@ -188,17 +228,20 @@ function ValidateSourceLinkLinks { Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." } } - elseif ($GHRepoName -ne "" -or $GHCommit -ne "") { - Write-Host "For using the http caching mechanism both GHRepoName and GHCommit should be informed." + elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { + Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' } if (Test-Path $ExtractPath) { Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue } + $ValidationFailures = 0 + # Process each NuGet package in parallel Get-ChildItem "$InputPath\*.symbols.nupkg" | ForEach-Object { + Write-Host "Starting $($_.FullName)" Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null $NumJobs = @(Get-Job -State 'Running').Count @@ -209,26 +252,25 @@ function ValidateSourceLinkLinks { } foreach ($Job in @(Get-Job -State 'Completed')) { - Receive-Job -Id $Job.Id + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) -LogErrors Remove-Job -Id $Job.Id } } - $ValidationFailures = 0 foreach ($Job in @(Get-Job)) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job - if ($jobResult -ne "0") { - $ValidationFailures++ - } + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) + Remove-Job -Id $Job.Id } if ($ValidationFailures -gt 0) { - Write-PipelineTaskError " $ValidationFailures package(s) failed validation." + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." ExitWithExitCode 1 } } function InstallSourcelinkCli { - $sourcelinkCliPackageName = "sourcelink" + $sourcelinkCliPackageName = 'sourcelink' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -239,7 +281,7 @@ function InstallSourcelinkCli { } else { Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." - Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global } } @@ -247,11 +289,15 @@ function InstallSourcelinkCli { try { InstallSourcelinkCli + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + ValidateSourceLinkLinks } catch { - Write-Host $_ Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/symbols-validation.ps1 b/eng/common/post-build/symbols-validation.ps1 index 096ac321d1298..a5af041ba775c 100644 --- a/eng/common/post-build/symbols-validation.ps1 +++ b/eng/common/post-build/symbols-validation.ps1 @@ -1,127 +1,215 @@ param( - [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored - [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation - [Parameter(Mandatory=$true)][string] $DotnetSymbolVersion # Version of dotnet symbol to use + [Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored + [Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use + [Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs + [Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error + [Parameter(Mandatory = $false)][switch] $Clean # Clean extracted symbols directory after checking symbols ) -. $PSScriptRoot\post-build-utils.ps1 +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 -Add-Type -AssemblyName System.IO.Compression.FileSystem +# Max number of retries +$MaxRetry = 5 -function FirstMatchingSymbolDescriptionOrDefault { - param( - [string] $FullPath, # Full path to the module that has to be checked - [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols - [string] $SymbolsPath - ) - - $FileName = [System.IO.Path]::GetFileName($FullPath) - $Extension = [System.IO.Path]::GetExtension($FullPath) - - # Those below are potential symbol files that the `dotnet symbol` might - # return. Which one will be returned depend on the type of file we are - # checking and which type of file was uploaded. - - # The file itself is returned - $SymbolPath = $SymbolsPath + "\" + $FileName +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 - # PDB file for the module - $PdbPath = $SymbolPath.Replace($Extension, ".pdb") +# Set error codes +Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1 +Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2 - # PDB file for R2R module (created by crossgen) - $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") - - # DBG file for a .so library - $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") - - # DWARF file for a .dylib - $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") - - $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" - $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" - - & $dotnetSymbolExe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath | Out-Null - - if (Test-Path $PdbPath) { - return "PDB" - } - elseif (Test-Path $NGenPdb) { - return "NGen PDB" - } - elseif (Test-Path $SODbg) { - return "DBG for SO" - } - elseif (Test-Path $DylibDwarf) { - return "Dwarf for Dylib" - } - elseif (Test-Path $SymbolPath) { - return "Module" - } - else { - return $null - } +$WindowsPdbVerificationParam = "" +if ($CheckForWindowsPdbs) { + $WindowsPdbVerificationParam = "--windows-pdbs" } -function CountMissingSymbols { +$CountMissingSymbols = { param( - [string] $PackagePath # Path to a NuGet package + [string] $PackagePath, # Path to a NuGet package + [string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs ) + . $using:PSScriptRoot\..\tools.ps1 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + Write-Host "Validating $PackagePath " + # Ensure input file exist if (!(Test-Path $PackagePath)) { Write-PipelineTaskError "Input file does not exist: $PackagePath" - ExitWithExitCode 1 + return [pscustomobject]@{ + result = $using:ERROR_FILEDOESNOTEXIST + packagePath = $PackagePath + } } # Extensions for which we'll look for symbols - $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") + $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') # How many files are missing symbol information $MissingSymbols = 0 $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $PackageGuid = New-Guid - $ExtractPath = Join-Path -Path $ExtractPath -ChildPath $PackageGuid - $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath "Symbols" + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageGuid + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' - [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + try { + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + } + catch { + Write-Host "Something went wrong extracting $PackagePath" + Write-Host $_ + return [pscustomobject]@{ + result = $using:ERROR_BADEXTRACT + packagePath = $PackagePath + } + } Get-ChildItem -Recurse $ExtractPath | - Where-Object {$RelevantExtensions -contains $_.Extension} | - ForEach-Object { - if ($_.FullName -Match "\\ref\\") { - Write-Host "`t Ignoring reference assembly file" $_.FullName - return + Where-Object { $RelevantExtensions -contains $_.Extension } | + ForEach-Object { + $FileName = $_.FullName + if ($FileName -Match '\\ref\\') { + Write-Host "`t Ignoring reference assembly file " $FileName + return + } + + $FirstMatchingSymbolDescriptionOrDefault = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + [string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs. + [string] $SymbolsPath + ) + + $FileName = [System.IO.Path]::GetFileName($FullPath) + $Extension = [System.IO.Path]::GetExtension($FullPath) + + # Those below are potential symbol files that the `dotnet symbol` might + # return. Which one will be returned depend on the type of file we are + # checking and which type of file was uploaded. + + # The file itself is returned + $SymbolPath = $SymbolsPath + '\' + $FileName + + # PDB file for the module + $PdbPath = $SymbolPath.Replace($Extension, '.pdb') + + # PDB file for R2R module (created by crossgen) + $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') + + # DBG file for a .so library + $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') + + # DWARF file for a .dylib + $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') + + $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" + $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" + + $totalRetries = 0 + + while ($totalRetries -lt $using:MaxRetry) { + + # Save the output and get diagnostic output + $output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String + + if (Test-Path $PdbPath) { + return 'PDB' + } + elseif (Test-Path $NGenPdb) { + return 'NGen PDB' + } + elseif (Test-Path $SODbg) { + return 'DBG for SO' + } + elseif (Test-Path $DylibDwarf) { + return 'Dwarf for Dylib' + } + elseif (Test-Path $SymbolPath) { + return 'Module' + } + else + { + $totalRetries++ + } } + + return $null + } + + $FileGuid = New-Guid + $ExpandedSymbolsPath = Join-Path -Path $SymbolsPath -ChildPath $FileGuid - $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" $SymbolsPath - $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" $SymbolsPath + $SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--microsoft-symbol-server' ` + -SymbolsPath "$ExpandedSymbolsPath-msdl" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + $SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--internal-server' ` + -SymbolsPath "$ExpandedSymbolsPath-symweb" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam - Write-Host -NoNewLine "`t Checking file" $_.FullName "... " + Write-Host -NoNewLine "`t Checking file " $FileName "... " - if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { - Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" + if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { + Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" + } + else { + $MissingSymbols++ + + if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { + Write-Host 'No symbols found on MSDL or SymWeb!' } else { - $MissingSymbols++ - - if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { - Write-Host "No symbols found on MSDL or SymWeb!" + if ($SymbolsOnMSDL -eq $null) { + Write-Host 'No symbols found on MSDL!' } else { - if ($SymbolsOnMSDL -eq $null) { - Write-Host "No symbols found on MSDL!" - } - else { - Write-Host "No symbols found on SymWeb!" - } + Write-Host 'No symbols found on SymWeb!' } } } + } + + if ($using:Clean) { + Remove-Item $ExtractPath -Recurse -Force + } Pop-Location - return $MissingSymbols + return [pscustomobject]@{ + result = $MissingSymbols + packagePath = $PackagePath + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$DupedSymbols, + [ref]$TotalFailures) { + if ($result -eq $ERROR_BADEXTRACT) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files" + $DupedSymbols.Value++ + } + elseif ($result -eq $ERROR_FILEDOESNOTEXIST) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath does not exist" + $TotalFailures.Value++ + } + elseif ($result -gt '0') { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $result modules in the package $packagePath" + $TotalFailures.Value++ + } + else { + Write-Host "All symbols verified for package $packagePath" + } } function CheckSymbolsAvailable { @@ -129,38 +217,72 @@ function CheckSymbolsAvailable { Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue } + $TotalPackages = 0 + $TotalFailures = 0 + $DupedSymbols = 0 + Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $FileName = $_.Name - + $FullName = $_.FullName + # These packages from Arcade-Services include some native libraries that # our current symbol uploader can't handle. Below is a workaround until # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. - if ($FileName -Match "Microsoft\.DotNet\.Darc\.") { + if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - elseif ($FileName -Match "Microsoft\.DotNet\.Maestro\.Tasks\.") { + elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - - Write-Host "Validating $FileName " - $Status = CountMissingSymbols "$InputPath\$FileName" - - if ($Status -ne 0) { - Write-PipelineTaskError "Missing symbols for $Status modules in the package $FileName" - ExitWithExitCode $exitCode + + $TotalPackages++ + + Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null + + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count } + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + Remove-Job -Id $Job.Id + } Write-Host } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + } + + if ($TotalFailures -gt 0 -or $DupedSymbols -gt 0) { + if ($TotalFailures -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures/$TotalPackages packages" + } + + if ($DupedSymbols -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$DupedSymbols/$TotalPackages packages had duplicated symbol files and could not be extracted" + } + + ExitWithExitCode 1 + } + else { + Write-Host "All symbols validated!" + } } function InstallDotnetSymbol { - $dotnetSymbolPackageName = "dotnet-symbol" + $dotnetSymbolPackageName = 'dotnet-symbol' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -171,19 +293,24 @@ function InstallDotnetSymbol { } else { Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." - Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global } } try { + . $PSScriptRoot\post-build-utils.ps1 + InstallDotnetSymbol + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + CheckSymbolsAvailable } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1 index 926d5b455131b..55dea518ac585 100644 --- a/eng/common/post-build/trigger-subscriptions.ps1 +++ b/eng/common/post-build/trigger-subscriptions.ps1 @@ -2,56 +2,63 @@ param( [Parameter(Mandatory=$true)][string] $SourceRepo, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' ) -. $PSScriptRoot\post-build-utils.ps1 +try { + . $PSScriptRoot\post-build-utils.ps1 -# Get all the $SourceRepo subscriptions -$normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') -$subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId + # Get all the $SourceRepo subscriptions + $normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') + $subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId -if (!$subscriptions) { - Write-Host "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" - ExitWithExitCode 0 -} + if (!$subscriptions) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" + ExitWithExitCode 0 + } -$subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] -$failedTriggeredSubscription = $false + $subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] + $failedTriggeredSubscription = $false -# Get all enabled subscriptions that need dependency flow on 'everyBuild' -foreach ($subscription in $subscriptions) { - if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { - Write-Host "Should trigger this subscription: $subscription.id" - [void]$subscriptionsToTrigger.Add($subscription.id) + # Get all enabled subscriptions that need dependency flow on 'everyBuild' + foreach ($subscription in $subscriptions) { + if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { + Write-Host "Should trigger this subscription: ${$subscription.id}" + [void]$subscriptionsToTrigger.Add($subscription.id) + } } -} -foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { - try { - Write-Host "Triggering subscription '$subscriptionToTrigger'." - - Trigger-Subscription -SubscriptionId $subscriptionToTrigger - - Write-Host "done." - } - catch - { - Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" - Write-Host $_ - Write-Host $_.ScriptStackTrace - $failedTriggeredSubscription = $true + foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { + try { + Write-Host "Triggering subscription '$subscriptionToTrigger'." + + Trigger-Subscription -SubscriptionId $subscriptionToTrigger + + Write-Host 'done.' + } + catch + { + Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" + Write-Host $_ + Write-Host $_.ScriptStackTrace + $failedTriggeredSubscription = $true + } } -} -if ($subscriptionsToTrigger.Count -eq 0) { - Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." + if ($subscriptionsToTrigger.Count -eq 0) { + Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." + } + elseif ($failedTriggeredSubscription) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message 'At least one subscription failed to be triggered...' + ExitWithExitCode 1 + } + else { + Write-Host 'All subscriptions were triggered successfully!' + } } -elseif ($failedTriggeredSubscription) { - Write-Host "At least one subscription failed to be triggered..." +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message $_ ExitWithExitCode 1 } -else { - Write-Host "All subscriptions were triggered successfully!" -} diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index d0eec5163efe2..65f1d75f3d322 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -1,8 +1,8 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $configuration = "Debug", + [string] $configuration = 'Debug', [string] $task, - [string] $verbosity = "minimal", + [string] $verbosity = 'minimal', [string] $msbuildEngine = $null, [switch] $restore, [switch] $prepareMachine, @@ -32,7 +32,7 @@ function Print-Usage() { } function Build([string]$target) { - $logSuffix = if ($target -eq "Execute") { "" } else { ".$target" } + $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } $log = Join-Path $LogDir "$task$logSuffix.binlog" $outputPath = Join-Path $ToolsetDir "$task\\" @@ -42,37 +42,55 @@ function Build([string]$target) { /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:BaseIntermediateOutputPath=$outputPath ` + /v:$verbosity ` @properties } try { - if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($task -eq "") { - Write-Host "Missing required parameter '-task '" -ForegroundColor Red + Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" Print-Usage ExitWithExitCode 1 } + if( $msbuildEngine -eq "vs") { + # Ensure desktop MSBuild is available for sdk tasks. + if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { + $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty + } + if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "16.8.0-preview3" -MemberType NoteProperty + } + if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { + $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true + } + if ($xcopyMSBuildToolsFolder -eq $null) { + throw 'Unable to get xcopy downloadable version of msbuild' + } + + $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" + } + $taskProject = GetSdkTaskProject $task if (!(Test-Path $taskProject)) { - Write-Host "Unknown task: $task" -ForegroundColor Red + Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" ExitWithExitCode 1 } if ($restore) { - Build "Restore" + Build 'Restore' } - Build "Execute" + Build 'Execute' } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 index 01799d63ff365..81b729f74a4d4 100644 --- a/eng/common/sdl/execute-all-sdl-tools.ps1 +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -1,100 +1,116 @@ Param( - [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) - [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) - [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified - [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) - [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master - [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located - [string] $ArtifactsDirectory = (Join-Path $env:BUILD_SOURCESDIRECTORY ("artifacts")), # Required: the directory where build artifacts are located - [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault - [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code - [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts - [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. - [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. - [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) - [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed - [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. - [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. - [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. - [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. - [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. - [string] $GuardianLoggerLevel="Standard", # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error - [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") - [string[]] $PoliCheckAdditionalRunConfigParams # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code + [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run ) -$ErrorActionPreference = "Stop" -Set-StrictMode -Version 2.0 -$LASTEXITCODE = 0 +try { + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $LASTEXITCODE = 0 -#Replace repo names to the format of org/repo -if (!($Repository.contains('/'))) { - $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; -} -else{ - $RepoName = $Repository; -} + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 -if ($GuardianPackageName) { - $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path "tools" "guardian.cmd")) -} else { - $guardianCliLocation = $GuardianCliLocation -} + #Replace repo names to the format of org/repo + if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + } + else{ + $RepoName = $Repository; + } -$workingDirectory = (Split-Path $SourceDirectory -Parent) -$ValidPath = Test-Path $guardianCliLocation + if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) + } else { + $guardianCliLocation = $GuardianCliLocation + } -if ($ValidPath -eq $False) -{ - Write-Host "Invalid Guardian CLI Location." - exit 1 -} + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' + ExitWithExitCode 1 + } -& $(Join-Path $PSScriptRoot "init-sdl.ps1") -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel -$gdnFolder = Join-Path $workingDirectory ".gdn" + & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel + $gdnFolder = Join-Path $workingDirectory '.gdn' -if ($TsaOnboard) { - if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { - Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" - & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian tsa-onboard failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE + if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' + ExitWithExitCode 1 } - } else { - Write-Host "Could not onboard to TSA -- not all required values ($$TsaCodebaseName, $$TsaNotificationEmail, $$TsaCodebaseAdmin, $$TsaBugAreaPath) were specified." - exit 1 } -} -if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { - & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams -} -if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { - & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams -} - -if ($UpdateBaseline) { - & (Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $RepoName -BranchName $BranchName -GdnFolder $GdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Update baseline" -} + if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams + } + if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams + } -if ($TsaPublish) { - if ($TsaBranchName -and $BuildNumber) { - if (-not $TsaRepositoryName) { - $TsaRepositoryName = "$($Repository)-$($BranchName)" + if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' + ExitWithExitCode 1 } - Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" - & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian tsa-publish failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE - } - } else { - Write-Host "Could not publish to TSA -- not all required values ($$TsaBranchName, $$BuildNumber) were specified." - exit 1 } + + if ($BreakOnFailure) { + Write-Host "Failing the build in case of breaking results..." + & $guardianCliLocation break + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + exit 1 } diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 index 6e6825013bf57..7f28d9c59ec69 100644 --- a/eng/common/sdl/extract-artifact-packages.ps1 +++ b/eng/common/sdl/extract-artifact-packages.ps1 @@ -3,54 +3,12 @@ param( [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 -# `tools.ps1` checks $ci to perform some actions. Since the post-build -# scripts don't necessarily execute in the same agent that run the -# build.ps1/sh script this variable isn't automatically set. -$ci = $true -. $PSScriptRoot\..\tools.ps1 +$disableConfigureToolsetImport = $true -$ExtractPackage = { - param( - [string] $PackagePath # Full path to a NuGet package - ) - - if (!(Test-Path $PackagePath)) { - Write-PipelineTaskError "Input file does not exist: $PackagePath" - ExitWithExitCode 1 - } - - $RelevantExtensions = @(".dll", ".exe", ".pdb") - Write-Host -NoNewLine "Extracting" ([System.IO.Path]::GetFileName($PackagePath)) "... " - - $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) - $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId - - Add-Type -AssemblyName System.IO.Compression.FileSystem - - [System.IO.Directory]::CreateDirectory($ExtractPath); - - try { - $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) - - $zip.Entries | - Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | - ForEach-Object { - $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name - - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) - } - } - catch { - - } - finally { - $zip.Dispose() - } - } - function ExtractArtifacts { +function ExtractArtifacts { if (!(Test-Path $InputPath)) { Write-Host "Input Path does not exist: $InputPath" ExitWithExitCode 0 @@ -67,11 +25,56 @@ $ExtractPackage = { } try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @('.dll', '.exe', '.pdb') + Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + } + } + catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 + } + finally { + $zip.Dispose() + } + } Measure-Command { ExtractArtifacts } } catch { Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 index d34d252875461..1fe9271193cc6 100644 --- a/eng/common/sdl/init-sdl.ps1 +++ b/eng/common/sdl/init-sdl.ps1 @@ -1,14 +1,15 @@ Param( [string] $GuardianCliLocation, [string] $Repository, - [string] $BranchName="master", + [string] $BranchName='master', [string] $WorkingDirectory, [string] $AzureDevOpsAccessToken, - [string] $GuardianLoggerLevel="Standard" + [string] $GuardianLoggerLevel='Standard' ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 # `tools.ps1` checks $ci to perform some actions. Since the SDL @@ -23,7 +24,7 @@ $ProgressPreference = 'SilentlyContinue' # Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file $encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) $escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") -$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0-preview.1" +$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0" $zipFile = "$WorkingDirectory/gdn.zip" Add-Type -AssemblyName System.IO.Compression.FileSystem @@ -31,23 +32,24 @@ $gdnFolder = (Join-Path $WorkingDirectory '.gdn') try { # if the folder does not exist, we'll do a guardian init and push it to the remote repository - Write-Host "Initializing Guardian..." + Write-Host 'Initializing Guardian...' Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel if ($LASTEXITCODE -ne 0) { - Write-Error "Guardian init failed with exit code $LASTEXITCODE." + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE } # We create the mainbaseline so it can be edited later Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline if ($LASTEXITCODE -ne 0) { - Write-Error "Guardian baseline failed with exit code $LASTEXITCODE." + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE } - & $(Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $Repository -BranchName $BranchName -GdnFolder $gdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Initialize gdn folder" ExitWithExitCode 0 } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 -} \ No newline at end of file +} diff --git a/eng/common/sdl/push-gdn.ps1 b/eng/common/sdl/push-gdn.ps1 deleted file mode 100644 index 79c707d6d8a03..0000000000000 --- a/eng/common/sdl/push-gdn.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -Param( - [string] $Repository, - [string] $BranchName="master", - [string] $GdnFolder, - [string] $AzureDevOpsAccessToken, - [string] $PushReason -) - -$ErrorActionPreference = "Stop" -Set-StrictMode -Version 2.0 -$LASTEXITCODE = 0 - -# We create the temp directory where we'll store the sdl-config repository -$sdlDir = Join-Path $env:TEMP "sdl" -if (Test-Path $sdlDir) { - Remove-Item -Force -Recurse $sdlDir -} - -Write-Host "git clone https://dnceng:`$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir" -git clone https://dnceng:$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir -if ($LASTEXITCODE -ne 0) { - Write-Error "Git clone failed with exit code $LASTEXITCODE." -} -# We copy the .gdn folder from our local run into the git repository so it can be committed -$sdlRepositoryFolder = Join-Path (Join-Path (Join-Path $sdlDir $Repository) $BranchName) ".gdn" -if (Get-Command Robocopy) { - Robocopy /S $GdnFolder $sdlRepositoryFolder -} else { - rsync -r $GdnFolder $sdlRepositoryFolder -} -# cd to the sdl-config directory so we can run git there -Push-Location $sdlDir -# git add . --> git commit --> git push -Write-Host "git add ." -git add . -if ($LASTEXITCODE -ne 0) { - Write-Error "Git add failed with exit code $LASTEXITCODE." -} -Write-Host "git -c user.email=`"dn-bot@microsoft.com`" -c user.name=`"Dotnet Bot`" commit -m `"$PushReason for $Repository/$BranchName`"" -git -c user.email="dn-bot@microsoft.com" -c user.name="Dotnet Bot" commit -m "$PushReason for $Repository/$BranchName" -if ($LASTEXITCODE -ne 0) { - Write-Error "Git commit failed with exit code $LASTEXITCODE." -} -Write-Host "git push" -git push -if ($LASTEXITCODE -ne 0) { - Write-Error "Git push failed with exit code $LASTEXITCODE." -} - -# Return to the original directory -Pop-Location \ No newline at end of file diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 index 9bc25314ae21d..fe95ab35aa5d1 100644 --- a/eng/common/sdl/run-sdl.ps1 +++ b/eng/common/sdl/run-sdl.ps1 @@ -5,55 +5,69 @@ Param( [string] $GdnFolder, [string[]] $ToolsList, [string] $UpdateBaseline, - [string] $GuardianLoggerLevel="Standard", + [string] $GuardianLoggerLevel='Standard', [string[]] $CrScanAdditionalRunConfigParams, [string[]] $PoliCheckAdditionalRunConfigParams ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 -# We store config files in the r directory of .gdn -Write-Host $ToolsList -$gdnConfigPath = Join-Path $GdnFolder "r" -$ValidPath = Test-Path $GuardianCliLocation +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 -if ($ValidPath -eq $False) -{ - Write-Host "Invalid Guardian CLI Location." - exit 1 -} + # We store config files in the r directory of .gdn + Write-Host $ToolsList + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation -$configParam = @("--config") - -foreach ($tool in $ToolsList) { - $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" - Write-Host $tool - # We have to manually configure tools that run on source to look at the source directory only - if ($tool -eq "credscan") { - Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" - & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE - } + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 } - if ($tool -eq "policheck") { - Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" - & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE + + $configParam = @('--config') + + foreach ($tool in $ToolsList) { + $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" + Write-Host $tool + # We have to manually configure tools that run on source to look at the source directory only + if ($tool -eq 'credscan') { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } } + if ($tool -eq 'policheck') { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } + + $configParam+=$gdnConfigFile } - $configParam+=$gdnConfigFile + Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" + & $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } } - -Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" -& $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam -if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 } diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml index 34ea015684c77..4a32181fd8f93 100644 --- a/eng/common/templates/job/execute-sdl.yml +++ b/eng/common/templates/job/execute-sdl.yml @@ -1,10 +1,12 @@ parameters: + enable: 'false' # Whether the SDL validation job should execute or not overrideParameters: '' # Optional: to override values for parameters. additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named # 'continueOnError', the parameter value is not correctly picked up. # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter sdlContinueOnError: false # optional: determines whether to continue the build if the step errors; + downloadArtifacts: true # optional: determines if the artifacts should be dowloaded dependsOn: '' # Optional: dependencies of the job artifactNames: '' # Optional: patterns supplied to DownloadBuildArtifacts # Usage: @@ -16,37 +18,55 @@ jobs: - job: Run_SDL dependsOn: ${{ parameters.dependsOn }} displayName: Run SDL tool + condition: eq( ${{ parameters.enable }}, 'true') variables: - group: DotNet-VSTS-Bot + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: name: Hosted VS2017 steps: - checkout: self clean: true - - ${{ if ne(parameters.artifactNames, '') }}: - - ${{ each artifactName in parameters.artifactNames }}: + - ${{ if ne(parameters.downloadArtifacts, 'false')}}: + - ${{ if ne(parameters.artifactNames, '') }}: + - ${{ each artifactName in parameters.artifactNames }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: ${{ artifactName }} + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.artifactNames, '') }}: - task: DownloadBuildArtifacts@0 displayName: Download Build Artifacts inputs: - buildType: current - artifactName: ${{ artifactName }} + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: specific files + itemPattern: "**" downloadPath: $(Build.ArtifactStagingDirectory)\artifacts - - ${{ if eq(parameters.artifactNames, '') }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Build Artifacts - inputs: - buildType: current - downloadType: specific files - itemPattern: "**" - downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true - powershell: eng/common/sdl/extract-artifact-packages.ps1 - -InputPath $(Build.SourcesDirectory)\artifacts\BlobArtifacts - -ExtractPath $(Build.SourcesDirectory)\artifacts\BlobArtifacts + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts displayName: Extract Blob Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - powershell: eng/common/sdl/extract-artifact-packages.ps1 - -InputPath $(Build.SourcesDirectory)\artifacts\PackageArtifacts - -ExtractPath $(Build.SourcesDirectory)\artifacts\PackageArtifacts + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts displayName: Extract Package Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - task: NuGetToolInstaller@1 diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index ffda80a197b20..8669679348024 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -1,67 +1,36 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + parameters: # Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job cancelTimeoutInMinutes: '' - condition: '' - - continueOnError: false - container: '' - + continueOnError: false dependsOn: '' - displayName: '' - - steps: [] - pool: '' - + steps: [] strategy: '' - timeoutInMinutes: '' - variables: [] - workspace: '' - # Job base template specific parameters - # Optional: Enable installing Microbuild plugin - # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix - # _TeamName - the name of your team - # _SignType - 'test' or 'real' +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' enableMicrobuild: false - - # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - - # Optional: Enable publishing to the build asset registry enablePublishBuildAssets: false - - # Optional: Prevent gather/push manifest from executing when using publishing pipelines - enablePublishUsingPipelines: false - - # Optional: Include PublishTestResults task enablePublishTestResults: false - - # Optional: enable sending telemetry - enableTelemetry: false - - # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') - helixRepo: '' - - # Optional: define the helix type for telemetry (example: 'build/product/') - helixType: '' - - # Required: name of the job + enablePublishUsingPipelines: false + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' name: '' - - # Optional: should run as a public build even in the internal project - # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + preSteps: [] runAsPublic: false -# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, -# and some (Microbuild) should only be applied to non-PR cases for internal builds. - jobs: - job: ${{ parameters.name }} @@ -93,9 +62,12 @@ jobs: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} variables: - - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' - ${{ each variable in parameters.variables }}: # handle name-value variable syntax # example: @@ -125,21 +97,12 @@ jobs: workspace: ${{ parameters.workspace }} steps: - - ${{ if eq(parameters.enableTelemetry, 'true') }}: - # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions - - task: sendStartTelemetry@0 - displayName: 'Send Helix Start Telemetry' - inputs: - helixRepo: ${{ parameters.helixRepo }} - ${{ if ne(parameters.helixType, '') }}: - helixType: ${{ parameters.helixType }} - buildConfig: $(_BuildConfig) - runAsPublic: ${{ parameters.runAsPublic }} - continueOnError: ${{ parameters.continueOnError }} - condition: always() + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} - - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - task: MicroBuildSigningPlugin@2 displayName: Install MicroBuild plugin inputs: @@ -151,12 +114,28 @@ jobs: continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: NuGetAuthenticate@0 + - ${{ if or(eq(parameters.artifacts.download, 'true'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + - ${{ each step in parameters.steps }}: - ${{ step }} + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + continueOnError: true + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: MicroBuildCleanup@1 @@ -166,30 +145,83 @@ jobs: env: TeamName: $(_TeamName) - - ${{ if eq(parameters.enableTelemetry, 'true') }}: - # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions - - task: sendEndTelemetry@0 - displayName: 'Send Helix End Telemetry' - continueOnError: ${{ parameters.continueOnError }} - condition: always() - - - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if or(eq(parameters.artifacts.publish.artifacts, 'true'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if or(eq(parameters.artifacts.publish.logs, 'true'), ne(parameters.artifacts.publish.logs, '')) }}: + - publish: artifacts/log + artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: Publish logs + continueOnError: true + condition: always() + - ${{ if or(eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - ${{ if and(ne(parameters.enablePublishUsingPipelines, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.ArtifactStagingDirectory)/AssetManifests' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: - task: PublishBuildArtifacts@1 displayName: Publish Logs inputs: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' PublishLocation: Container - ArtifactName: $(Agent.Os)_$(Agent.JobName) + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} continueOnError: true condition: always() - - ${{ if eq(parameters.enablePublishTestResults, 'true') }}: + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: - task: PublishTestResults@2 - displayName: Publish Test Results + displayName: Publish XUnit Test Results inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true condition: always() diff --git a/eng/common/templates/job/onelocbuild.yml b/eng/common/templates/job/onelocbuild.yml new file mode 100644 index 0000000000000..2acdd5256dd83 --- /dev/null +++ b/eng/common/templates/job/onelocbuild.yml @@ -0,0 +1,85 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: + vmImage: vs2017-win2016 + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + condition: '' + +jobs: +- job: OneLocBuild + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild + + pool: ${{ parameters.pool }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + + + steps: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates/job/performance.yml b/eng/common/templates/job/performance.yml deleted file mode 100644 index f877fd7a89800..0000000000000 --- a/eng/common/templates/job/performance.yml +++ /dev/null @@ -1,95 +0,0 @@ -parameters: - steps: [] # optional -- any additional steps that need to happen before pulling down the performance repo and sending the performance benchmarks to helix (ie building your repo) - variables: [] # optional -- list of additional variables to send to the template - jobName: '' # required -- job name - displayName: '' # optional -- display name for the job. Will use jobName if not passed - pool: '' # required -- name of the Build pool - container: '' # required -- name of the container - osGroup: '' # required -- operating system for the job - extraSetupParameters: '' # optional -- extra arguments to pass to the setup script - frameworks: ['netcoreapp3.0'] # optional -- list of frameworks to run against - continueOnError: 'false' # optional -- determines whether to continue the build if the step errors - dependsOn: '' # optional -- dependencies of the job - timeoutInMinutes: 320 # optional -- timeout for the job - enableTelemetry: false # optional -- enable for telemetry - -jobs: -- template: ../jobs/jobs.yml - parameters: - dependsOn: ${{ parameters.dependsOn }} - enableTelemetry: ${{ parameters.enableTelemetry }} - enablePublishBuildArtifacts: true - continueOnError: ${{ parameters.continueOnError }} - - jobs: - - job: '${{ parameters.jobName }}' - - ${{ if ne(parameters.displayName, '') }}: - displayName: '${{ parameters.displayName }}' - ${{ if eq(parameters.displayName, '') }}: - displayName: '${{ parameters.jobName }}' - - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - - variables: - - - ${{ each variable in parameters.variables }}: - - ${{ if ne(variable.name, '') }}: - - name: ${{ variable.name }} - value: ${{ variable.value }} - - ${{ if ne(variable.group, '') }}: - - group: ${{ variable.group }} - - - IsInternal: '' - - HelixApiAccessToken: '' - - HelixPreCommand: '' - - - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - ${{ if eq( parameters.osGroup, 'Windows_NT') }}: - - HelixPreCommand: 'set "PERFLAB_UPLOAD_TOKEN=$(PerfCommandUploadToken)"' - - IsInternal: -Internal - - ${{ if ne(parameters.osGroup, 'Windows_NT') }}: - - HelixPreCommand: 'export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' - - IsInternal: --internal - - - group: DotNet-HelixApi-Access - - group: dotnet-benchview - - workspace: - clean: all - pool: - ${{ parameters.pool }} - container: ${{ parameters.container }} - strategy: - matrix: - ${{ each framework in parameters.frameworks }}: - ${{ framework }}: - _Framework: ${{ framework }} - steps: - - checkout: self - clean: true - # Run all of the steps to setup repo - - ${{ each step in parameters.steps }}: - - ${{ step }} - - powershell: $(Build.SourcesDirectory)\eng\common\performance\performance-setup.ps1 $(IsInternal) -Framework $(_Framework) ${{ parameters.extraSetupParameters }} - displayName: Performance Setup (Windows) - condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - script: $(Build.SourcesDirectory)/eng/common/performance/performance-setup.sh $(IsInternal) --framework $(_Framework) ${{ parameters.extraSetupParameters }} - displayName: Performance Setup (Unix) - condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - script: $(Python) $(PerformanceDirectory)/scripts/ci_setup.py $(SetupArguments) - displayName: Run ci setup script - # Run perf testing in helix - - template: /eng/common/templates/steps/perf-send-to-helix.yml - parameters: - HelixSource: '$(HelixSourcePrefix)/$(Build.Repository.Name)/$(Build.SourceBranch)' # sources must start with pr/, official/, prodcon/, or agent/ - HelixType: 'test/performance/$(Kind)/$(_Framework)/$(Architecture)' - HelixAccessToken: $(HelixApiAccessToken) - HelixTargetQueues: $(Queue) - HelixPreCommands: $(HelixPreCommand) - Creator: $(Creator) - WorkItemTimeout: 4:00 # 4 hours - WorkItemDirectory: '$(WorkItemDirectory)' # WorkItemDirectory can not be empty, so we send it some docs to keep it happy - CorrelationPayloadDirectory: '$(PayloadDirectory)' # it gets checked out to a folder with shorter path than WorkItemDirectory so we can avoid file name too long exceptions \ No newline at end of file diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index b722975f9c288..3b9e2524ff37c 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -37,6 +37,13 @@ jobs: - name: _BuildConfig value: ${{ parameters.configuration }} - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + # Skip component governance and codesign validation for SDL. These jobs + # create no content. + - name: skipComponentGovernanceDetection + value: true + - name: runCodesignValidationInjection + value: false steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: @@ -45,12 +52,19 @@ jobs: inputs: artifactName: AssetManifests downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: NuGetAuthenticate@0 + - task: PowerShell@2 + displayName: Enable cross-org NuGet feed authentication + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-all-orgs-artifact-feeds-rw) + - task: PowerShell@2 displayName: Publish Build Assets inputs: @@ -61,6 +75,7 @@ jobs: /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} /p:Configuration=$(_BuildConfig) + /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} @@ -81,11 +96,6 @@ jobs: ArtifactName: ReleaseConfigs - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - - task: PublishBuildArtifacts@1 - displayName: Publish Logs to VSTS - inputs: - PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' - PublishLocation: Container - ArtifactName: $(Agent.Os)_PublishBuildAssets - continueOnError: true - condition: always() + - template: /eng/common/templates/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates/job/source-build.yml b/eng/common/templates/job/source-build.yml new file mode 100644 index 0000000000000..5023d36dcb3c5 --- /dev/null +++ b/eng/common/templates/job/source-build.yml @@ -0,0 +1,60 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # Specifies the build script to invoke to perform the build in the repo. The default + # './build.sh' should work for typical Arcade repositories, but this is customizable for + # difficult situations. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + defaultContainerHostPool: + vmImage: ubuntu-20.04 + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + pool: ${{ parameters.defaultContainerHostPool }} + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - template: /eng/common/templates/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml new file mode 100644 index 0000000000000..a649d2b5990c0 --- /dev/null +++ b/eng/common/templates/job/source-index-stage1.yml @@ -0,0 +1,58 @@ +parameters: + runAsPublic: false + sourceIndexPackageVersion: 1.0.1-20210421.1 + sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" + preSteps: [] + binlogPath: artifacts/log/Debug/Build.binlog + pool: + vmImage: vs2017-win2016 + +jobs: +- job: SourceIndexStage1 + variables: + - name: SourceIndexPackageVersion + value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: source-dot-net stage1 variables + + pool: ${{ parameters.pool }} + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET Core sdk 3.1 + inputs: + packageType: sdk + version: 3.1.x + + - task: UseDotNet@2 + displayName: Use .NET Core sdk + inputs: + useGlobalJson: true + + - script: | + dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path .source-index/tools + dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path .source-index/tools + echo ##vso[task.prependpath]$(Build.SourcesDirectory)/.source-index/tools + displayName: Download Tools + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + env: + DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX: 2 + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - script: UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) + displayName: Upload stage1 artifacts to source index + env: + BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) + DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX: 2 diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 6a2f98c036f61..a1f8fce96ca81 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -1,41 +1,29 @@ parameters: - # Optional: 'true' if failures in job.yml job should not fail the job + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md continueOnError: false - # Optional: Enable installing Microbuild plugin - # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix - # _TeamName - the name of your team - # _SignType - 'test' or 'real' - enableMicrobuild: false - # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - # Optional: Enable publishing to the build asset registry - enablePublishBuildAssets: false - # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false - + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates/jobs/source-build.yml for options + sourceBuildParameters: [] + graphFileGeneration: # Optional: Enable generating the graph files at the end of the build enabled: false # Optional: Include toolset dependencies in the generated graph files includeToolset: false - # Optional: Include PublishTestResults task - enablePublishTestResults: false - - # Optional: enable sending telemetry - # if enabled then the 'helixRepo' parameter should also be specified - enableTelemetry: false - # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job jobs: [] - # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') - helixRepo: '' - # Optional: Override automatically derived dependsOn value for "publish build assets" job publishBuildAssetsDependsOn: '' @@ -43,6 +31,9 @@ parameters: # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false + enableSourceIndex: false + sourceIndexParams: {} + # Internal resources (telemetry, microbuild) can only be accessed from non-public projects, # and some (Microbuild) should only be applied to non-PR cases for internal builds. @@ -62,29 +53,47 @@ jobs: name: ${{ job.job }} -- ${{ if and(eq(parameters.enablePublishBuildAssets, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - template: ../job/publish-build-assets.yml +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates/jobs/source-build.yml parameters: - continueOnError: ${{ parameters.continueOnError }} - dependsOn: - - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: - - ${{ each job in parameters.publishBuildAssetsDependsOn }}: - - ${{ job.job }} - - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: - - ${{ each job in parameters.jobs }}: - - ${{ job.job }} - pool: - vmImage: vs2017-win2016 - runAsPublic: ${{ parameters.runAsPublic }} - publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} - enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} - -- ${{ if and(eq(parameters.graphFileGeneration.enabled, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - template: ../job/generate-graph-files.yml + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml parameters: - continueOnError: ${{ parameters.continueOnError }} - includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} - dependsOn: - - Asset_Registry_Publish - pool: - vmImage: vs2017-win2016 + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + pool: + vmImage: vs2017-win2016 + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + + - ${{ if eq(parameters.graphFileGeneration.enabled, true) }}: + - template: ../job/generate-graph-files.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} + dependsOn: + - Asset_Registry_Publish + pool: + vmImage: vs2017-win2016 diff --git a/eng/common/templates/jobs/source-build.yml b/eng/common/templates/jobs/source-build.yml new file mode 100644 index 0000000000000..00aa98eb3bfd3 --- /dev/null +++ b/eng/common/templates/jobs/source-build.yml @@ -0,0 +1,46 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-3e800f1-20190501005343' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} diff --git a/eng/common/templates/phases/publish-build-assets.yml b/eng/common/templates/phases/publish-build-assets.yml index a0a8074282aa8..4e51e472e2bba 100644 --- a/eng/common/templates/phases/publish-build-assets.yml +++ b/eng/common/templates/phases/publish-build-assets.yml @@ -20,6 +20,7 @@ phases: inputs: artifactName: AssetManifests downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - task: AzureKeyVault@1 diff --git a/eng/common/templates/post-build/channels/generic-internal-channel.yml b/eng/common/templates/post-build/channels/generic-internal-channel.yml index ad9375f5e5c5a..8990dfc8c87cc 100644 --- a/eng/common/templates/post-build/channels/generic-internal-channel.yml +++ b/eng/common/templates/post-build/channels/generic-internal-channel.yml @@ -1,5 +1,10 @@ parameters: - publishInstallersAndChecksums: false + BARBuildId: '' + PromoteToChannelIds: '' + artifactsPublishingAdditionalParameters: '' + dependsOn: + - Validate + publishInstallersAndChecksums: true symbolPublishingAdditionalParameters: '' stageName: '' channelName: '' @@ -10,37 +15,65 @@ parameters: stages: - stage: ${{ parameters.stageName }} - dependsOn: validate + dependsOn: ${{ parameters.dependsOn }} variables: - template: ../common-variables.yml displayName: ${{ parameters.channelName }} Publishing jobs: - template: ../setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - - job: + - job: publish_symbols displayName: Symbol Publishing dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) variables: - group: DotNet-Symbol-Server-Pats + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage + # This is necessary whenever we want to publish/restore to an AzDO private feed - task: NuGetAuthenticate@0 displayName: 'Authenticate to AzDO Feeds' - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - artifactName: 'BlobArtifacts' + displayName: Download Build Assets continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PdbArtifacts/** + BlobArtifacts/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true - - task: DownloadBuildArtifacts@0 - displayName: Download PDB Artifacts + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: PowerShell@2 + displayName: Enable cross-org publishing inputs: - artifactName: 'PDBArtifacts' - continueOnError: true + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) - task: PowerShell@2 displayName: Publish @@ -53,39 +86,52 @@ stages: /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' /p:SymbolPublishingExclusionsFile='$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' /p:Configuration=Release + /p:PublishToMSDL=false ${{ parameters.symbolPublishingAdditionalParameters }} + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'SymbolPublishing' + - job: publish_assets displayName: Publish Assets dependsOn: setupMaestroVars + timeoutInMinutes: 120 variables: - - group: DotNet-Blob-Feed - - group: AzureDevOps-Artifact-Feeds-Pats - name: BARBuildId value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - name: IsStableBuild value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }})) + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) pool: vmImage: 'windows-2019' steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: current - artifactName: PackageArtifacts + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: current - artifactName: BlobArtifacts - - - task: DownloadBuildArtifacts@0 - displayName: Download Asset Manifests + displayName: Download Build Assets + continueOnError: true inputs: - buildType: current - artifactName: AssetManifests + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PackageArtifacts/** + BlobArtifacts/** + AssetManifests/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' @@ -105,6 +151,7 @@ stages: inputs: filePath: eng\common\sdk-task.ps1 arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet + /p:PublishingInfraVersion=2 /p:IsStableBuild=$(IsStableBuild) /p:IsInternalBuild=$(IsInternalBuild) /p:RepositoryName=$(Build.Repository.Name) @@ -119,12 +166,11 @@ stages: /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts/' /p:Configuration=Release - /p:PublishInstallersAndChecksums=true + /p:PublishInstallersAndChecksums=${{ parameters.publishInstallersAndChecksums }} /p:ChecksumsTargetStaticFeed=$(InternalChecksumsBlobFeedUrl) /p:ChecksumsAzureAccountKey=$(InternalChecksumsBlobFeedKey) /p:InstallersTargetStaticFeed=$(InternalInstallersBlobFeedUrl) /p:InstallersAzureAccountKey=$(InternalInstallersBlobFeedKey) - /p:PublishToAzureDevOpsNuGetFeeds=true /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' @@ -134,6 +180,11 @@ stages: /p:PublishToMSDL=false ${{ parameters.artifactsPublishingAdditionalParameters }} - - template: ../../steps/promote-build.yml + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'AssetsPublishing' + + - template: ../../steps/add-build-to-channel.yml parameters: ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/channels/generic-public-channel.yml b/eng/common/templates/post-build/channels/generic-public-channel.yml index c4bc1897d81f3..3220c6a4f92ff 100644 --- a/eng/common/templates/post-build/channels/generic-public-channel.yml +++ b/eng/common/templates/post-build/channels/generic-public-channel.yml @@ -1,6 +1,10 @@ parameters: + BARBuildId: '' + PromoteToChannelIds: '' artifactsPublishingAdditionalParameters: '' - publishInstallersAndChecksums: false + dependsOn: + - Validate + publishInstallersAndChecksums: true symbolPublishingAdditionalParameters: '' stageName: '' channelName: '' @@ -8,36 +12,54 @@ parameters: transportFeed: '' shippingFeed: '' symbolsFeed: '' + # If the channel name is empty, no links will be generated + akaMSChannelName: '' stages: - stage: ${{ parameters.stageName }} - dependsOn: validate + dependsOn: ${{ parameters.dependsOn }} variables: - template: ../common-variables.yml displayName: ${{ parameters.channelName }} Publishing jobs: - template: ../setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - - job: + - job: publish_symbols displayName: Symbol Publishing dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) variables: - group: DotNet-Symbol-Server-Pats + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - artifactName: 'BlobArtifacts' - continueOnError: true + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage - task: DownloadBuildArtifacts@0 - displayName: Download PDB Artifacts - inputs: - artifactName: 'PDBArtifacts' + displayName: Download Build Assets continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PdbArtifacts/** + BlobArtifacts/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here @@ -64,37 +86,51 @@ stages: /p:Configuration=Release ${{ parameters.symbolPublishingAdditionalParameters }} + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'SymbolPublishing' + - job: publish_assets displayName: Publish Assets dependsOn: setupMaestroVars + timeoutInMinutes: 120 variables: - name: BARBuildId value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - name: IsStableBuild value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }})) + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + - name: ArtifactsCategory + value: ${{ coalesce(variables._DotNetArtifactsCategory, '.NETCore') }} + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) pool: vmImage: 'windows-2019' steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: current - artifactName: PackageArtifacts - continueOnError: true + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: current - artifactName: BlobArtifacts + displayName: Download Build Assets continueOnError: true - - - task: DownloadBuildArtifacts@0 - displayName: Download Asset Manifests inputs: - buildType: current - artifactName: AssetManifests + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PackageArtifacts/** + BlobArtifacts/** + AssetManifests/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' @@ -114,7 +150,8 @@ stages: inputs: filePath: eng\common\sdk-task.ps1 arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet - /p:ArtifactsCategory=$(_DotNetArtifactsCategory) + /p:PublishingInfraVersion=2 + /p:ArtifactsCategory=$(ArtifactsCategory) /p:IsStableBuild=$(IsStableBuild) /p:IsInternalBuild=$(IsInternalBuild) /p:RepositoryName=$(Build.Repository.Name) @@ -134,15 +171,22 @@ stages: /p:InstallersAzureAccountKey=$(dotnetcli-storage-key) /p:ChecksumsTargetStaticFeed=$(ChecksumsBlobFeedUrl) /p:ChecksumsAzureAccountKey=$(dotnetclichecksums-storage-key) - /p:PublishToAzureDevOpsNuGetFeeds=true /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' /p:AzureDevOpsStaticTransportFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticSymbolsFeed='${{ parameters.symbolsFeed }}' /p:AzureDevOpsStaticSymbolsFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:LatestLinkShortUrlPrefix=dotnet/'${{ parameters.akaMSChannelName }}' + /p:AkaMSClientId=$(akams-client-id) + /p:AkaMSClientSecret=$(akams-client-secret) ${{ parameters.artifactsPublishingAdditionalParameters }} - - template: ../../steps/promote-build.yml + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'AssetsPublishing' + + - template: ../../steps/add-build-to-channel.yml parameters: ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml index 1883d2b17006e..c99fd7503767c 100644 --- a/eng/common/templates/post-build/common-variables.yml +++ b/eng/common/templates/post-build/common-variables.yml @@ -4,13 +4,13 @@ variables: - group: DotNet-DotNetCli-Storage - group: DotNet-MSRC-Storage - group: Publish-Build-Assets - + # .NET Core 3.1 Dev - name: PublicDevRelease_31_Channel_Id value: 128 - # .NET Core 5 Dev - - name: NetCore_5_Dev_Channel_Id + # .NET 5 Dev + - name: Net_5_Dev_Channel_Id value: 131 # .NET Eng - Validation @@ -29,6 +29,14 @@ variables: - name: NetCore_3_Tools_Channel_Id value: 344 + # .NET Core 3.0 Internal Servicing + - name: InternalServicing_30_Channel_Id + value: 184 + + # .NET Core 3.0 Release + - name: PublicRelease_30_Channel_Id + value: 19 + # .NET Core 3.1 Release - name: PublicRelease_31_Channel_Id value: 129 @@ -41,6 +49,10 @@ variables: - name: NetCore_31_Blazor_Features_Channel_Id value: 531 + # .NET Core Experimental + - name: NetCore_Experimental_Channel_Id + value: 562 + # Whether the build is internal or not - name: IsInternalBuild value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} @@ -51,7 +63,7 @@ variables: - name: MaestroApiAccessToken value: $(MaestroAccessToken) - name: MaestroApiVersion - value: "2019-01-16" + value: "2020-02-20" - name: SourceLinkCLIVersion value: 3.0.0 @@ -78,3 +90,10 @@ variables: value: https://dotnetclimsrc.blob.core.windows.net/dotnet/index.json - name: InternalInstallersBlobFeedKey value: $(dotnetclimsrc-access-key) + + # Skip component governance and codesign validation for SDL. These jobs + # create no content. + - name: skipComponentGovernanceDetection + value: true + - name: runCodesignValidationInjection + value: false diff --git a/eng/common/templates/post-build/darc-gather-drop.yml b/eng/common/templates/post-build/darc-gather-drop.yml deleted file mode 100644 index 3268ccaa55139..0000000000000 --- a/eng/common/templates/post-build/darc-gather-drop.yml +++ /dev/null @@ -1,23 +0,0 @@ -parameters: - ChannelId: 0 - -jobs: -- job: gatherDrop - displayName: Gather Drop - dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.ChannelId }})) - variables: - - name: BARBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - pool: - vmImage: 'windows-2019' - steps: - - task: PowerShell@2 - displayName: Darc gather-drop - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/darc-gather-drop.ps1 - arguments: -BarBuildId $(BARBuildId) - -DropLocation $(Agent.BuildDirectory)/Temp/Drop/ - -MaestroApiAccessToken $(MaestroApiAccessToken) - -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index 9104ab1870be9..4f79cf0f33703 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -1,14 +1,31 @@ parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V2 accepts optionally outlining the publishing stages - default is inline. + # Publishing V3 DOES NOT accept inlining the publishing stages. + publishingInfraVersion: 2 + # When set to true the publishing templates from the repo will be used + # otherwise Darc add-build-to-channel will be used to trigger the promotion pipeline + inline: true + + # Only used if inline==false. When set to true will stall the current build until + # the Promotion Pipeline build finishes. Otherwise, the current build will continue + # execution concurrently with the promotion build. + waitPublishingFinish: true + + BARBuildId: '' + PromoteToChannelIds: '' + enableSourceLinkValidation: false enableSigningValidation: true enableSymbolValidation: false enableNugetValidation: true - publishInstallersAndChecksums: false + publishInstallersAndChecksums: true SDLValidationParameters: enable: false continueOnError: false params: '' artifactNames: '' + downloadArtifacts: true # These parameters let the user customize the call to sdk-task.ps1 for publishing # symbols & general artifacts as well as for signing validation @@ -17,24 +34,90 @@ parameters: signingValidationAdditionalParameters: '' # Which stages should finish execution before post-build stages start - dependsOn: [build] + validateDependsOn: + - build + publishDependsOn: + - Validate + + # Channel ID's instantiated in this file. + # When adding a new channel implementation the call to `check-channel-consistency.ps1` + # needs to be updated with the new channel ID + NetEngLatestChannelId: 2 + NetEngValidationChannelId: 9 + NetDev5ChannelId: 131 + NetDev6ChannelId: 1296 + GeneralTestingChannelId: 529 + NETCoreToolingDevChannelId: 548 + NETCoreToolingReleaseChannelId: 549 + NETInternalToolingChannelId: 551 + NETCoreExperimentalChannelId: 562 + NetEngServicesIntChannelId: 678 + NetEngServicesProdChannelId: 679 + NetCoreSDK313xxChannelId: 759 + NetCoreSDK313xxInternalChannelId: 760 + NetCoreSDK314xxChannelId: 921 + NetCoreSDK314xxInternalChannelId: 922 + VS166ChannelId: 1010 + VS167ChannelId: 1011 + VS168ChannelId: 1154 + VSMasterChannelId: 1012 + VS169ChannelId: 1473 + VS1610ChannelId: 1692 stages: -- stage: validate - dependsOn: ${{ parameters.dependsOn }} - displayName: Validate - jobs: - - ${{ if eq(parameters.enableNugetValidation, 'true') }}: +- ${{ if or(and(le(parameters.publishingInfraVersion, 2), eq(parameters.inline, 'true')), eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + jobs: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - ${{ if and(le(parameters.publishingInfraVersion, 2), eq(parameters.inline, 'true')) }}: + - job: + displayName: Post-build Checks + dependsOn: setupMaestroVars + variables: + - name: TargetChannels + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Maestro Channels Consistency + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/check-channel-consistency.ps1 + arguments: -PromoteToChannels "$(TargetChannels)" + -AvailableChannelIds ${{parameters.NetEngLatestChannelId}},${{parameters.NetEngValidationChannelId}},${{parameters.NetDev5ChannelId}},${{parameters.NetDev6ChannelId}},${{parameters.GeneralTestingChannelId}},${{parameters.NETCoreToolingDevChannelId}},${{parameters.NETCoreToolingReleaseChannelId}},${{parameters.NETInternalToolingChannelId}},${{parameters.NETCoreExperimentalChannelId}},${{parameters.NetEngServicesIntChannelId}},${{parameters.NetEngServicesProdChannelId}},${{parameters.NetCoreSDK313xxChannelId}},${{parameters.NetCoreSDK313xxInternalChannelId}},${{parameters.NetCoreSDK314xxChannelId}},${{parameters.NetCoreSDK314xxInternalChannelId}},${{parameters.VS166ChannelId}},${{parameters.VS167ChannelId}},${{parameters.VS168ChannelId}},${{parameters.VSMasterChannelId}},${{parameters.VS169ChannelId}},${{parameters.VS1610ChannelId}} + - job: displayName: NuGet Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableNugetValidation }}, 'true') pool: vmImage: 'windows-2019' + variables: + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] steps: - task: DownloadBuildArtifacts@0 displayName: Download Package Artifacts inputs: - buildType: current + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) artifactName: PackageArtifacts + checkDownloadedFiles: true - task: PowerShell@2 displayName: Validate @@ -43,47 +126,88 @@ stages: arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ - - ${{ if eq(parameters.enableSigningValidation, 'true') }}: - job: displayName: Signing Validation + dependsOn: setupMaestroVars + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + variables: + - template: common-variables.yml + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here # otherwise it'll complain about accessing a private feed. - task: NuGetAuthenticate@0 displayName: 'Authenticate to AzDO Feeds' - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts + - task: PowerShell@2 + displayName: Enable cross-org publishing inputs: - buildType: current - artifactName: PackageArtifacts + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. - task: PowerShell@2 displayName: Validate inputs: filePath: eng\common\sdk-task.ps1 - arguments: -task SigningValidation -restore -msbuildEngine dotnet + arguments: -task SigningValidation -restore -msbuildEngine vs /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' - /p:Configuration=Release ${{ parameters.signingValidationAdditionalParameters }} - - ${{ if eq(parameters.enableSourceLinkValidation, 'true') }}: + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + - job: displayName: SourceLink Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') variables: - template: common-variables.yml + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 displayName: Download Blob Artifacts inputs: - buildType: current + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) artifactName: BlobArtifacts + checkDownloadedFiles: true - task: PowerShell@2 displayName: Validate @@ -96,309 +220,370 @@ stages: -SourcelinkCliVersion $(SourceLinkCLIVersion) continueOnError: true - - ${{ if eq(parameters.SDLValidationParameters.enable, 'true') }}: - template: /eng/common/templates/job/execute-sdl.yml parameters: + enable: ${{ parameters.SDLValidationParameters.enable }} + dependsOn: setupMaestroVars additionalParameters: ${{ parameters.SDLValidationParameters.params }} continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} + downloadArtifacts: ${{ parameters.SDLValidationParameters.downloadArtifacts }} + +- ${{ if or(ge(parameters.publishingInfraVersion, 3), eq(parameters.inline, 'false')) }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ if and(ne(parameters.enableNugetValidation, 'true'), ne(parameters.enableSigningValidation, 'true'), ne(parameters.enableSourceLinkValidation, 'true'), ne(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + jobs: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Dev31_Publish' - channelName: '.NET Core 3.1 Dev' - channelId: 128 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Latest_Publish' - channelName: '.NET Eng - Latest' - channelId: 2 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Validation_Publish' - channelName: '.NET Eng - Validation' - channelId: 9 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_3_Tools_Validation_Publish' - channelName: '.NET 3 Tools - Validation' - channelId: 390 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_3_Tools_Publish' - channelName: '.NET 3 Tools' - channelId: 344 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Release31_Publish' - channelName: '.NET Core 3.1 Release' - channelId: 129 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Blazor31_Features_Publish' - channelName: '.NET Core 3.1 Blazor Features' - channelId: 531 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_31_Internal_Servicing_Publishing' - channelName: '.NET Core 3.1 Internal Servicing' - channelId: 550 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'General_Testing_Publish' - channelName: 'General Testing' - channelId: 529 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_Tooling_Dev_Publishing' - channelName: '.NET Core Tooling Dev' - channelId: 548 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_Tooling_Release_Publishing' - channelName: '.NET Core Tooling Release' - channelId: 549 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_311xx_Publishing' - channelName: '.NET Core SDK 3.1.1xx' - channelId: 560 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_311xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.1xx Internal' - channelId: 559 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_312xx_Publishing' - channelName: '.NET Core SDK 3.1.2xx' - channelId: 558 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_312xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.2xx Internal' - channelId: 557 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_313xx_Publishing' - channelName: '.NET Core SDK 3.1.3xx' - channelId: 759 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_313xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.3xx Internal' - channelId: 760 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_314xx_Publishing' - channelName: '.NET Core SDK 3.1.4xx' - channelId: 921 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_314xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.4xx Internal' - channelId: 922 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'VS16_6_Publishing' - channelName: 'VS 16.6' - channelId: 1010 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'VS16_7_Publishing' - channelName: 'VS 16.7' - channelId: 1011 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'VS16_8_Publishing' - channelName: 'VS 16.8' - channelId: 1154 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'VS16_9_Publishing' - channelName: 'VS 16.9' - channelId: 1473 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'VS16_10_Publishing' - channelName: 'VS 16.10' - channelId: 1692 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'VS_Master_Publishing' - channelName: 'VS Master' - channelId: 1012 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' \ No newline at end of file + - job: + displayName: Publish Using Darc + dependsOn: setupMaestroVars + timeoutInMinutes: 120 + variables: + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.PublishingInfraVersion }} + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish ${{ parameters.waitPublishingFinish }} + -PublishInstallersAndChecksums ${{ parameters.publishInstallersAndChecksums }} + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + +- ${{ if and(le(parameters.publishingInfraVersion, 2), eq(parameters.inline, 'true')) }}: + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NetCore_Dev5_Publish' + channelName: '.NET 5 Dev' + akaMSChannelName: 'net5/dev' + channelId: ${{ parameters.NetDev5ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NetCore_Dev6_Publish' + channelName: '.NET 6 Dev' + akaMSChannelName: 'net6/dev' + channelId: ${{ parameters.NetDev6ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Latest_Publish' + channelName: '.NET Eng - Latest' + akaMSChannelName: 'eng/daily' + channelId: ${{ parameters.NetEngLatestChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Validation_Publish' + channelName: '.NET Eng - Validation' + akaMSChannelName: 'eng/validation' + channelId: ${{ parameters.NetEngValidationChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'General_Testing_Publish' + channelName: 'General Testing' + akaMSChannelName: 'generaltesting' + channelId: ${{ parameters.GeneralTestingChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_Tooling_Dev_Publishing' + channelName: '.NET Core Tooling Dev' + channelId: ${{ parameters.NETCoreToolingDevChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_Tooling_Release_Publishing' + channelName: '.NET Core Tooling Release' + channelId: ${{ parameters.NETCoreToolingReleaseChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NET_Internal_Tooling_Publishing' + channelName: '.NET Internal Tooling' + channelId: ${{ parameters.NETInternalToolingChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_Experimental_Publishing' + channelName: '.NET Core Experimental' + channelId: ${{ parameters.NETCoreExperimentalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Services_Int_Publish' + channelName: '.NET Eng Services - Int' + channelId: ${{ parameters.NetEngServicesIntChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Services_Prod_Publish' + channelName: '.NET Eng Services - Prod' + channelId: ${{ parameters.NetEngServicesProdChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_314xx_Publishing' + channelName: '.NET Core SDK 3.1.4xx' + channelId: ${{ parameters.NetCoreSDK314xxChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_314xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.4xx Internal' + channelId: ${{ parameters.NetCoreSDK314xxInternalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_313xx_Publishing' + channelName: '.NET Core SDK 3.1.3xx' + channelId: ${{ parameters.NetCoreSDK313xxChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_313xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.3xx Internal' + channelId: ${{ parameters.NetCoreSDK313xxInternalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS16_6_Publishing' + channelName: 'VS 16.6' + channelId: ${{ parameters.VS166ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS16_7_Publishing' + channelName: 'VS 16.7' + channelId: ${{ parameters.VS167ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS16_8_Publishing' + channelName: 'VS 16.8' + channelId: ${{ parameters.VS168ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_Master_Publishing' + channelName: 'VS Master' + channelId: ${{ parameters.VSMasterChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_16_9_Publishing' + channelName: 'VS 16.9' + channelId: ${{ parameters.VS169ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_16_10_Publishing' + channelName: 'VS 16.10' + channelId: ${{ parameters.VS1610ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' diff --git a/eng/common/templates/post-build/promote-build.yml b/eng/common/templates/post-build/promote-build.yml deleted file mode 100644 index 6b479c3b82a89..0000000000000 --- a/eng/common/templates/post-build/promote-build.yml +++ /dev/null @@ -1,25 +0,0 @@ -parameters: - ChannelId: 0 - -jobs: -- job: - displayName: Promote Build - dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.ChannelId }})) - variables: - - name: BARBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - - name: ChannelId - value: ${{ parameters.ChannelId }} - pool: - vmImage: 'windows-2019' - steps: - - task: PowerShell@2 - displayName: Add Build to Channel - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/promote-build.ps1 - arguments: -BuildId $(BARBuildId) - -ChannelId $(ChannelId) - -MaestroApiAccessToken $(MaestroApiAccessToken) - -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/post-build/setup-maestro-vars.yml b/eng/common/templates/post-build/setup-maestro-vars.yml index 56242b068e15b..4a22b2e6f6de7 100644 --- a/eng/common/templates/post-build/setup-maestro-vars.yml +++ b/eng/common/templates/post-build/setup-maestro-vars.yml @@ -1,18 +1,78 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + jobs: - job: setupMaestroVars displayName: Setup Maestro Vars + variables: + - template: common-variables.yml pool: vmImage: 'windows-2019' steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Release Configs - inputs: - buildType: current - artifactName: ReleaseConfigs + - checkout: none + + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true - task: PowerShell@2 name: setReleaseVars displayName: Set Release Configs Vars inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/setup-maestro-vars.ps1 - arguments: -ReleaseConfigsPath '$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt' + targetType: inline + script: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId;isOutput=true]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels;isOutput=true]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild;isOutput=true]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName;isOutput=true]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId;isOutput=true]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId;isOutput=true]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff --git a/eng/common/templates/steps/promote-build.yml b/eng/common/templates/steps/add-build-to-channel.yml similarity index 68% rename from eng/common/templates/steps/promote-build.yml rename to eng/common/templates/steps/add-build-to-channel.yml index b90404435dd79..f67a210d62f3e 100644 --- a/eng/common/templates/steps/promote-build.yml +++ b/eng/common/templates/steps/add-build-to-channel.yml @@ -5,9 +5,9 @@ steps: - task: PowerShell@2 displayName: Add Build to Channel inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/promote-build.ps1 + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 arguments: -BuildId $(BARBuildId) -ChannelId ${{ parameters.ChannelId }} -MaestroApiAccessToken $(MaestroApiAccessToken) -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/perf-send-to-helix.yml b/eng/common/templates/steps/perf-send-to-helix.yml deleted file mode 100644 index b3ea9acf1f160..0000000000000 --- a/eng/common/templates/steps/perf-send-to-helix.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Please remember to update the documentation if you make changes to these parameters! -parameters: - HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ - HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' - HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number - HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues - HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group - HelixPreCommands: '' # optional -- commands to run before Helix work item execution - HelixPostCommands: '' # optional -- commands to run after Helix work item execution - WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects - CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload - IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion - DotNetCliPackageType: '' # optional -- either 'sdk' or 'runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json - DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json - EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control - WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." - Creator: '' # optional -- if the build is external, use this to specify who is sending the job - DisplayNamePrefix: 'Send job to Helix' # optional -- rename the beginning of the displayName of the steps in AzDO - condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() - continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false - -steps: - - powershell: $(Build.SourcesDirectory)\eng\common\msbuild.ps1 $(Build.SourcesDirectory)\eng\common\performance\perfhelixpublish.proj /restore /t:Test /bl:$(Build.SourcesDirectory)\artifacts\log\$env:BuildConfig\SendToHelix.binlog - displayName: ${{ parameters.DisplayNamePrefix }} (Windows) - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - Creator: ${{ parameters.Creator }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/performance/perfhelixpublish.proj /restore /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog - displayName: ${{ parameters.DisplayNamePrefix }} (Unix) - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - Creator: ${{ parameters.Creator }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates/steps/publish-logs.yml b/eng/common/templates/steps/publish-logs.yml new file mode 100644 index 0000000000000..88f238f36bfd8 --- /dev/null +++ b/eng/common/templates/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml index 05df886f55f74..cd02ae1607f3b 100644 --- a/eng/common/templates/steps/send-to-helix.yml +++ b/eng/common/templates/steps/send-to-helix.yml @@ -10,7 +10,7 @@ parameters: HelixPostCommands: '' # optional -- commands to run after Helix work item execution WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects - WorkItemTimeout: '' # optional -- a timeout in seconds for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload XUnitProjects: '' # optional -- semicolon delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects @@ -18,11 +18,12 @@ parameters: XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion - DotNetCliPackageType: '' # optional -- either 'sdk' or 'runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json - DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting int) Creator: '' # optional -- if the build is external, use this to specify who is sending the job DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() @@ -55,6 +56,7 @@ steps: DotNetCliVersion: ${{ parameters.DotNetCliVersion }} EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) @@ -85,6 +87,7 @@ steps: DotNetCliVersion: ${{ parameters.DotNetCliVersion }} EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml new file mode 100644 index 0000000000000..e20637ed6a177 --- /dev/null +++ b/eng/common/templates/steps/source-build.yml @@ -0,0 +1,71 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- script: | + set -x + df -h + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $targetRidArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 2205c9e811734..2d8a74f7d9e89 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -5,11 +5,13 @@ [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false } # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. -[string]$configuration = if (Test-Path variable:configuration) { $configuration } else { "Debug" } +[string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' } + +# Set to true to opt out of outputting binary log while running in CI +[bool]$excludeCIBinarylog = if (Test-Path variable:excludeCIBinarylog) { $excludeCIBinarylog } else { $false } # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. -# Binary log must be enabled on CI. -[bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci } +[bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci -and !$excludeCIBinarylog } # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md @@ -24,7 +26,7 @@ [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true } # Adjusts msbuild verbosity level. -[string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { "minimal" } +[string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' } # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } @@ -41,22 +43,30 @@ # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://dot.net/v1/dotnet-install.ps1 -[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { "v1" } +[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci } +# True to exclude prerelease versions Visual Studio during build +[bool]$excludePrereleaseVS = if (Test-Path variable:excludePrereleaseVS) { $excludePrereleaseVS } else { $false } + # An array of names of processes to stop on script exit if prepareMachine is true. -$processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @("msbuild", "dotnet", "vbcscompiler") } +$processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') } + +$disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null } set-strictmode -version 2.0 -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -function Create-Directory([string[]] $path) { - if (!(Test-Path $path)) { - New-Item -path $path -force -itemType "Directory" | Out-Null - } +# If specifies, provides an alternate path for getting .NET Core SDKs and Runtimes. This script will still try public sources first. +[string]$runtimeSourceFeed = if (Test-Path variable:runtimeSourceFeed) { $runtimeSourceFeed } else { $null } +# Base-64 encoded SAS token that has permission to storage container described by $runtimeSourceFeed +[string]$runtimeSourceFeedKey = if (Test-Path variable:runtimeSourceFeedKey) { $runtimeSourceFeedKey } else { $null } + +function Create-Directory ([string[]] $path) { + New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } function Unzip([string]$zipfile, [string]$outpath) { @@ -96,7 +106,10 @@ function Exec-Process([string]$command, [string]$commandArgs) { } } -function InitializeDotNetCli([bool]$install, [string] $runtimeSourceFeed, [string] $runtimeSourceFeedKey) { +# createSdkLocationFile parameter enables a file being generated under the toolset directory +# which writes the sdk's location into. This is only necessary for cmd --> powershell invocations +# as dot sourcing isn't possible. +function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if (Test-Path variable:global:_DotNetInstallDir) { return $global:_DotNetInstallDir } @@ -131,16 +144,16 @@ function InitializeDotNetCli([bool]$install, [string] $runtimeSourceFeed, [strin # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. - if ((-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -ne $null) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { + if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { - $dotnetRoot = Join-Path $RepoRoot ".dotnet" + $dotnetRoot = Join-Path $RepoRoot '.dotnet' if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { - InstallDotNetSdk -dotnetRoot $dotnetRoot -version $dotnetSdkVersion -runtimeSourceFeed $runtimeSourceFeed -runtimeSourceFeedKey $runtimeSourceFeedKey + InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } @@ -148,6 +161,24 @@ function InitializeDotNetCli([bool]$install, [string] $runtimeSourceFeed, [strin $env:DOTNET_INSTALL_DIR = $dotnetRoot } + # Creates a temporary file under the toolset dir. + # The following code block is protecting against concurrent access so that this function can + # be called in parallel. + if ($createSdkLocationFile) { + do { + $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) + } + until (!(Test-Path $sdkCacheFileTemp)) + Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot + + try { + Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') + } catch { + # Somebody beat us + Remove-Item -Path $sdkCacheFileTemp + } + } + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. # It also ensures that VS msbuild will use the downloaded sdk targets. @@ -156,15 +187,6 @@ function InitializeDotNetCli([bool]$install, [string] $runtimeSourceFeed, [strin # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot - # Work around issues with Azure Artifacts credential provider - # https://github.com/dotnet/arcade/issues/3932 - if ($ci) { - $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 - $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 - Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' - Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' - } - Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' @@ -172,7 +194,7 @@ function InitializeDotNetCli([bool]$install, [string] $runtimeSourceFeed, [strin } function GetDotNetInstallScript([string] $dotnetRoot) { - $installScript = Join-Path $dotnetRoot "dotnet-install.ps1" + $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit @@ -201,23 +223,25 @@ function GetDotNetInstallScript([string] $dotnetRoot) { else { throw "Unable to download file in $maxRetries attempts." } + } } return $installScript } -function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = "", [string] $runtimeSourceFeed, [string] $runtimeSourceFeedKey ) { - InstallDotNet -dotnetRoot $dotnetRoot -version $version -architecture $architecture -skipNonVersionedFiles $false -runtimeSourceFeed $runtimeSourceFeed -runtimeSourceFeedKey $runtimeSourceFeedKey +function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { + InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath } -function InstallDotNet([string] $dotnetRoot, - [string] $version, - [string] $architecture = "", - [string] $runtime = "", - [bool] $skipNonVersionedFiles = $false, - [string] $runtimeSourceFeed = "", - [string] $runtimeSourceFeedKey = "") { +function InstallDotNet([string] $dotnetRoot, + [string] $version, + [string] $architecture = '', + [string] $runtime = '', + [bool] $skipNonVersionedFiles = $false, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $noPath) { $installScript = GetDotNetInstallScript $dotnetRoot $installParameters = @{ @@ -228,14 +252,14 @@ function InstallDotNet([string] $dotnetRoot, if ($architecture) { $installParameters.Architecture = $architecture } if ($runtime) { $installParameters.Runtime = $runtime } if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } + if ($noPath) { $installParameters.NoPath = $True } try { & $installScript @installParameters } catch { - # If we fail, retry from a custom (possibly private) location. if ($runtimeSourceFeed -or $runtimeSourceFeedKey) { - Write-Host "Failed to install dotnet runtime '$runtime' version '$version' from public location; trying specified alternate feed credentials" + Write-Host "Failed to install dotnet from public location. Trying from '$runtimeSourceFeed'" if ($runtimeSourceFeed) { $installParameters.AzureFeed = $runtimeSourceFeed } if ($runtimeSourceFeedKey) { @@ -248,11 +272,11 @@ function InstallDotNet([string] $dotnetRoot, & $installScript @installParameters } catch { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet runtime '$runtime' from custom location '$runtimeSourceFeed'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet from custom location '$runtimeSourceFeed'." ExitWithExitCode 1 } } else { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet runtime '$runtime' version '$version' from public location." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet from public location." ExitWithExitCode 1 } } @@ -278,17 +302,26 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = return $global:_MSBuildExe } + # Minimum VS version to require. + $vsMinVersionReqdStr = '16.8' + $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) + + # If the version of msbuild is going to be xcopied, + # use this version. Version matches a package here: + # https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&package=RoslynTools.MSBuild&protocolType=NuGet&version=16.8.0-preview3&view=overview + $defaultXCopyMSBuildVersion = '16.8.0-preview3' + if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { "15.9" } + $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { - $msbuildCmd = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue + $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { # Workaround for https://github.com/dotnet/roslyn/issues/35793 # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ - $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0]) + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path @@ -308,17 +341,35 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { - if (Get-Member -InputObject $GlobalJson.tools -Name "xcopy-msbuild") { + if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] } else { - $vsMajorVersion = $vsMinVersion.Major - $xcopyMSBuildVersion = "$vsMajorVersion.$($vsMinVersion.Minor).0-alpha" + #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download + if($vsMinVersion -lt $vsMinVersionReqd){ + Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" + $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion + } + else{ + # If the VS version IS compatible, look for an xcopy msbuild package + # with a version matching VS. + # Note: If this version does not exist, then an explicit version of xcopy msbuild + # can be specified in global.json. This will be required for pre-release versions of msbuild. + $vsMajorVersion = $vsMinVersion.Major + $vsMinorVersion = $vsMinVersion.Minor + $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" + } } - $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + $vsInstallDir = $null + if ($xcopyMSBuildVersion.Trim() -ine "none") { + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + if ($vsInstallDir -eq $null) { + throw "Could not xcopy msbuild. Please check that package 'RoslynTools.MSBuild @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." + } + } if ($vsInstallDir -eq $null) { - throw "Unable to find Visual Studio that has required version and components installed" + throw 'Unable to find Visual Studio that has required version and components installed' } } @@ -342,7 +393,7 @@ function InstallXCopyMSBuild([string]$packageVersion) { } function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { - $packageName = "RoslynTools.MSBuild" + $packageName = 'RoslynTools.MSBuild' $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" @@ -358,7 +409,7 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { Unzip $packagePath $packageDir } - return Join-Path $packageDir "tools" + return Join-Path $packageDir 'tools' } # @@ -382,15 +433,15 @@ function LocateVisualStudio([object]$vsRequirements = $null){ if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { - $vswhereVersion = "2.5.2" + $vswhereVersion = '2.5.2' } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" - $vsWhereExe = Join-Path $vsWhereDir "vswhere.exe" + $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir - Write-Host "Downloading vswhere" + Write-Host 'Downloading vswhere' $maxRetries = 5 $retries = 1 @@ -415,16 +466,20 @@ function LocateVisualStudio([object]$vsRequirements = $null){ } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $args = @("-latest", "-prerelease", "-format", "json", "-requires", "Microsoft.Component.MSBuild", "-products", "*") + $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') - if (Get-Member -InputObject $vsRequirements -Name "version") { - $args += "-version" + if (!$excludePrereleaseVS) { + $args += '-prerelease' + } + + if (Get-Member -InputObject $vsRequirements -Name 'version') { + $args += '-version' $args += $vsRequirements.version } - if (Get-Member -InputObject $vsRequirements -Name "components") { + if (Get-Member -InputObject $vsRequirements -Name 'components') { foreach ($component in $vsRequirements.components) { - $args += "-requires" + $args += '-requires' $args += $component } } @@ -439,9 +494,15 @@ function LocateVisualStudio([object]$vsRequirements = $null){ return $vsInfo[0] } -function InitializeBuildTool([string] $runtimeSourceFeed, [string] $runtimeSourceFeedKey) { +function InitializeBuildTool() { if (Test-Path variable:global:_BuildTool) { - return $global:_BuildTool + # If the requested msbuild parameters do not match, clear the cached variables. + if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { + Remove-Item variable:global:_BuildTool + Remove-Item variable:global:_MSBuildExe + } else { + return $global:_BuildTool + } } if (-not $msbuildEngine) { @@ -450,28 +511,28 @@ function InitializeBuildTool([string] $runtimeSourceFeed, [string] $runtimeSourc # Initialize dotnet cli if listed in 'tools' $dotnetRoot = $null - if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { - $dotnetRoot = InitializeDotNetCli -install:$restore -runtimeSourceFeed $runtimeSourceFeed -runtimeSourceFeedKey $runtimeSourceFeedKey + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + $dotnetRoot = InitializeDotNetCli -install:$restore } - if ($msbuildEngine -eq "dotnet") { + if ($msbuildEngine -eq 'dotnet') { if (!$dotnetRoot) { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "/global.json must specify 'tools.dotnet'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') - $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp2.1' } + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp3.1' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore } catch { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } - $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472" } + $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472"; ExcludePrereleaseVS = $excludePrereleaseVS } } else { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } @@ -480,26 +541,29 @@ function InitializeBuildTool([string] $runtimeSourceFeed, [string] $runtimeSourc function GetDefaultMSBuildEngine() { # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. - if (Get-Member -InputObject $GlobalJson.tools -Name "vs") { - return "vs" + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + return 'vs' } - if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { - return "dotnet" + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + return 'dotnet' } - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } function GetNuGetPackageCachePath() { if ($env:NUGET_PACKAGES -eq $null) { - # Use local cache on CI to ensure deterministic build, + # Use local cache on CI to ensure deterministic build. + # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116 # use global cache in dev builds to avoid cost of downloading packages. + # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 if ($useGlobalNuGetCache) { - $env:NUGET_PACKAGES = Join-Path $env:UserProfile ".nuget\packages" + $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' } else { - $env:NUGET_PACKAGES = Join-Path $RepoRoot ".packages" + $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' + $env:RESTORENOCACHE = $true } } @@ -512,7 +576,7 @@ function GetSdkTaskProject([string]$taskName) { } function InitializeNativeTools() { - if (Get-Member -InputObject $GlobalJson -Name "native-tools") { + if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { $nativeArgs= @{} if ($ci) { $nativeArgs = @{ @@ -523,8 +587,7 @@ function InitializeNativeTools() { } } -function InitializeToolset([string] $runtimeSourceFeed, [string] $runtimeSourceFeedKey) -{ +function InitializeToolset() { if (Test-Path variable:global:_ToolsetBuildProj) { return $global:_ToolsetBuildProj } @@ -542,20 +605,20 @@ function InitializeToolset([string] $runtimeSourceFeed, [string] $runtimeSourceF } if (-not $restore) { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Toolset version $toolsetVersion has not been restored." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } - $buildTool = InitializeBuildTool -runtimeSourceFeed $runtimeSourceFeed -runtimeSourceFeedKey $runtimeSourceFeedKey + $buildTool = InitializeBuildTool - $proj = Join-Path $ToolsetDir "restore.proj" - $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "ToolsetRestore.binlog") } else { "" } + $proj = Join-Path $ToolsetDir 'restore.proj' + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } '' | Set-Content $proj MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile - $path = Get-Content $toolsetLocationFile -TotalCount 1 + $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1 if (!(Test-Path $path)) { throw "Invalid toolset path: $path" } @@ -571,7 +634,7 @@ function ExitWithExitCode([int] $exitCode) { } function Stop-Processes() { - Write-Host "Killing running build processes..." + Write-Host 'Killing running build processes...' foreach ($processName in $processesToStopOnExit) { Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process } @@ -586,16 +649,34 @@ function MSBuild() { if ($pipelinesLog) { $buildTool = InitializeBuildTool - # Work around issues with Azure Artifacts credential provider - # https://github.com/dotnet/arcade/issues/3932 - if ($ci -and $buildTool.Tool -eq "dotnet") { - dotnet nuget locals http-cache -c + if ($ci -and $buildTool.Tool -eq 'dotnet') { + $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 + $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' } $toolsetBuildProject = InitializeToolset - $path = Split-Path -parent $toolsetBuildProject - $path = Join-Path $path (Join-Path $buildTool.Framework "Microsoft.DotNet.Arcade.Sdk.dll") - $args += "/logger:$path" + $basePath = Split-Path -parent $toolsetBuildProject + $possiblePaths = @( + # new scripts need to work with old packages, so we need to look for the old names/versions + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + ) + $selectedPath = $null + foreach ($path in $possiblePaths) { + if (Test-Path $path -PathType Leaf) { + $selectedPath = $path + break + } + } + if (-not $selectedPath) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Unable to find arcade sdk logger assembly.' + ExitWithExitCode 1 + } + $args += "/logger:$selectedPath" } MSBuild-Core @args @@ -608,13 +689,13 @@ function MSBuild() { # function MSBuild-Core() { if ($ci) { - if (!$binaryLog) { - Write-PipelineTaskError -Message "Binary log must be enabled in CI build." + if (!$binaryLog -and !$excludeCIBinarylog) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' ExitWithExitCode 1 } if ($nodeReuse) { - Write-PipelineTaskError -Message "Node reuse must be disabled in CI build." + Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' ExitWithExitCode 1 } } @@ -624,10 +705,10 @@ function MSBuild-Core() { $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" if ($warnAsError) { - $cmdArgs += " /warnaserror /p:TreatWarningsAsErrors=true" + $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' } else { - $cmdArgs += " /p:TreatWarningsAsErrors=false" + $cmdArgs += ' /p:TreatWarningsAsErrors=false' } foreach ($arg in $args) { @@ -636,17 +717,28 @@ function MSBuild-Core() { } } + $env:ARCADE_BUILD_TOOL_COMMAND = "$($buildTool.Path) $cmdArgs" + $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { - Write-PipelineTaskError -Message "Build failed." + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red $buildLog = GetMSBuildBinaryLogCommandLineArgument $args - if ($buildLog -ne $null) { + if ($null -ne $buildLog) { Write-Host "See log: $buildLog" -ForegroundColor DarkGray } - ExitWithExitCode $exitCode + if ($ci) { + Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + } else { + ExitWithExitCode $exitCode + } } } @@ -654,12 +746,12 @@ function GetMSBuildBinaryLogCommandLineArgument($arguments) { foreach ($argument in $arguments) { if ($argument -ne $null) { $arg = $argument.Trim() - if ($arg.StartsWith("/bl:", "OrdinalIgnoreCase")) { - return $arg.Substring("/bl:".Length) + if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { + return $arg.Substring('/bl:'.Length) } - if ($arg.StartsWith("/binaryLogger:", "OrdinalIgnoreCase")) { - return $arg.Substring("/binaryLogger:".Length) + if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { + return $arg.Substring('/binaryLogger:'.Length) } } } @@ -680,16 +772,26 @@ function IsWindowsPlatform() { return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT } +function Get-Darc($version) { + $darcPath = "$TempDir\darc\$(New-Guid)" + if ($version -ne $null) { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host + } else { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host + } + return "$darcPath\darc.exe" +} + . $PSScriptRoot\pipeline-logging-functions.ps1 -$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") -$EngRoot = Resolve-Path (Join-Path $PSScriptRoot "..") -$ArtifactsDir = Join-Path $RepoRoot "artifacts" -$ToolsetDir = Join-Path $ArtifactsDir "toolset" -$ToolsDir = Join-Path $RepoRoot ".tools" -$LogDir = Join-Path (Join-Path $ArtifactsDir "log") $configuration -$TempDir = Join-Path (Join-Path $ArtifactsDir "tmp") $configuration -$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot "global.json") | ConvertFrom-Json +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$ToolsetDir = Join-Path $ArtifactsDir 'toolset' +$ToolsDir = Join-Path $RepoRoot '.tools' +$LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json # true if global.json contains a "runtimes" section $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } @@ -702,3 +804,18 @@ Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir Write-PipelineSetVariable -Name 'TMP' -Value $TempDir + +# Import custom tools configuration, if present in the repo. +# Note: Import in global scope so that the script set top-level variables without qualification. +if (!$disableConfigureToolsetImport) { + $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' + if (Test-Path $configureToolsetScript) { + . $configureToolsetScript + if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { + if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' + ExitWithExitCode $LastExitCode + } + } + } +} diff --git a/eng/common/tools.sh b/eng/common/tools.sh index c50849b01d0ef..5fad1846e5a53 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -18,9 +18,17 @@ fi # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. configuration=${configuration:-'Debug'} +# Set to true to opt out of outputting binary log while running in CI +exclude_ci_binary_log=${exclude_ci_binary_log:-false} + +if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then + binary_log_default=true +else + binary_log_default=false +fi + # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. -# Binary log must be enabled on CI. -binary_log=${binary_log:-$ci} +binary_log=${binary_log:-$binary_log_default} # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). prepare_machine=${prepare_machine:-false} @@ -28,10 +36,6 @@ prepare_machine=${prepare_machine:-false} # True to restore toolsets and dependencies. restore=${restore:-true} -# Allows restoring .NET Core Runtimes and SDKs from alternative feeds -runtimeSourceFeed=${runtimeSourceFeed:-""} -runtimeSourceFeedKey=${runtimeSourceFeedKey:-""} - # Adjusts msbuild verbosity level. verbosity=${verbosity:-'minimal'} @@ -45,7 +49,7 @@ fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} -# True to attempt using .NET Core already that meets requirements specified in global.json +# True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} @@ -60,6 +64,10 @@ else use_global_nuget_cache=${use_global_nuget_cache:-true} fi +# Used when restoring .NET SDK from alternative feeds +runtime_source_feed=${runtime_source_feed:-''} +runtime_source_feed_key=${runtime_source_feed_key:-''} + # Resolve any symlinks in the given path. function ResolvePath { local path=$1 @@ -81,16 +89,16 @@ function ResolvePath { function ReadGlobalVersion { local key=$1 - local line=`grep -m 1 "$key" "$global_json_file"` - local pattern="\"$key\" *: *\"(.*)\"" + if command -v jq &> /dev/null; then + _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")" + elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then + _ReadGlobalVersion=${BASH_REMATCH[1]} + fi - if [[ ! $line =~ $pattern ]]; then - Write-PipelineTelemetryError -category 'InitializeToolset' "Error: Cannot find \"$key\" in $global_json_file" + if [[ -z "$_ReadGlobalVersion" ]]; then + Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" ExitWithExitCode 1 fi - - # return value - _ReadGlobalVersion=${BASH_REMATCH[1]} } function InitializeDotNetCli { @@ -99,12 +107,6 @@ function InitializeDotNetCli { fi local install=$1 - local runtimeSourceFeedArg="" - local runtimeSourceFeedKeyArg="" - if [[ $# == 3 ]]; then - runtimeSourceFeedArg=$2 - runtimeSourceFeedKeyArg=$3 - fi # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism export DOTNET_MULTILEVEL_LOOKUP=0 @@ -150,7 +152,7 @@ function InitializeDotNetCli { if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then if [[ "$install" == true ]]; then - InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" "unset" $runtimeSourceFeedArg $runtimeSourceFeedKeyArg + InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" else Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" ExitWithExitCode 1 @@ -162,15 +164,6 @@ function InitializeDotNetCli { # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" - # Work around issues with Azure Artifacts credential provider - # https://github.com/dotnet/arcade/issues/3932 - if [[ "$ci" == true ]]; then - export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 - export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 - Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" - Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" - fi - Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" @@ -182,10 +175,10 @@ function InstallDotNetSdk { local root=$1 local version=$2 local architecture="unset" - if [[ $# == 3 ]]; then + if [[ $# -ge 3 ]]; then architecture=$3 fi - InstallDotNet "$root" "$version" $architecture "not-a-runtime" 0 $runtimeSourceFeed $runtimeSourceFeedKey + InstallDotNet "$root" "$version" $architecture 'sdk' 'false' $runtime_source_feed $runtime_source_feed_key } function InstallDotNet { @@ -196,20 +189,20 @@ function InstallDotNet { local install_script=$_GetDotNetInstallScript local archArg='' - if [[ -n "${3:-}" && "$3" != "unset" ]]; then + if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then archArg="--architecture $3" fi local runtimeArg='' - if [[ -n "${4:-}" && "$4" != "not-a-runtime" ]]; then + if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then runtimeArg="--runtime $4" fi - local skipNonVersionedFilesArg="" - if [[ "$#" -ge "5" ]]; then + if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then skipNonVersionedFilesArg="--skip-non-versioned-files" fi bash "$install_script" --version $version --install-dir "$root" $archArg $runtimeArg $skipNonVersionedFilesArg || { local exit_code=$? + echo "Failed to install dotnet SDK from public location (exit code '$exit_code')." local runtimeSourceFeed='' if [[ -n "${6:-}" ]]; then @@ -227,8 +220,6 @@ function InstallDotNet { fi decodedFeedKey=`echo $7 | base64 $decodeArg` runtimeSourceFeedKey="--feed-credential $decodedFeedKey" - else - Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK from public location (exit code '$exit_code')." fi if [[ -n "$runtimeSourceFeed" || -n "$runtimeSourceFeedKey" ]]; then @@ -238,6 +229,9 @@ function InstallDotNet { ExitWithExitCode $exit_code } else + if [[ $exit_code != 0 ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK from public location (exit code '$exit_code')." + fi ExitWithExitCode $exit_code fi } @@ -306,21 +300,23 @@ function InitializeBuildTool { if [[ -n "${_InitializeBuildTool:-}" ]]; then return fi - - InitializeDotNetCli $restore $runtimeSourceFeed $runtimeSourceFeedKey + + InitializeDotNetCli $restore # return values - _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" - _InitializeBuildToolFramework="netcoreapp2.1" + _InitializeBuildToolFramework="netcoreapp3.1" } +# Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116 function GetNuGetPackageCachePath { if [[ -z ${NUGET_PACKAGES:-} ]]; then if [[ "$use_global_nuget_cache" == true ]]; then export NUGET_PACKAGES="$HOME/.nuget/packages" else export NUGET_PACKAGES="$repo_root/.packages" + export RESTORENOCACHE=true fi fi @@ -329,6 +325,9 @@ function GetNuGetPackageCachePath { } function InitializeNativeTools() { + if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then + return + fi if grep -Fq "native-tools" $global_json_file then local nativeArgs="" @@ -371,14 +370,14 @@ function InitializeToolset { if [[ "$binary_log" == true ]]; then bl="/bl:$log_dir/ToolsetRestore.binlog" fi - + echo '' > "$proj" MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" local toolset_build_proj=`cat "$toolset_location_file"` if [[ ! -a "$toolset_build_proj" ]]; then - Write-PipelineTelemetryError -category 'InitializeToolset' "Invalid toolset path: $toolset_build_proj" + Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" ExitWithExitCode 3 fi @@ -406,15 +405,32 @@ function MSBuild { InitializeBuildTool InitializeToolset - # Work around issues with Azure Artifacts credential provider - # https://github.com/dotnet/arcade/issues/3932 if [[ "$ci" == true ]]; then - dotnet nuget locals http-cache -c + export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 + export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" + Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" fi local toolset_dir="${_InitializeToolset%/*}" - local logger_path="$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" - args=( "${args[@]}" "-logger:$logger_path" ) + # new scripts need to work with old packages, so we need to look for the old names/versions + local selectedPath= + local possiblePaths=() + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.Arcade.Sdk.dll" ) + for path in "${possiblePaths[@]}"; do + if [[ -f $path ]]; then + selectedPath=$path + break + fi + done + if [[ -z "$selectedPath" ]]; then + Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly." + ExitWithExitCode 1 + fi + args+=( "-logger:$selectedPath" ) fi MSBuild-Core ${args[@]} @@ -422,13 +438,13 @@ function MSBuild { function MSBuild-Core { if [[ "$ci" == true ]]; then - if [[ "$binary_log" != true ]]; then - Write-PipelineTaskError "Binary log must be enabled in CI build." + if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then + Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." ExitWithExitCode 1 fi if [[ "$node_reuse" == true ]]; then - Write-PipelineTaskError "Node reuse must be disabled in CI build." + Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." ExitWithExitCode 1 fi fi @@ -440,11 +456,26 @@ function MSBuild-Core { warnaserror_switch="/warnaserror" fi - "$_InitializeBuildTool" "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" || { - local exit_code=$? - Write-PipelineTaskError "Build failed (exit code '$exit_code')." - ExitWithExitCode $exit_code + function RunBuildTool { + export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" + + "$_InitializeBuildTool" "$@" || { + local exit_code=$? + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + echo "Build failed with exit code $exit_code. Check errors above." + if [[ "$ci" == "true" ]]; then + Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + else + ExitWithExitCode $exit_code + fi + } } + + RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" } ResolvePath "${BASH_SOURCE[0]}" @@ -463,8 +494,11 @@ temp_dir="$artifacts_dir/tmp/$configuration" global_json_file="$repo_root/global.json" # determine if global.json contains a "runtimes" entry global_json_has_runtimes=false -dotnetlocal_key=`grep -m 1 "runtimes" "$global_json_file"` || true -if [[ -n "$dotnetlocal_key" ]]; then +if command -v jq &> /dev/null; then + if jq -er '. | select(has("runtimes"))' "$global_json_file" &> /dev/null; then + global_json_has_runtimes=true + fi +elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then global_json_has_runtimes=true fi @@ -483,3 +517,18 @@ Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" Write-PipelineSetVariable -name "Temp" -value "$temp_dir" Write-PipelineSetVariable -name "TMP" -value "$temp_dir" + +# Import custom tools configuration, if present in the repo. +if [ -z "${disable_configure_toolset_import:-}" ]; then + configure_toolset_script="$eng_root/configure-toolset.sh" + if [[ -a "$configure_toolset_script" ]]; then + . "$configure_toolset_script" + fi +fi + +# TODO: https://github.com/dotnet/arcade/issues/1468 +# Temporary workaround to avoid breaking change. +# Remove once repos are updated. +if [[ -n "${useInstalledDotNetCli:-}" ]]; then + use_installed_dotnet_cli="$useInstalledDotNetCli" +fi diff --git a/eng/config/OptProf.json b/eng/config/OptProf.json index d2b2cbfd25fbb..b7b8aadd1fa91 100644 --- a/eng/config/OptProf.json +++ b/eng/config/OptProf.json @@ -176,10 +176,6 @@ "filename": "/Microsoft.VisualStudio.LanguageServices.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/Microsoft.CodeAnalysis.VisualBasic.Features.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] @@ -216,10 +212,6 @@ "filename": "/Microsoft.VisualStudio.LanguageServices.Implementation.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.CSharp.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index cf68b513f2a42..fc75d14e9f6d1 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -12,6 +12,7 @@ "Microsoft.CodeAnalysis.Scripting.Common": "arcade", "Microsoft.CodeAnalysis.Workspaces.Common": "arcade", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "arcade", + "Microsoft.CodeAnalysis.Workspaces.Desktop": "arcade", "Microsoft.CodeAnalysis.CSharp": "arcade", "Microsoft.CodeAnalysis.CSharp.CodeStyle": "arcade", "Microsoft.CodeAnalysis.CSharp.Features": "arcade", @@ -47,6 +48,8 @@ "Microsoft.CodeAnalysis.ExternalAccess.FSharp": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.IntelliTrace": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.ProjectSystem": "vs-impl", + "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp": "arcade", + "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.Razor": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.TypeScript": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.UnitTesting": "vs-impl", @@ -66,6 +69,7 @@ "Microsoft.CodeAnalysis.Scripting.Common": "arcade", "Microsoft.CodeAnalysis.Workspaces.Common": "arcade", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "arcade", + "Microsoft.CodeAnalysis.Workspaces.Desktop": "arcade", "Microsoft.CodeAnalysis.CSharp": "arcade", "Microsoft.CodeAnalysis.CSharp.CodeStyle": "arcade", "Microsoft.CodeAnalysis.CSharp.Features": "arcade", @@ -101,6 +105,8 @@ "Microsoft.CodeAnalysis.ExternalAccess.FSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.IntelliTrace": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.ProjectSystem": "arcade", + "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp": "arcade", + "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.Razor": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.TypeScript": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.UnitTesting": "arcade", @@ -169,15 +175,15 @@ "vsBranch": "rel/d16.10", "vsMajorVersion": 16 }, - "main-vs-deps": { + "release/dev16.11-vs-deps": { "nugetKind": [ "Shipping", "NonShipping" ], - "version": "3.10.*", + "version": "3.11.*", "packageFeeds": "default", "channels": [], - "vsBranch": "main", + "vsBranch": "rel/d16.11", "vsMajorVersion": 16 }, "release/dev17.0-preview1-vs-deps": { @@ -188,10 +194,10 @@ "version": "4.0.*", "packageFeeds": "default", "channels": [], - "vsBranch": "feature/d17initial", + "vsBranch": "main", "vsMajorVersion": 17 }, - "release/dev17.0-vs-deps": { + "main-vs-deps": { "nugetKind": [ "Shipping", "NonShipping" @@ -199,7 +205,7 @@ "version": "4.0.*", "packageFeeds": "default", "channels": [], - "vsBranch": "main", + "vsBranch": "feature/d17initial", "vsMajorVersion": 17 }, "main": { @@ -207,11 +213,11 @@ "Shipping", "NonShipping" ], - "version": "3.10.*", + "version": "4.0.*", "packageFeeds": "arcade", "channels": [], - "vsBranch": "main", - "vsMajorVersion": 16 + "vsBranch": "feature/d17initial", + "vsMajorVersion": 17 }, "features/NullableReferenceTypes": { "nugetKind": "PerBuildPreRelease", @@ -290,4 +296,4 @@ "channels": [] } } -} \ No newline at end of file +} diff --git a/eng/docker/Mono/Dockerfile b/eng/docker/Mono/Dockerfile deleted file mode 100644 index f592fa7239cf0..0000000000000 --- a/eng/docker/Mono/Dockerfile +++ /dev/null @@ -1,74 +0,0 @@ -# -# Copyright (c) .NET Foundation and contributors. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -FROM ubuntu:16.04 as Info - -# install wget -RUN apt-get update && \ - apt-get install -y wget - -# we always want to download the content of the release file, regardless of cache -ARG CACHE_BUST=0 -RUN wget https://download.mono-project.com/repo/ubuntu/dists/nightly-xenial/Release - -FROM ubuntu:16.04 - -# Install the base toolchain we need to build anything -# this does not include libraries that we need to compile different projects, we'd like -# them in a different layer. -RUN rm -rf rm -rf /var/lib/apt/lists/* && \ - apt-get clean && \ - apt-get update && \ - apt-get install -y make \ - git \ - curl \ - tar \ - unzip \ - sudo && \ - apt-get clean - -# Dependencies for CoreCLR and CoreFX -RUN apt-get install -y libunwind8 \ - libkrb5-3 \ - libicu55 \ - liblttng-ust0 \ - libssl1.0.0 \ - zlib1g \ - libuuid1 \ - liblldb-3.6 \ - libcurl4-openssl-dev && \ - apt-get clean - -# Install Mono -RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \ - apt install apt-transport-https && \ - (echo "deb https://download.mono-project.com/repo/ubuntu nightly-xenial main" | tee /etc/apt/sources.list.d/mono-official-nightly.list) && \ - (echo "deb https://download.mono-project.com/repo/ubuntu preview-xenial main" | tee /etc/apt/sources.list.d/mono-official-preview.list) && \ - apt-get update && \ - apt-get install -y mono-devel && \ - apt-get clean - - -# Copy will check the hash of the release file. If it's changed it will be copied and everything from this point will re-run regardless of cache status -COPY --from=Info /Release . - -# Update previously installed mono-devel. -RUN apt-get update && apt-get upgrade -y - -# Setup User to match Host User, and give superuser permissions -ARG USER_ID=0 -RUN useradd -m code_executor -u ${USER_ID} -g sudo -RUN echo 'code_executor ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -# With the User Change, we need to change permissions on these directories -RUN chmod -R a+rwx /usr/local -RUN chmod -R a+rwx /home -RUN chmod -R 755 /usr/lib/sudo - -# Set user to the one we just created -USER ${USER_ID} - -# Set working directory -WORKDIR /opt/code \ No newline at end of file diff --git a/eng/docker/mono.sh b/eng/docker/mono.sh deleted file mode 100755 index de707b9f67d06..0000000000000 --- a/eng/docker/mono.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) .NET Foundation and contributors. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -set -e - -dir="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -dockerfile="$dir"/Mono - -[ -z "$CONTAINER_TAG" ] && CONTAINER_TAG="roslyn-build" -[ -z "$CONTAINER_NAME" ] && CONTAINER_NAME="roslyn-build-container-mono-nightly" -[ -z "$DOCKER_HOST_SHARE_dir" ] && DOCKER_HOST_SHARE_DIR="$dir"/../.. - -# Ensure the container isn't already running. Can happened for cancelled jobs in CI -docker kill $CONTAINER_NAME || true -docker container rm $CONTAINER_NAME || true - -# Make container names CI-specific if we're running in CI -# Jenkins -[ ! -z "$BUILD_TAG" ] && CONTAINER_NAME="$BUILD_TAG" - -# Build the docker container (will be fast if it is already built) -echo "Building Docker Container using Dockerfile: $dockerfile" -docker build --build-arg USER_ID=$(id -u) --build-arg CACHE_BUST=$(date +%s) -t $CONTAINER_TAG $dockerfile - -# Run the build in the container -echo "Launching build in Docker Container" -echo "Running command: $BUILD_COMMAND" -echo "Using code from: $DOCKER_HOST_SHARE_DIR" - -# Note: passwords/keys should not be passed in the environment -docker run -t --rm --sig-proxy=true \ - --name $CONTAINER_NAME \ - -v $DOCKER_HOST_SHARE_DIR:/opt/code \ - $CONTAINER_TAG \ - $BUILD_COMMAND "$@" diff --git a/eng/pipelines/test-unix-job-single-machine.yml b/eng/pipelines/test-unix-job-single-machine.yml index 65f60b1fc5f1a..703d92c13c492 100644 --- a/eng/pipelines/test-unix-job-single-machine.yml +++ b/eng/pipelines/test-unix-job-single-machine.yml @@ -36,6 +36,9 @@ jobs: ${{ if ne(parameters.vmImageName, '') }}: vmImage: ${{ parameters.vmImageName }} timeoutInMinutes: 40 + variables: + DOTNET_ROLL_FORWARD: LatestMajor + DOTNET_ROLL_FORWARD_TO_PRERELEASE: 1 steps: - checkout: none diff --git a/eng/pipelines/test-windows-job-single-machine.yml b/eng/pipelines/test-windows-job-single-machine.yml index 201f8d8be85ce..250db3a9c4424 100644 --- a/eng/pipelines/test-windows-job-single-machine.yml +++ b/eng/pipelines/test-windows-job-single-machine.yml @@ -29,6 +29,9 @@ jobs: name: NetCorePublic-Pool queue: ${{ parameters.queueName }} timeoutInMinutes: 120 + variables: + DOTNET_ROLL_FORWARD: LatestMajor + DOTNET_ROLL_FORWARD_TO_PRERELEASE: 1 steps: - checkout: none diff --git a/eng/targets/Imports.targets b/eng/targets/Imports.targets index e33e38e1c82d5..15b067deb0476 100644 --- a/eng/targets/Imports.targets +++ b/eng/targets/Imports.targets @@ -46,15 +46,6 @@ --> false true - - - false - true - + diff --git a/eng/targets/VisualStudio.targets b/eng/targets/VisualStudio.targets index 47be52372592e..c538278ae5690 100644 --- a/eng/targets/VisualStudio.targets +++ b/eng/targets/VisualStudio.targets @@ -15,6 +15,7 @@ $(CreateVsixContainer) + Neutral diff --git a/eng/targets/XUnit.targets b/eng/targets/XUnit.targets index d51023aaab798..e803a80906a25 100644 --- a/eng/targets/XUnit.targets +++ b/eng/targets/XUnit.targets @@ -28,6 +28,22 @@ Condition="Exists('$(RepositoryEngineeringDir)config\test\Core\InstallTraceListener$(DefaultLanguageSourceExtension)')"/> + + + + + + + diff --git a/eng/test-build-correctness.ps1 b/eng/test-build-correctness.ps1 index 539cd70b88a3c..a8e9ecb67ebfe 100644 --- a/eng/test-build-correctness.ps1 +++ b/eng/test-build-correctness.ps1 @@ -12,6 +12,7 @@ [CmdletBinding(PositionalBinding=$false)] param( [string]$configuration = "Debug", + [switch]$enableDumps = $false, [switch]$help) Set-StrictMode -version 2.0 @@ -33,6 +34,14 @@ try { . (Join-Path $PSScriptRoot "build-utils.ps1") Push-Location $RepoRoot + if ($enableDumps) { + $key = "HKLM:\\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" + New-Item -Path $key -ErrorAction SilentlyContinue + New-ItemProperty -Path $key -Name 'DumpType' -PropertyType 'DWord' -Value 2 -Force + New-ItemProperty -Path $key -Name 'DumpCount' -PropertyType 'DWord' -Value 2 -Force + New-ItemProperty -Path $key -Name 'DumpFolder' -PropertyType 'String' -Value $LogDir -Force + } + # Verify no PROTOTYPE marker left in main if ($env:SYSTEM_PULLREQUEST_TARGETBRANCH -eq "main") { Write-Host "Checking no PROTOTYPE markers in compiler source" @@ -67,5 +76,11 @@ catch [exception] { exit 1 } finally { + if ($enableDumps) { + $key = "HKLM:\\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" + Remove-ItemProperty -Path $key -Name 'DumpType' + Remove-ItemProperty -Path $key -Name 'DumpCount' + Remove-ItemProperty -Path $key -Name 'DumpFolder' + } Pop-Location } diff --git a/global.json b/global.json index aac612294ada5..bae01b1ae194e 100644 --- a/global.json +++ b/global.json @@ -1,18 +1,18 @@ { "sdk": { - "version": "5.0.102", + "version": "6.0.100-preview.3.21202.5", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "5.0.102", + "dotnet": "6.0.100-preview.3.21202.5", "vs": { "version": "16.8" }, "xcopy-msbuild": "16.8.0-preview2.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.21161.1", - "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.21161.1" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21303.2", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21303.2" } } diff --git a/src/Analyzers/CSharp/Analyzers/AddAccessibilityModifiers/CSharpAddAccessibilityModifiersDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/AddAccessibilityModifiers/CSharpAddAccessibilityModifiersDiagnosticAnalyzer.cs index a54317d9dc426..a6f3580f908c6 100644 --- a/src/Analyzers/CSharp/Analyzers/AddAccessibilityModifiers/CSharpAddAccessibilityModifiersDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/AddAccessibilityModifiers/CSharpAddAccessibilityModifiersDiagnosticAnalyzer.cs @@ -55,7 +55,8 @@ private void ProcessMemberDeclaration( // If we have a class or struct, recurse inwards. if (member.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) || member.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration) || - member.IsKind(SyntaxKind.RecordDeclaration, out typeDeclaration)) + member.IsKind(SyntaxKind.RecordDeclaration, out typeDeclaration) || + member.IsKind(SyntaxKind.RecordStructDeclaration, out typeDeclaration)) { ProcessMembers(context, option, typeDeclaration.Members); } @@ -116,6 +117,7 @@ private void ProcessMemberDeclaration( case SyntaxKind.ClassDeclaration: case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: { // Inside a type, default is private if (accessibility != Accessibility.Private) diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index 5a807640c8547..7d5a81aba1d08 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -52,6 +52,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/MakeLocalFunctionStatic/MakeLocalFunctionStaticDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MakeLocalFunctionStatic/MakeLocalFunctionStaticDiagnosticAnalyzer.cs index 56f8e1440156e..a8fbf6b30189d 100644 --- a/src/Analyzers/CSharp/Analyzers/MakeLocalFunctionStatic/MakeLocalFunctionStaticDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MakeLocalFunctionStatic/MakeLocalFunctionStaticDiagnosticAnalyzer.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -13,7 +12,7 @@ namespace Microsoft.CodeAnalysis.CSharp.MakeLocalFunctionStatic { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class MakeLocalFunctionStaticDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + internal sealed class MakeLocalFunctionStaticDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer { public MakeLocalFunctionStaticDiagnosticAnalyzer() : base(IDEDiagnosticIds.MakeLocalFunctionStaticDiagnosticId, @@ -29,7 +28,14 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; protected override void InitializeWorker(AnalysisContext context) - => context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.LocalFunctionStatement); + => context.RegisterCompilationStartAction(context => + { + // All trees are expected to have the same language version. So make the check only once in compilation start . + if (context.Compilation.SyntaxTrees.FirstOrDefault() is SyntaxTree tree && MakeLocalFunctionStaticHelper.IsStaticLocalFunctionSupported(tree)) + { + context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.LocalFunctionStatement); + } + }); private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { @@ -40,11 +46,6 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) } var syntaxTree = context.Node.SyntaxTree; - if (!MakeLocalFunctionStaticHelper.IsStaticLocalFunctionSupported(syntaxTree)) - { - return; - } - var cancellationToken = context.CancellationToken; var option = context.Options.GetOption(CSharpCodeStyleOptions.PreferStaticLocalFunction, syntaxTree, cancellationToken); if (!option.Value) diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs index 5bcffd041eee1..065a5f4a4cf7d 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.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.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Precedence; @@ -24,19 +25,20 @@ protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; protected override bool CanRemoveParentheses( - ParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, + ParenthesizedExpressionSyntax parenthesizedExpression, + SemanticModel semanticModel, CancellationToken cancellationToken, out PrecedenceKind precedence, out bool clarifiesPrecedence) { return CanRemoveParenthesesHelper( - parenthesizedExpression, semanticModel, + parenthesizedExpression, semanticModel, cancellationToken, out precedence, out clarifiesPrecedence); } public static bool CanRemoveParenthesesHelper( - ParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, + ParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, CancellationToken cancellationToken, out PrecedenceKind parentPrecedenceKind, out bool clarifiesPrecedence) { - var result = parenthesizedExpression.CanRemoveParentheses(semanticModel); + var result = parenthesizedExpression.CanRemoveParentheses(semanticModel, cancellationToken); if (!result) { parentPrecedenceKind = default; diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryPatternParenthesesDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryPatternParenthesesDiagnosticAnalyzer.cs index bf0eaf391dc7c..048f36b8ee8ad 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryPatternParenthesesDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryPatternParenthesesDiagnosticAnalyzer.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.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Precedence; @@ -24,17 +25,15 @@ protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; protected override bool CanRemoveParentheses( - ParenthesizedPatternSyntax parenthesizedExpression, SemanticModel semanticModel, + ParenthesizedPatternSyntax parenthesizedExpression, + SemanticModel semanticModel, CancellationToken cancellationToken, out PrecedenceKind precedence, out bool clarifiesPrecedence) { - return CanRemoveParenthesesHelper( - parenthesizedExpression, - out precedence, out clarifiesPrecedence); + return CanRemoveParenthesesHelper(parenthesizedExpression, out precedence, out clarifiesPrecedence); } public static bool CanRemoveParenthesesHelper( - ParenthesizedPatternSyntax parenthesizedPattern, - out PrecedenceKind parentPrecedenceKind, out bool clarifiesPrecedence) + ParenthesizedPatternSyntax parenthesizedPattern, out PrecedenceKind parentPrecedenceKind, out bool clarifiesPrecedence) { var result = parenthesizedPattern.CanRemoveParentheses(); if (!result) diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs index 6484ab2763622..8a220b1aa1e4e 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs @@ -4,6 +4,7 @@ #nullable disable +using Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation; using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -16,12 +17,14 @@ namespace Microsoft.CodeAnalysis.CSharp.SimplifyInterpolation { [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class CSharpSimplifyInterpolationDiagnosticAnalyzer : AbstractSimplifyInterpolationDiagnosticAnalyzer< - InterpolationSyntax, ExpressionSyntax, ConditionalExpressionSyntax, ParenthesizedExpressionSyntax> + InterpolationSyntax, ExpressionSyntax> { protected override IVirtualCharService GetVirtualCharService() => CSharpVirtualCharService.Instance; protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; + + protected override AbstractSimplifyInterpolationHelpers GetHelpers() => CSharpSimplifyInterpolationHelpers.Instance; } } diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs new file mode 100644 index 0000000000000..11f479e03f0b4 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.SimplifyInterpolation; + +namespace Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation +{ + internal sealed class CSharpSimplifyInterpolationHelpers : AbstractSimplifyInterpolationHelpers + { + public static CSharpSimplifyInterpolationHelpers Instance { get; } = new(); + + private CSharpSimplifyInterpolationHelpers() { } + + protected override bool PermitNonLiteralAlignmentComponents => true; + + protected override SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation) + { + return operation.Syntax switch + { + ConditionalExpressionSyntax { Parent: ParenthesizedExpressionSyntax parent } => parent, + var syntax => syntax, + }; + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs index c8c9e50207761..48f4e857f23be 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.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.Threading; using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -20,5 +21,11 @@ internal class CSharpUseCoalesceExpressionDiagnosticAnalyzer : { protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; + + protected override bool IsTargetTyped(SemanticModel semanticModel, ConditionalExpressionSyntax conditional, CancellationToken cancellationToken) + { + var conversion = semanticModel.GetConversion(conditional, cancellationToken); + return conversion.IsConditionalExpression; + } } } diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs index 273e1782c8761..7bc3bac93c3ee 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyForAccessorsHelper.cs @@ -22,7 +22,7 @@ private UseExpressionBodyForAccessorsHelper() new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_accessors), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_accessors), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, - ImmutableArray.Create(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration)) + ImmutableArray.Create(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKind.InitAccessorDeclaration)) { } diff --git a/src/Analyzers/CSharp/Analyzers/UseImplicitObjectCreation/CSharpUseImplicitObjectCreationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseImplicitObjectCreation/CSharpUseImplicitObjectCreationDiagnosticAnalyzer.cs index 01d4f840f0e96..ada69073628dc 100644 --- a/src/Analyzers/CSharp/Analyzers/UseImplicitObjectCreation/CSharpUseImplicitObjectCreationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseImplicitObjectCreation/CSharpUseImplicitObjectCreationDiagnosticAnalyzer.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Diagnostics; @@ -88,7 +89,7 @@ objectCreation.Parent.Parent.Parent is VariableDeclarationSyntax variableDeclara ConversionOperatorDeclarationSyntax conversion => conversion.Type, OperatorDeclarationSyntax op => op.ReturnType, BasePropertyDeclarationSyntax property => property.Type, - AccessorDeclarationSyntax { RawKind: (int)SyntaxKind.GetAccessorDeclaration, Parent: AccessorListSyntax { Parent: BasePropertyDeclarationSyntax baseProperty } } accessor => baseProperty.Type, + AccessorDeclarationSyntax(SyntaxKind.GetAccessorDeclaration) { Parent: AccessorListSyntax { Parent: BasePropertyDeclarationSyntax baseProperty } } accessor => baseProperty.Type, _ => null, }; } diff --git a/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs index 09453b54bcce1..f7983fcff4a8d 100644 --- a/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs @@ -229,7 +229,7 @@ private static bool CanReplaceAnonymousWithLocalFunction( if (identifierName.Identifier.ValueText == local.Name && local.Equals(semanticModel.GetSymbolInfo(identifierName, cancellationToken).GetAnySymbol())) { - if (identifierName.IsWrittenTo()) + if (identifierName.IsWrittenTo(semanticModel, cancellationToken)) { // Can't change this to a local function if it is assigned to. return false; diff --git a/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs b/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs index fdf8ca0504b77..31b610a0b1e66 100644 --- a/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs +++ b/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs @@ -113,7 +113,8 @@ private Binary(AnalyzedPattern leftPattern, AnalyzedPattern rightPattern, bool i if (target is null) return null; - if (!SyntaxFactory.AreEquivalent(target.Syntax, rightPattern.Target.Syntax)) + var compareTarget = target == leftTarget ? rightTarget : leftTarget; + if (!SyntaxFactory.AreEquivalent(target.Syntax, compareTarget.Syntax)) return null; return new Binary(leftPattern, rightPattern, isDisjunctive, token, target); diff --git a/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpAsAndNullCheckDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpAsAndNullCheckDiagnosticAnalyzer.cs index 7fdf6d04a5b45..19d4f426e4295 100644 --- a/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpAsAndNullCheckDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpAsAndNullCheckDiagnosticAnalyzer.cs @@ -194,7 +194,7 @@ private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) // Check if this is a 'write' to the asOperand. if (identifierName.Identifier.ValueText == asOperand?.Name && asOperand.Equals(semanticModel.GetSymbolInfo(identifierName, cancellationToken).Symbol) && - identifierName.IsWrittenTo()) + identifierName.IsWrittenTo(semanticModel, cancellationToken)) { return; } diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf index e15b7caa78ba3..9275c7032194a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Za dvojtečkou inicializátoru konstruktoru se nepovoluje prázdný řádek. Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Po sobě jdoucí závorky nesmí mít mezi sebou prázdný řádek. @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Vložené příkazy musí mít svůj vlastní řádek. @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct obsahuje přiřazení do this mimo konstruktor. Nastavte pole určená jen pro čtení jako zapisovatelná. + Struct obsahuje přiřazení do this mimo konstruktor. Nastavte pole určená jen pro čtení jako zapisovatelná. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf index 90c0104c857e9..792d06259729a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Nach dem Doppelpunkt für den Initialisierer des Konstruktors ist keine leere Zeile zulässig. Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Aufeinanderfolgende geschweifte Klammern dürfen keine leere Zeile einschließen. @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Eingebettete Anweisungen müssen in einer eigenen Zeile enthalten sein. @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct enthält eine Zuweisung zu "this" außerhalb des Konstruktors. Legen Sie schreibgeschützte Felder als beschreibbar fest. + "Struct" enthält außerhalb des Konstruktors eine Zuweisung zu "this". Legen Sie schreibgeschützte Felder als beschreibbar fest. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf index 0490e363b6edb..73c4553d30459 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + No se permite una línea en blanco después del signo de dos puntos del inicializador del constructor Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Las llaves consecutivas no deben tener una línea en blanco entre ellas @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Las instrucciones incrustadas deben estar en su propia línea @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct contiene una asignación a "this" fuera del constructor. Convertir en editables los campos de solo lectura + Struct contiene una asignación a "this" fuera del constructor. Convierta en editables los campos de solo lectura. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf index c51eeffb98c41..e438db8ef900f 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Ligne vide non autorisée après le signe des deux-points de l'initialiseur du constructeur Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Les accolades consécutives ne doivent pas avoir de ligne vide entre elles @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Les instructions imbriquées doivent être placées sur leur propre ligne @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct contient une assignation à 'this' en dehors du constructeur. Rendre les champs readonly accessibles en écriture + Struct contient une assignation à 'this' en dehors du constructeur. Rend les champs readonly accessibles en écriture. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf index f1d775a4c57f5..75364ffe50e22 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Non sono consentite righe vuote dopo i due punti dell'inizializzatore del costruttore Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Tra parentesi graffe consecutive non devono essere presenti righe vuote @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Le istruzioni incorporate devono essere posizionate nella rispettiva riga @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct contiene l'assegnazione a 'this' all'esterno del costruttore. Impostare i campi di sola lettura come scrivibili + La parola chiave Struct contiene l'assegnazione a 'this' all'esterno del costruttore. Impostare i campi di sola lettura come scrivibili. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf index 41a487938d06d..19e1034667d43 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + コンストラクター初期化子のコロンの後に空白行を使用することはできません Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + 連続する中かっこの間に空白行を含めることはできません @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + 埋め込みステートメントは独自の行に配置する必要があります @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct に、コンストラクターの外部での 'this' への代入が含まれています。読み取り専用フィールドを書き込み可能にしてください + Struct に、コンストラクター外部の 'this' に対する代入が含まれています。読み取り専用フィールドを書き込み可能にしてください。 {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf index c4de89200d11a..843b32cfc965d 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + 생성자 이니셜라이저 콜론 뒤에 빈 줄을 사용할 수 없음 Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + 연속 중괄호 사이에 빈 줄이 없어야 함 @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + 포함 문은 고유한 줄에 있어야 합니다. @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct에서 'this'에 대한 할당이 생성자 외부에 있습니다. 읽기 전용 필드를 쓰기 가능으로 설정하세요. + Struct에서 'this'에 대한 할당이 생성자 외부에 있습니다. 읽기 전용 필드를 쓰기 가능으로 설정하세요. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf index aa24ad896ad1f..e07e168532c9b 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Pusty wiersz jest niedozwolony po dwukropku inicjatora konstruktora Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Między kolejnymi nawiasami klamrowymi nie może znajdować się pusty wiersz. @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Osadzone instrukcje muszą znajdować się w osobnym wierszu @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Element Struct zawiera przypisanie do elementu „this” poza konstruktorem. Ustaw pola tylko do odczytu jako zapisywalne + Element Struct zawiera przypisanie do elementu „this” poza konstruktorem. Ustaw pola tylko do odczytu jako zapisywalne. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf index acfbfb22143db..973a6ed3437a3 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Linha em branco não permitida após os dois-pontos do inicializador de construtor Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + As chaves consecutivas não podem ter uma linha em branco entre elas @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + As instruções inseridas precisam estar nas próprias linhas @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct contém atribuição para 'this' fora do construtor. Torne os campos somente leitura graváveis + O Struct contém a atribuição para 'this' fora do construtor. Converta os campos somente leitura em graváveis. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf index 065bcc3c8e5e2..e757e4dab8da3 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + После инициализатора конструктора с двоеточием не допускается пустая строка. Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Между последовательными фигурными скобками не должно быть пустых строк. @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Встроенные операторы должны располагаться на отдельной строке. @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct содержит назначение для "this" вне конструктора. Сделайте поля, доступные только для чтения, доступными для записи. + Структура (Struct) содержит присваивание "this" вне конструктора. Сделайте поля, доступные только для чтения, доступными для записи. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf index 77107925f85ab..77b4cbbd66ffb 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + Oluşturucu başlatıcı iki noktadan sonra boş satıra izin verilmiyor Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + Ardışık ayraçlar arasında boş çizgi olmamalıdır @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + Katıştırılmış deyimler kendi satırına yerleştirilmelidir @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct, oluşturucu dışında 'this' öğesine yönelik atama içeriyor. Salt okunur alanları yazılabilir yapın + Struct, oluşturucu dışında 'this' öğesine yönelik atama içeriyor. Salt okunur alanları yazılabilir yapın. {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf index cf840778102eb..709577026af57 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Add braces to '{0}' statement. - 向“{0}”报表添加大括号。 + 向 “{0}” 语句添加大括号。 Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + 构造函数初始值设定项冒号后面不允许有空白行 Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + 连续大括号之间不能有空白行 @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + 嵌入的语句必须放在其自己的行上 @@ -84,12 +84,12 @@ Negate expression (changes semantics) - 请使用求反表达式(更改语义) + 使用求反表达式(更改语义) Remove operator (preserves semantics) - 请删除运算符(保留语义) + 删除运算符(保留语义) @@ -114,12 +114,12 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct 包含对构造函数之外的 "this" 的赋值。使只读字段可写 + Struct 包含对构造函数之外的 "this" 的赋值。使只读字段可写。 {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. Suppression operator has no effect and can be misinterpreted - 请禁止使用不起任何作用且可能被误解的运算符 + 抑制运算符不起任何作用且可能被误解 @@ -289,7 +289,7 @@ 'if' statement can be simplified - 可简化“If”语句 + 可简化 "if" 语句 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf index a9849abcfeeb4..01d387c72a704 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Blank line not allowed after constructor initializer colon - Blank line not allowed after constructor initializer colon + 不允許在建構函式初始設定式冒號後加上空白行 Consecutive braces must not have blank line between them - Consecutive braces must not have blank line between them + 連續大括弧之間不能有空白行 @@ -44,7 +44,7 @@ Embedded statements must be on their own line - Embedded statements must be on their own line + 內嵌陳述式必須位於自己的行中 @@ -114,7 +114,7 @@ Struct contains assignment to 'this' outside of constructor. Make readonly fields writable. - Struct 包含建構函式外對 'this' 的指派。請將唯讀欄位設為可寫入 + Struct 包含建構函式外對 'this' 的指派。請將唯讀欄位設為可寫入。 {Locked="Struct"}{Locked="this"} these are C#/VB keywords and should not be localized. diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs b/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs index e101d73fcbdcd..d66e35ee7b572 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs @@ -216,7 +216,7 @@ private ExpressionSyntax RewriteSwitchStatement(SwitchStatementSyntax node, Sema return SwitchExpression( switchStatement.Expression.Parenthesize(), Token(leading: default, SyntaxKind.SwitchKeyword, node.CloseParenToken.TrailingTrivia), - Token(SyntaxKind.OpenBraceToken), + Token(leading: default, SyntaxKind.OpenBraceToken, node.OpenBraceToken.TrailingTrivia), SeparatedList( switchArms.Select(t => t.armExpression.WithLeadingTrivia(t.tokensForLeadingTrivia.GetTrivia().FilterComments(addElasticMarker: false))), switchArms.Select(t => Token(SyntaxKind.CommaToken).WithTrailingTrivia(t.tokensForTrailingTrivia.GetTrivia().FilterComments(addElasticMarker: true)))), diff --git a/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs index e769cbeb1f7e1..5665e0045e9fc 100644 --- a/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs @@ -110,6 +110,8 @@ private static void HandleSingleIfStatementForm( } newStatement = newStatement.WithAdditionalAnnotations(Formatter.Annotation); + newStatement = AppendTriviaWithoutEndOfLines(newStatement, ifStatement); + cancellationToken.ThrowIfCancellationRequested(); editor.ReplaceNode(ifStatement, newStatement); @@ -143,12 +145,24 @@ private static void HandleVariableAndIfStatementForm( SyntaxFactory.MemberBindingExpression(SyntaxFactory.IdentifierName(nameof(Action.Invoke))), invocationExpression.ArgumentList))); newStatement = newStatement.WithAdditionalAnnotations(Formatter.Annotation); + newStatement = AppendTriviaWithoutEndOfLines(newStatement, ifStatement); editor.ReplaceNode(ifStatement, newStatement); editor.RemoveNode(localDeclarationStatement, SyntaxRemoveOptions.KeepLeadingTrivia | SyntaxRemoveOptions.AddElasticMarker); cancellationToken.ThrowIfCancellationRequested(); } + private static T AppendTriviaWithoutEndOfLines(T newStatement, IfStatementSyntax ifStatement) where T : SyntaxNode + { + // We're combining trivia from the delegate invocation and the end of the if statement + // but we don't want two EndOfLines so we ignore the one on the invocation (if it exists) + var expressionTrivia = newStatement.GetTrailingTrivia(); + var expressionTriviaWithoutEndOfLine = expressionTrivia.Where(t => !t.IsKind(SyntaxKind.EndOfLineTrivia)); + var ifStatementTrivia = ifStatement.GetTrailingTrivia(); + + return newStatement.WithTrailingTrivia(expressionTriviaWithoutEndOfLine.Concat(ifStatementTrivia)); + } + private class MyCodeAction : CustomCodeActions.DocumentChangeAction { public MyCodeAction(Func> createChangedDocument) diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryParenthesesCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryParenthesesCodeFixProvider.cs index e8fb324b82ab9..0912edfa4b341 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryParenthesesCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryParenthesesCodeFixProvider.cs @@ -6,6 +6,7 @@ using System.Composition; using System.Diagnostics.CodeAnalysis; +using System.Threading; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses; @@ -22,10 +23,10 @@ public CSharpRemoveUnnecessaryParenthesesCodeFixProvider() { } - protected override bool CanRemoveParentheses(SyntaxNode current, SemanticModel semanticModel) + protected override bool CanRemoveParentheses(SyntaxNode current, SemanticModel semanticModel, CancellationToken cancellationToken) => current switch { - ParenthesizedExpressionSyntax p => CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.CanRemoveParenthesesHelper(p, semanticModel, out _, out _), + ParenthesizedExpressionSyntax p => CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.CanRemoveParenthesesHelper(p, semanticModel, cancellationToken, out _, out _), ParenthesizedPatternSyntax p => CSharpRemoveUnnecessaryPatternParenthesesDiagnosticAnalyzer.CanRemoveParenthesesHelper(p, out _, out _), _ => false, }; diff --git a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs index ef1160b31ca73..ee5951b6c1fee 100644 --- a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.SimplifyInterpolation; @@ -16,8 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp.SimplifyInterpolation [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.SimplifyInterpolation), Shared] internal class CSharpSimplifyInterpolationCodeFixProvider : AbstractSimplifyInterpolationCodeFixProvider< InterpolationSyntax, ExpressionSyntax, InterpolationAlignmentClauseSyntax, - InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax, - ConditionalExpressionSyntax, ParenthesizedExpressionSyntax> + InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax> { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -25,6 +25,8 @@ public CSharpSimplifyInterpolationCodeFixProvider() { } + protected override AbstractSimplifyInterpolationHelpers GetHelpers() => CSharpSimplifyInterpolationHelpers.Instance; + protected override InterpolationSyntax WithExpression(InterpolationSyntax interpolation, ExpressionSyntax expression) => interpolation.WithExpression(expression); diff --git a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs index c8bb76cd02f65..4bcc1c3f56f0a 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs @@ -162,7 +162,7 @@ private static ExpressionSyntax GetCondition( private class MyCodeAction : CustomCodeActions.DocumentChangeAction { public MyCodeAction(Func> createChangedDocument) - : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument) + : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument, nameof(CSharpAsAndNullCheckCodeFixProvider)) { } } diff --git a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpIsAndCastCheckCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpIsAndCastCheckCodeFixProvider.cs index 7120ee01233c3..195c4b6dfae69 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpIsAndCastCheckCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpIsAndCastCheckCodeFixProvider.cs @@ -110,7 +110,7 @@ private static IfStatementSyntax GetUpdatedIfStatement( private class MyCodeAction : CustomCodeActions.DocumentChangeAction { public MyCodeAction(Func> createChangedDocument) - : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument) + : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument, nameof(CSharpIsAndCastCheckCodeFixProvider)) { } } diff --git a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpUseNotPatternCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpUseNotPatternCodeFixProvider.cs index c43bc8ddcf311..2b32550c04602 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpUseNotPatternCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpUseNotPatternCodeFixProvider.cs @@ -78,7 +78,7 @@ private static void ProcessDiagnostic( private class MyCodeAction : CustomCodeActions.DocumentChangeAction { public MyCodeAction(Func> createChangedDocument) - : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument, CSharpAnalyzersResources.Use_pattern_matching) + : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument, nameof(CSharpUseNotPatternCodeFixProvider)) { } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs index 51db70cec4bfc..b7faa6ff7e0d3 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs @@ -119,8 +119,21 @@ private static SyntaxTriviaList Expand(List result, UsingStatem { case BlockSyntax blockSyntax: var statements = blockSyntax.Statements; + if (!statements.Any()) + { + return blockSyntax.CloseBraceToken.LeadingTrivia; + } var openBraceTrailingTrivia = blockSyntax.OpenBraceToken.TrailingTrivia; + var usingHasEndOfLineTrivia = usingStatement.CloseParenToken.TrailingTrivia + .Any(SyntaxKind.EndOfLineTrivia); + if (!usingHasEndOfLineTrivia) + { + var newFirstStatement = statements.First() + .WithPrependedLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); + statements = statements.Replace(statements.First(), newFirstStatement); + } + if (openBraceTrailingTrivia.Any(t => t.IsSingleOrMultiLineComment())) { var newFirstStatement = statements.First() diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf index b2ff656a717e7..634d776f8c871 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Vložit dvojtečku na následující řádek Place statement on following line - Place statement on following line + Umístit příkaz na následující řádek @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Odebrat prázdný řádek mezi závorkami diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf index dbddc7708a438..05489cf79b208 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Doppelpunkt in folgender Zeile platzieren Place statement on following line - Place statement on following line + Anweisung in folgender Zeile platzieren @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Leerzeile zwischen geschweiften Klammern entfernen diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf index 4f1fdd93a9301..bee65143c2af4 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Colocar dos puntos en la línea siguiente Place statement on following line - Place statement on following line + Instrucción de colocación en la línea siguiente @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Quitar línea en blanco entre llaves diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf index ebee402cbdd0c..858f532ececb0 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Placer le signe des deux-points sur la ligne suivante Place statement on following line - Place statement on following line + Placer l'instruction sur la ligne suivante @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Supprimer la ligne vide entre les accolades diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf index 08a0b4cedc2a5..c21e7a071a741 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Inserisci due punti nella riga seguente Place statement on following line - Place statement on following line + Inserire l'istruzione nella riga seguente @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Rimuovi la riga vuota tra parentesi graffe diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf index e348a3734f3ca..27c492588b41e 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + 次の行にコロンを配置する Place statement on following line - Place statement on following line + 次の行にステートメントを配置する @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + 中かっこの間の空白行を削除する diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf index 658e1b47a2175..a515e1168d8ae 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + 다음 줄에 콜론 배치 Place statement on following line - Place statement on following line + 다음 줄에 문 배치 @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + 중괄호 사이의 빈 줄 제거 diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf index 95f762f21b707..854758d6afbff 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Umieść dwukropek w następnym wierszu Place statement on following line - Place statement on following line + Umieść instrukcję w następnym wierszu @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Usuń pusty wiersz w nawiasach klamrowych diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf index 21790699ab831..49bb4cd9fffcd 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Colocar os dois-pontos na próxima linha Place statement on following line - Place statement on following line + Colocar a instrução na linha seguinte @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Remover a linha em branco entre chaves diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf index 5329ac975512c..acded83acd76e 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Поместите двоеточие на следующей строке. Place statement on following line - Place statement on following line + Размещайте оператор на отдельной строке @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Удалить пустую строку между скобками diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf index 7d48b87030d99..fa07a900703e1 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + Aşağıdaki satıra iki noktayı yerleştirin Place statement on following line - Place statement on following line + Aşağıdaki satıra ifadeyi yerleştirin @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + Küme ayraçları arasındaki boş satırı kaldır diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf index 2e3a0d262afd6..4de0856186c4a 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + 将冒号另起一行 Place statement on following line - Place statement on following line + 将语句另起一行 @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + 删除括号之间的空白行 diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf index ee1b0fbb28ad3..c738bff6fe5e0 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -19,12 +19,12 @@ Place colon on following line - Place colon on following line + 將冒號放在下一行 Place statement on following line - Place statement on following line + 將陳述式放在下一行 @@ -34,7 +34,7 @@ Remove blank line between braces - Remove blank line between braces + 移除大括弧之間的空白行 diff --git a/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs b/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs index 8a10be0d1e6f0..e99a8daa52c2b 100644 --- a/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs +++ b/src/Analyzers/CSharp/Tests/AddAccessibilityModifiers/AddAccessibilityModifiersTests.cs @@ -197,6 +197,32 @@ public sealed class IsExternalInit await test.RunAsync(); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)] + public async Task TestRecordStructs() + { + var source = @" +record struct [|Record|] +{ + int [|field|]; +} +"; + var fixedSource = @" +internal record struct Record +{ + private int field; +} +"; + + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = fixedSource, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.Preview, + }; + + await test.RunAsync(); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)] public async Task TestReadOnlyStructs() { diff --git a/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs b/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs index 1b204c7250b39..d60e131fb31e3 100644 --- a/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs +++ b/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs @@ -759,6 +759,37 @@ static int GetValue(int input) }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + [WorkItem(52258, "https://github.com/dotnet/roslyn/issues/52258")] + public async Task TestTrivia_03() + { + await VerifyCS.VerifyCodeFixAsync( +@"class Program +{ + int M(int i) + { + [|switch|] (i) + { // Tip-toe through the trailing trivia + case 0: return 123; + case 1: return 234; + default: throw null; + } + } +}", +@"class Program +{ + int M(int i) + { + return i switch + { // Tip-toe through the trailing trivia + 0 => 123, + 1 => 234, + _ => throw null, + }; + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] [WorkItem(36086, "https://github.com/dotnet/roslyn/issues/36086")] public async Task TestSeverity() diff --git a/src/Analyzers/CSharp/Tests/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessTests.cs b/src/Analyzers/CSharp/Tests/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessTests.cs index 76e8bde43644e..d0ca59f4d1299 100644 --- a/src/Analyzers/CSharp/Tests/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessTests.cs +++ b/src/Analyzers/CSharp/Tests/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessTests.cs @@ -501,7 +501,7 @@ void Goo() [||]var v = a; if (v != null) { - v(); + v(); // Comment2 } } }", @@ -511,7 +511,7 @@ void Goo() void Goo() { // Comment - a?.Invoke(); + a?.Invoke(); // Comment2 } }"); } @@ -528,7 +528,7 @@ void Goo() // Comment [||]if (a != null) { - a(); + a(); // Comment2 } } }", @@ -538,7 +538,60 @@ void Goo() void Goo() { // Comment - a?.Invoke(); + a?.Invoke(); // Comment2 + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvokeDelegateWithConditionalAccess)] + [WorkItem(51563, "https://github.com/dotnet/roslyn/issues/51563")] + public async Task TestTrivia3() + { + await TestInRegularAndScript1Async( +@"class C +{ + System.Action a; + void Goo() + { + // Comment + [||]var v = a; + if (v != null) { v(); /* 123 */ } // trails + System.Console.WriteLine(); + } +}", +@"class C +{ + System.Action a; + void Goo() + { + // Comment + a?.Invoke(); /* 123 */ // trails + System.Console.WriteLine(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvokeDelegateWithConditionalAccess)] + [WorkItem(51563, "https://github.com/dotnet/roslyn/issues/51563")] + public async Task TestTrivia4() + { + await TestInRegularAndScript1Async( +@"class C +{ + System.Action a; + void Goo() + { + [||]if (a != null) { a(); /* 123 */ } // trails + System.Console.WriteLine(); + } +}", +@"class C +{ + System.Action a; + void Goo() + { + a?.Invoke(); /* 123 */ // trails + System.Console.WriteLine(); } }"); } diff --git a/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs b/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs index 58a279a732dec..811f51493f746 100644 --- a/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs @@ -1746,5 +1746,38 @@ class Program private static object [|t_obj|]; }"); } + + [WorkItem(50925, "https://github.com/dotnet/roslyn/issues/50925")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task Test_MemberUsedInGeneratedCode() + { + await TestMissingInRegularAndScriptAsync( +@" + + +public sealed partial class Test +{ + private int [|_value|]; + + public static void M() + => _ = new Test { Value = 1 }; +} + + +using System.CodeDom.Compiler; + +[GeneratedCode(null, null)] +public sealed partial class Test +{ + public int Value + { + get => _value; + set => _value = value; + } +} + + +"); + } } } diff --git a/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs b/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs index 647266a5b938f..b39fd72a189f6 100644 --- a/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs +++ b/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs @@ -1383,6 +1383,33 @@ class C : I }", new TestParameters(options: s_options.PropertyNamesArePascalCase)); } + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + [WorkItem(50734, "https://github.com/dotnet/roslyn/issues/50734")] + public async Task TestAsyncEntryPoint() + { + await TestMissingInRegularAndScriptAsync(@" +using System.Threading.Tasks; + +class C +{ + static async Task [|Main|]() + { + await Task.Delay(0); + } +}", new TestParameters(options: s_options.AsyncFunctionNamesEndWithAsync)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + [WorkItem(49648, "https://github.com/dotnet/roslyn/issues/49648")] + public async Task TestAsyncEntryPoint_TopLevel() + { + await TestMissingInRegularAndScriptAsync(@" +using System.Threading.Tasks; + +[|await Task.Delay(0);|] +", new TestParameters(options: s_options.AsyncFunctionNamesEndWithAsync)); + } + [Fact] [WorkItem(51727, "https://github.com/dotnet/roslyn/issues/51727")] public async Task TestExternAsync() diff --git a/src/Analyzers/CSharp/Tests/OrderModifiers/OrderModifiersTests.cs b/src/Analyzers/CSharp/Tests/OrderModifiers/OrderModifiersTests.cs index 8a5ec8d4badda..d9ea306089169 100644 --- a/src/Analyzers/CSharp/Tests/OrderModifiers/OrderModifiersTests.cs +++ b/src/Analyzers/CSharp/Tests/OrderModifiers/OrderModifiersTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -374,6 +375,21 @@ await TestInRegularAndScript1Async( @"partial class C { unsafe partial void M(); +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsOrderModifiers)] + [WorkItem(52297, "https://github.com/dotnet/roslyn/pull/52297")] + public async Task TestInLocalFunction() + { + // Not handled for performance reason. + await TestMissingInRegularAndScriptAsync( +@"class C +{ + public static async void M() + { + [|async|] static void Local() { } + } }"); } } diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs index 0dbe9c592c6a7..88907e8d865c7 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs @@ -2626,5 +2626,53 @@ void M(object o) } }", offeredWhenRequireForClarityIsEnabled: true); } + + [WorkItem(50025, "https://github.com/dotnet/roslyn/issues/50025")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)] + public async Task TestDoNotRemoveWithConstantAndTypeAmbiguity() + { + await TestMissingAsync( +@" +public class C +{ + public const int Goo = 1; + + public void M(Goo o) + { + if (o is $$(Goo)) M(1); + } +} + +public class Goo { }"); + } + + [WorkItem(50025, "https://github.com/dotnet/roslyn/issues/50025")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)] + public async Task TestDoRemoveWithNoConstantAndTypeAmbiguity() + { + await TestAsync( +@" +public class C +{ + public const int Goo = 1; + + public void M(object o) + { + if (o is $$(Goo)) M(1); + } +} +", +@" +public class C +{ + public const int Goo = 1; + + public void M(object o) + { + if (o is Goo) M(1); + } +} +", offeredWhenRequireForClarityIsEnabled: true); + } } } diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryPatternParenthesesTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryPatternParenthesesTests.cs index a987189f1df2c..da95513ac8c7c 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryPatternParenthesesTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryPatternParenthesesTests.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -250,5 +251,48 @@ void M(object o) } }", offeredWhenRequireForClarityIsEnabled: false); } + + [WorkItem(52589, "https://github.com/dotnet/roslyn/issues/52589")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)] + public async Task TestAlwaysNecessaryForDiscard() + { + await TestDiagnosticMissingAsync( +@" +class C +{ + void M(object o) + { + if (o is $$(_)) + { + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)] + public async Task TestUnnecessaryForDiscardInSubpattern() + { + await TestAsync( +@" +class C +{ + void M(object o) + { + if (o is string { Length: $$(_) }) + { + } + } +}", +@" +class C +{ + void M(object o) + { + if (o is string { Length: _ }) + { + } + } +}", offeredWhenRequireForClarityIsEnabled: true); + } } } diff --git a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs index 525a3400bc4b7..2ba5d60c76a3e 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs @@ -1580,6 +1580,38 @@ public async Task RecordPrimaryConstructorParameter_PublicRecord() await TestDiagnosticMissingAsync( @"public record Base(int I) { } public record Derived(string [|S|]) : Base(42) { } +"); + } + + [WorkItem(45743, "https://github.com/dotnet/roslyn/issues/45743")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedParameters)] + public async Task RequiredGetInstanceMethodByICustomMarshaler() + { + await TestDiagnosticMissingAsync(@" +using System; +using System.Runtime.InteropServices; + + +public class C : ICustomMarshaler +{ + public void CleanUpManagedData(object ManagedObj) + => throw new NotImplementedException(); + + public void CleanUpNativeData(IntPtr pNativeData) + => throw new NotImplementedException(); + + public int GetNativeDataSize() + => throw new NotImplementedException(); + + public IntPtr MarshalManagedToNative(object ManagedObj) + => throw new NotImplementedException(); + + public object MarshalNativeToManaged(IntPtr pNativeData) + => throw new NotImplementedException(); + + public static ICustomMarshaler GetInstance(string [|s|]) + => null; +} "); } } diff --git a/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs b/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs index c57ba2958339c..a4d4186aba205 100644 --- a/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs +++ b/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs @@ -214,6 +214,114 @@ void M(System.DateTime someValue) }"); } + [Fact] + public async Task ToStringWithInvariantCultureInsideFormattableStringInvariant() + { + // Invariance remains explicit, so this is okay. + + await TestInRegularAndScript1Async( +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(System.Globalization.CultureInfo.InvariantCulture)|}} suffix""); + } +}", +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue} suffix""); + } +}"); + } + + [Fact] + public async Task DateTimeFormatInfoInvariantInfoIsRecognized() + { + await TestInRegularAndScript1Async( +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(System.Globalization.DateTimeFormatInfo.InvariantInfo)|}} suffix""); + } +}", +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue} suffix""); + } +}"); + } + + [Fact] + public async Task NumberFormatInfoInvariantInfoIsRecognized() + { + await TestInRegularAndScript1Async( +@"class C +{ + void M(int someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(System.Globalization.NumberFormatInfo.InvariantInfo)|}} suffix""); + } +}", +@"class C +{ + void M(int someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue} suffix""); + } +}"); + } + + [Fact] + public async Task ToStringWithInvariantCultureOutsideFormattableStringInvariant() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void M(System.DateTime someValue) + { + _ = $""prefix {someValue[||].ToString(System.Globalization.CultureInfo.InvariantCulture)} suffix""; + } +}"); + } + + [Fact] + public async Task ToStringWithFormatAndInvariantCultureInsideFormattableStringInvariant() + { + await TestInRegularAndScript1Async( +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(""|}some format code{|Unnecessary:"", System.Globalization.CultureInfo.InvariantCulture)|}} suffix""); + } +}", +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue:some format code} suffix""); + } +}"); + } + + [Fact] + public async Task ToStringWithFormatAndInvariantCultureOutsideFormattableStringInvariant() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void M(System.DateTime someValue) + { + _ = $""prefix {someValue[||].ToString(""some format code"", System.Globalization.CultureInfo.InvariantCulture)} suffix""; + } +}"); + } + [Fact] public async Task PadLeftWithIntegerLiteral() { diff --git a/src/Analyzers/CSharp/Tests/UseCoalesceExpression/UseCoalesceExpressionTests.cs b/src/Analyzers/CSharp/Tests/UseCoalesceExpression/UseCoalesceExpressionTests.cs index f612891b1f3e6..83f704189ff29 100644 --- a/src/Analyzers/CSharp/Tests/UseCoalesceExpression/UseCoalesceExpressionTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCoalesceExpression/UseCoalesceExpressionTests.cs @@ -579,6 +579,27 @@ void M(string s) ? s ?? """" : """"; } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)] + [WorkItem(53190, "https://github.com/dotnet/roslyn/issues/53190")] + public async Task TestNotWithTargetTyping() + { + await TestMissingAsync( +@" +class Program +{ + class A { } + class B { } + + static void Main(string[] args) + { + var a = new A(); + var b = new B(); + + object x = [||]a != null ? a : b; + } }"); } } diff --git a/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForAccessorsAnalyzerTests.cs b/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForAccessorsAnalyzerTests.cs index ce756da2279f3..b567c8841a246 100644 --- a/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForAccessorsAnalyzerTests.cs +++ b/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForAccessorsAnalyzerTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; using Xunit; @@ -24,6 +25,7 @@ private static async Task TestWithUseExpressionBody(string code, string fixedCod { await new VerifyCS.Test { + ReferenceAssemblies = version == LanguageVersion.CSharp9 ? ReferenceAssemblies.Net.Net50 : ReferenceAssemblies.Default, TestCode = code, FixedCode = fixedCode, LanguageVersion = version, @@ -36,12 +38,14 @@ private static async Task TestWithUseExpressionBody(string code, string fixedCod }.RunAsync(); } - private static async Task TestWithUseExpressionBodyIncludingPropertiesAndIndexers(string code, string fixedCode) + private static async Task TestWithUseExpressionBodyIncludingPropertiesAndIndexers(string code, string fixedCode, LanguageVersion version = LanguageVersion.CSharp8) { await new VerifyCS.Test { + ReferenceAssemblies = version == LanguageVersion.CSharp9 ? ReferenceAssemblies.Net.Net50 : ReferenceAssemblies.Default, TestCode = code, FixedCode = fixedCode, + LanguageVersion = version, Options = { { CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, ExpressionBodyPreference.WhenPossible }, @@ -51,12 +55,14 @@ private static async Task TestWithUseExpressionBodyIncludingPropertiesAndIndexer }.RunAsync(); } - private static async Task TestWithUseBlockBodyIncludingPropertiesAndIndexers(string code, string fixedCode) + private static async Task TestWithUseBlockBodyIncludingPropertiesAndIndexers(string code, string fixedCode, LanguageVersion version = LanguageVersion.CSharp8) { await new VerifyCS.Test { + ReferenceAssemblies = version == LanguageVersion.CSharp9 ? ReferenceAssemblies.Net.Net50 : ReferenceAssemblies.Default, TestCode = code, FixedCode = fixedCode, + LanguageVersion = version, Options = { { CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, ExpressionBodyPreference.Never }, @@ -207,6 +213,35 @@ int Goo await TestWithUseExpressionBody(code, fixedCode); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestOnInit1() + { + var code = +@"class C +{ + int Goo + { + {|IDE0027:init + { + Bar(); + }|} + } + + int Bar() { return 0; } +}"; + var fixedCode = +@"class C +{ + int Goo + { + init => Bar(); + } + + int Bar() { return 0; } +}"; + await TestWithUseExpressionBody(code, fixedCode, LanguageVersion.CSharp9); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public async Task TestMissingWithOnlySetter() { @@ -222,6 +257,28 @@ int Goo }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestMissingWithOnlyInit() + { + var code = +@"class C +{ + int Goo + { + init => Bar(); + } + + int Bar() { return 0; } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = code, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public async Task TestUseExpressionBody3() { @@ -338,6 +395,36 @@ int Goo await TestWithUseBlockBodyIncludingPropertiesAndIndexers(code, fixedCode); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestUseBlockBodyForInit1() + { + var code = +@"class C +{ + int Goo + { + {|IDE0027:init => Bar();|} + } + + int Bar() { return 0; } + }"; + var fixedCode = +@"class C +{ + int Goo + { + init + { + Bar(); + } + } + + int Bar() { return 0; } + }"; + + await TestWithUseBlockBodyIncludingPropertiesAndIndexers(code, fixedCode, LanguageVersion.CSharp9); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public async Task TestUseBlockBody3() { @@ -505,6 +592,66 @@ int Goo }.RunAsync(); } + [WorkItem(20350, "https://github.com/dotnet/roslyn/issues/20350")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] + public async Task TestAccessorListFormatting_FixAll2() + { + var code = +@"class C +{ + int Goo { {|IDE0027:get => Bar();|} {|IDE0027:init => Bar();|} } + + int Bar() { return 0; } +}"; + var fixedCode = +@"class C +{ + int Goo + { + get { return Bar(); } + init + { + Bar(); + } + } + + int Bar() { return 0; } +}"; + var batchFixedCode = +@"class C +{ + int Goo + { + get + { + return Bar(); + } + + init + { + Bar(); + } + } + + int Bar() { return 0; } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = code, + FixedCode = fixedCode, + BatchFixedCode = batchFixedCode, + LanguageVersion = LanguageVersion.CSharp9, + Options = + { + { CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, ExpressionBodyPreference.Never }, + { CSharpCodeStyleOptions.PreferExpressionBodiedProperties, ExpressionBodyPreference.Never }, + { CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, ExpressionBodyPreference.Never }, + } + }.RunAsync(); + } + [WorkItem(20362, "https://github.com/dotnet/roslyn/issues/20362")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public async Task TestOfferToConvertToBlockEvenIfExpressionBodyPreferredIfPriorToCSharp7() diff --git a/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs b/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs index b641636d0451d..e704178d92377 100644 --- a/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs +++ b/src/Analyzers/CSharp/Tests/UseSimpleUsingStatement/UseSimpleUsingStatementTests.cs @@ -1496,6 +1496,149 @@ void M() // ...transformation } }", +parseOptions: CSharp8ParseOptions); + } + + [WorkItem(52970, "https://github.com/dotnet/roslyn/issues/52970")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseSimpleUsingStatement)] + public async Task TestWithBlockBodyWithOpeningBracketOnSameLine() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void M() + { + [||]using (var a = b){ + Console.WriteLine(a); + } + } +}", +@"using System; + +class C +{ + void M() + { + using var a = b; + Console.WriteLine(a); + } +}", +parseOptions: CSharp8ParseOptions); + } + + [WorkItem(52970, "https://github.com/dotnet/roslyn/issues/52970")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseSimpleUsingStatement)] + public async Task TestWithBlockBodyWithOpeningBracketOnSameLine2() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void M() + { + [||]using (var a = b) { + Console.WriteLine(a); + } + } +}", +@"using System; + +class C +{ + void M() + { + using var a = b; + Console.WriteLine(a); + } +}", +parseOptions: CSharp8ParseOptions); + } + + [WorkItem(52970, "https://github.com/dotnet/roslyn/issues/52970")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseSimpleUsingStatement)] + public async Task TestWithBlockBodyWithOpeningBracketAndCommentOnSameLine() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void M() + { + [||]using (var a = b) { //comment + Console.WriteLine(a); + } + } +}", +@"using System; + +class C +{ + void M() + { + using var a = b; //comment + Console.WriteLine(a); + } +}", +parseOptions: CSharp8ParseOptions); + } + + [WorkItem(52970, "https://github.com/dotnet/roslyn/issues/52970")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseSimpleUsingStatement)] + public async Task TestWithBlockBodyWithOpeningBracketOnSameLineWithNoStatements() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void M() + { + [||]using (var a = b) { + } + } +}", +@"using System; + +class C +{ + void M() + { + using var a = b; + } +}", +parseOptions: CSharp8ParseOptions); + } + + [WorkItem(52970, "https://github.com/dotnet/roslyn/issues/52970")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseSimpleUsingStatement)] + public async Task TestWithBlockBodyWithOpeningBracketOnSameLineAndCommentInBlock() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void M() + { + [||]using (var a = b) { + // intentionally empty + } + } +}", +@"using System; + +class C +{ + void M() + { + using var a = b; + // intentionally empty + } +}", parseOptions: CSharp8ParseOptions); } } diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs index 5793dc9e32a43..8d0a0b3688a4f 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs @@ -65,10 +65,16 @@ protected static DiagnosticDescriptor CreateDescriptorWithId( customTags: DiagnosticCustomTags.Create(isUnnecessary, isConfigurable, enforceOnBuild)); #pragma warning restore RS0030 // Do not used banned APIs + /// + /// Flag indicating whether or not analyzer should receive analysis callbacks for generated code. + /// By default, code style analyzers should not run on generated code, so the value is false. + /// + protected virtual bool ReceiveAnalysisCallbacksForGeneratedCode => false; + public sealed override void Initialize(AnalysisContext context) { - // Code style analyzers should not run on generated code. - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + var flags = ReceiveAnalysisCallbacksForGeneratedCode ? GeneratedCodeAnalysisFlags.Analyze : GeneratedCodeAnalysisFlags.None; + context.ConfigureGeneratedCodeAnalysis(flags); context.EnableConcurrentExecution(); InitializeWorker(context); diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems index 2eef2fe530149..010bb8993e949 100644 --- a/src/Analyzers/Core/Analyzers/Analyzers.projitems +++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems @@ -64,7 +64,7 @@ - + diff --git a/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs index 5e35fd21c8ac6..f22cd988815cd 100644 --- a/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs @@ -63,7 +63,7 @@ private static bool IsValidOperation(IOperation operation) { // Cast to a typeof operation & check parent is a property reference and member access var typeofOperation = (ITypeOfOperation)operation; - if (!(operation.Parent is IPropertyReferenceOperation)) + if (operation.Parent is not IPropertyReferenceOperation) { return false; } @@ -71,7 +71,7 @@ private static bool IsValidOperation(IOperation operation) // Check Parent is a .Name access var operationParent = (IPropertyReferenceOperation)operation.Parent; var parentProperty = operationParent.Property.Name; - if (parentProperty != nameof(System.Type.Name) && parentProperty != "Name") + if (parentProperty is not nameof(System.Type.Name)) { return false; } diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 744e864cbdfe2..6a72420de1367 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -75,7 +75,8 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild RemoveUnnecessaryDiscardDesignation = /*IDE0110*/ EnforceOnBuild.Recommended; public const EnforceOnBuild InvokeDelegateWithConditionalAccess = /*IDE1005*/ EnforceOnBuild.Recommended; public const EnforceOnBuild NamingRule = /*IDE1006*/ EnforceOnBuild.Recommended; - public const EnforceOnBuild MatchFolderAndNamespace = /* IDE0130*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild MatchFolderAndNamespace = /*IDE0130*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild SimplifyObjectCreation = /*IDE0140*/ EnforceOnBuild.Recommended; /* EnforceOnBuild.WhenExplicitlyEnabled */ public const EnforceOnBuild RemoveUnnecessaryCast = /*IDE0004*/ EnforceOnBuild.WhenExplicitlyEnabled; // TODO: Move to 'Recommended' OR 'HighlyRecommended' bucket once performance problems are addressed: https://github.com/dotnet/roslyn/issues/43304 diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index fedbf3b9edc06..aefae585b505f 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -158,6 +158,8 @@ internal static class IDEDiagnosticIds public const string MatchFolderAndNamespaceDiagnosticId = "IDE0130"; + public const string SimplifyObjectCreationDiagnosticId = "IDE0140"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; @@ -166,7 +168,6 @@ internal static class IDEDiagnosticIds public const string InvokeDelegateWithConditionalAccessId = "IDE1005"; public const string NamingRuleId = "IDE1006"; public const string UnboundIdentifierId = "IDE1007"; - public const string UnboundConstructorId = "IDE1008"; // Reserved for workspace error ids IDE1100-IDE1200 (see WorkspaceDiagnosticDescriptors) diff --git a/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs index 273404db77b94..af266f53e39ad 100644 --- a/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs @@ -31,6 +31,9 @@ public MakeFieldReadonlyDiagnosticAnalyzer() public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + // We need to analyze generated code to get callbacks for read/writes to non-generated members in generated code. + protected override bool ReceiveAnalysisCallbacksForGeneratedCode => true; + protected override void InitializeWorker(AnalysisContext context) { context.RegisterCompilationStartAction(compilationStartContext => diff --git a/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs b/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs index dec3fe4ef8f1b..3a6e82d88639d 100644 --- a/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs +++ b/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs @@ -113,6 +113,11 @@ void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) return null; } + if (symbol is IMethodSymbol methodSymbol && methodSymbol.IsEntryPoint(compilation.TaskType(), compilation.TaskOfTType())) + { + return null; + } + if (ShouldIgnore(symbol)) { return null; diff --git a/src/Analyzers/Core/Analyzers/PopulateSwitch/AbstractPopulateSwitchDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/PopulateSwitch/AbstractPopulateSwitchDiagnosticAnalyzer.cs index cc9868eb0b8db..b4394cd13251a 100644 --- a/src/Analyzers/Core/Analyzers/PopulateSwitch/AbstractPopulateSwitchDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/PopulateSwitch/AbstractPopulateSwitchDiagnosticAnalyzer.cs @@ -43,7 +43,7 @@ protected sealed override void InitializeWorker(AnalysisContext context) private void AnalyzeOperation(OperationAnalysisContext context) { var switchOperation = (TSwitchOperation)context.Operation; - if (!(switchOperation.Syntax is TSwitchSyntax switchBlock)) + if (switchOperation.Syntax is not TSwitchSyntax switchBlock) return; var tree = switchBlock.SyntaxTree; diff --git a/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs b/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs index 96d43b44f5239..27fd1737d8ea6 100644 --- a/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs +++ b/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs @@ -113,7 +113,7 @@ public static bool TryGetAllEnumMembers( foreach (var member in enumType.GetMembers()) { // skip `.ctor` and `__value` - if (!(member is IFieldSymbol fieldSymbol) || fieldSymbol.Type.SpecialType != SpecialType.None) + if (member is not IFieldSymbol fieldSymbol || fieldSymbol.Type.SpecialType != SpecialType.None) { continue; } diff --git a/src/Analyzers/Core/Analyzers/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs index 630550c07edf0..74c175fc6bcdf 100644 --- a/src/Analyzers/Core/Analyzers/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs @@ -128,7 +128,7 @@ private void AnalyzeOperation(OperationAnalysisContext context, IOperation opera return; } - if (!(instanceOperation.Syntax is TSimpleNameSyntax simpleName)) + if (instanceOperation.Syntax is not TSimpleNameSyntax simpleName) { return; } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs index eec26687ad09c..9179b2d43cc7b 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs @@ -40,14 +40,15 @@ protected sealed override void InitializeWorker(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntax, GetSyntaxKind()); protected abstract bool CanRemoveParentheses( - TParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, + TParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, CancellationToken cancellationToken, out PrecedenceKind precedence, out bool clarifiesPrecedence); private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { + var cancellationToken = context.CancellationToken; var parenthesizedExpression = (TParenthesizedExpressionSyntax)context.Node; - if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel, + if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel, cancellationToken, out var precedence, out var clarifiesPrecedence)) { return; @@ -112,7 +113,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags( Descriptor, - GetDiagnosticSquiggleLocation(parenthesizedExpression, context.CancellationToken), + AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken), severity, additionalLocations, additionalUnnecessaryLocations)); diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs index 3d3661ab75e23..791c62728bf4a 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs @@ -633,7 +633,7 @@ private bool IsCandidateSymbol(ISymbol memberSymbol) } // Do not flag unused entry point (Main) method. - if (IsEntryPoint(methodSymbol)) + if (methodSymbol.IsEntryPoint(_taskType, _genericTaskType)) { return false; } @@ -699,14 +699,6 @@ private bool IsCandidateSymbol(ISymbol memberSymbol) return false; } - private bool IsEntryPoint(IMethodSymbol methodSymbol) - => methodSymbol.Name is WellKnownMemberNames.EntryPointMethodName or WellKnownMemberNames.TopLevelStatementsEntryPointMethodName && - methodSymbol.IsStatic && - (methodSymbol.ReturnsVoid || - methodSymbol.ReturnType.SpecialType == SpecialType.System_Int32 || - methodSymbol.ReturnType.OriginalDefinition.Equals(_taskType) || - methodSymbol.ReturnType.OriginalDefinition.Equals(_genericTaskType)); - private bool IsMethodWithSpecialAttribute(IMethodSymbol methodSymbol) => methodSymbol.GetAttributes().Any(a => _attributeSetForMethodsToIgnore.Contains(a.AttributeClass)); diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs index c025a67408c0e..25c131192aa8c 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs @@ -140,7 +140,7 @@ static bool IsSingleThrowNotImplementedOperation(IOperation firstBlock) if (notImplementedExceptionType == null) return false; - if (!(firstBlock is IBlockOperation block)) + if (firstBlock is not IBlockOperation block) return false; if (block.Operations.Length == 0) @@ -224,8 +224,8 @@ private void AnalyzeExpressionStatement(OperationAnalysisContext context) } // 4. Assignments, increment/decrement operations: value is actually being assigned. - if (value is IAssignmentOperation || - value is IIncrementOrDecrementOperation) + if (value is IAssignmentOperation or + IIncrementOrDecrementOperation) { return; } @@ -284,7 +284,7 @@ private void AnalyzeLocalOrParameterReference(OperationAnalysisContext operation /// private static bool IsHandledDelegateCreationOrAnonymousFunctionTreeShape(IOperation operation) { - Debug.Assert(operation.Kind == OperationKind.DelegateCreation || operation.Kind == OperationKind.AnonymousFunction); + Debug.Assert(operation.Kind is OperationKind.DelegateCreation or OperationKind.AnonymousFunction); // 1. Delegate creation or anonymous function variable initializer is handled. // For example, for 'Action a = () => { ... };', the lambda is the variable initializer @@ -333,7 +333,7 @@ private static bool IsHandledDelegateCreationOrAnonymousFunctionTreeShape(IOpera /// private static bool IsHandledLocalOrParameterReferenceTreeShape(IOperation operation) { - Debug.Assert(operation.Kind == OperationKind.LocalReference || operation.Kind == OperationKind.ParameterReference); + Debug.Assert(operation.Kind is OperationKind.LocalReference or OperationKind.ParameterReference); // 1. We are only interested in parameters or locals of delegate type. if (!operation.Type.IsDelegateType()) @@ -615,7 +615,7 @@ bool ShouldReportUnusedValueDiagnostic( SymbolUsageResult resultFromFlowAnalysis, out ImmutableDictionary? properties) { - Debug.Assert(!(symbol is ILocalSymbol local) || !local.IsRef); + Debug.Assert(symbol is not ILocalSymbol local || !local.IsRef); properties = null; @@ -735,7 +735,7 @@ private void AnalyzeUnusedParameters( } // 2. Report unused parameters only for method symbols. - if (!(context.OwningSymbol is IMethodSymbol method)) + if (context.OwningSymbol is not IMethodSymbol method) { return; } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.cs index 486f1d1e1bee9..c67a79a71a8e4 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; @@ -29,6 +30,7 @@ private sealed partial class SymbolStartAnalyzer private readonly ImmutableHashSet _attributeSetForMethodsToIgnore; private readonly DeserializationConstructorCheck _deserializationConstructorCheck; private readonly ConcurrentDictionary _methodsUsedAsDelegates; + private readonly INamedTypeSymbol _iCustomMarshaler; /// /// Map from unused parameters to a boolean value indicating if the parameter has a read reference or not. @@ -41,7 +43,8 @@ public SymbolStartAnalyzer( AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer compilationAnalyzer, INamedTypeSymbol eventArgsTypeOpt, ImmutableHashSet attributeSetForMethodsToIgnore, - DeserializationConstructorCheck deserializationConstructorCheck) + DeserializationConstructorCheck deserializationConstructorCheck, + INamedTypeSymbol iCustomMarshaler) { _compilationAnalyzer = compilationAnalyzer; @@ -50,6 +53,7 @@ public SymbolStartAnalyzer( _deserializationConstructorCheck = deserializationConstructorCheck; _unusedParameters = new ConcurrentDictionary(); _methodsUsedAsDelegates = new ConcurrentDictionary(); + _iCustomMarshaler = iCustomMarshaler; } public static void CreateAndRegisterActions( @@ -59,6 +63,8 @@ public static void CreateAndRegisterActions( var attributeSetForMethodsToIgnore = ImmutableHashSet.CreateRange(GetAttributesForMethodsToIgnore(context.Compilation).WhereNotNull()); var eventsArgType = context.Compilation.EventArgsType(); var deserializationConstructorCheck = new DeserializationConstructorCheck(context.Compilation); + var iCustomMarshaler = context.Compilation.GetTypeByMetadataName(typeof(ICustomMarshaler).FullName!); + context.RegisterSymbolStartAction(symbolStartContext => { if (HasSyntaxErrors((INamedTypeSymbol)symbolStartContext.Symbol, symbolStartContext.CancellationToken)) @@ -71,7 +77,7 @@ public static void CreateAndRegisterActions( // to ensure there is no shared state (such as identified unused parameters within the type), // as that would lead to duplicate diagnostics being reported from symbol end action callbacks // for unrelated named types. - var symbolAnalyzer = new SymbolStartAnalyzer(analyzer, eventsArgType, attributeSetForMethodsToIgnore, deserializationConstructorCheck); + var symbolAnalyzer = new SymbolStartAnalyzer(analyzer, eventsArgType, attributeSetForMethodsToIgnore, deserializationConstructorCheck, iCustomMarshaler); symbolAnalyzer.OnSymbolStart(symbolStartContext); }, SymbolKind.NamedType); @@ -205,7 +211,7 @@ private bool IsUnusedParameterCandidate(IParameterSymbol parameter) if (parameter.IsImplicitlyDeclared || parameter.Name == DiscardVariableName || - !(parameter.ContainingSymbol is IMethodSymbol method) || + parameter.ContainingSymbol is not IMethodSymbol method || method.IsImplicitlyDeclared || method.IsExtern || method.IsAbstract || @@ -264,6 +270,15 @@ private bool IsUnusedParameterCandidate(IParameterSymbol parameter) return false; } + // Don't report on valid GetInstance method of ICustomMarshaler. + // See https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.icustommarshaler#implementing-the-getinstance-method + if (method is { MetadataName: "GetInstance", IsStatic: true, Parameters: { Length: 1 }, ContainingType: { } containingType } methodSymbol && + methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_String && + containingType.AllInterfaces.Any((@interface, marshaler) => @interface.Equals(marshaler), _iCustomMarshaler)) + { + return false; + } + return true; } } diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs index c971b77eff266..aaedb6caad853 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs @@ -15,13 +15,9 @@ namespace Microsoft.CodeAnalysis.SimplifyInterpolation { internal abstract class AbstractSimplifyInterpolationDiagnosticAnalyzer< TInterpolationSyntax, - TExpressionSyntax, - TConditionalExpressionSyntax, - TParenthesizedExpressionSyntax> : AbstractBuiltInCodeStyleDiagnosticAnalyzer + TExpressionSyntax> : AbstractBuiltInCodeStyleDiagnosticAnalyzer where TInterpolationSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax { protected AbstractSimplifyInterpolationDiagnosticAnalyzer() : base(IDEDiagnosticIds.SimplifyInterpolationId, @@ -37,6 +33,8 @@ protected AbstractSimplifyInterpolationDiagnosticAnalyzer() protected abstract ISyntaxFacts GetSyntaxFacts(); + protected abstract AbstractSimplifyInterpolationHelpers GetHelpers(); + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; @@ -63,7 +61,7 @@ private void AnalyzeInterpolation(OperationAnalysisContext context) return; } - Helpers.UnwrapInterpolation( + GetHelpers().UnwrapInterpolation( GetVirtualCharService(), GetSyntaxFacts(), interpolation, out _, out var alignment, out _, out var formatString, out var unnecessaryLocations); diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs new file mode 100644 index 0000000000000..e209bd36a6a9f --- /dev/null +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs @@ -0,0 +1,252 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.SimplifyInterpolation +{ + internal abstract class AbstractSimplifyInterpolationHelpers + { + protected abstract bool PermitNonLiteralAlignmentComponents { get; } + + protected abstract SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation); + + public void UnwrapInterpolation( + IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IInterpolationOperation interpolation, + out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, out bool negate, + out string? formatString, out ImmutableArray unnecessaryLocations) + where TInterpolationSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode + { + alignment = null; + negate = false; + formatString = null; + + var unnecessarySpans = new List(); + + var expression = Unwrap(interpolation.Expression); + if (interpolation.Alignment == null) + { + UnwrapAlignmentPadding(expression, out expression, out alignment, out negate, unnecessarySpans); + } + + if (interpolation.FormatString == null) + { + UnwrapFormatString(virtualCharService, syntaxFacts, expression, out expression, out formatString, unnecessarySpans); + } + + unwrapped = GetPreservedInterpolationExpressionSyntax(expression) as TExpressionSyntax; + + unnecessaryLocations = + unnecessarySpans.OrderBy(t => t.Start) + .SelectAsArray(interpolation.Syntax.SyntaxTree.GetLocation); + } + + [return: NotNullIfNotNull("expression")] + private static IOperation? Unwrap(IOperation? expression, bool towardsParent = false) + { + while (true) + { + if (towardsParent && expression?.Parent is null) + return expression; + + switch (expression) + { + case IParenthesizedOperation parenthesized: + expression = towardsParent ? expression.Parent : parenthesized.Operand; + continue; + case IConversionOperation { IsImplicit: true } conversion: + expression = towardsParent ? expression.Parent : conversion.Operand; + continue; + default: + return expression; + } + } + } + + private void UnwrapFormatString( + IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IOperation expression, out IOperation unwrapped, + out string? formatString, List unnecessarySpans) + { + Contract.ThrowIfNull(expression.SemanticModel); + + if (expression is IInvocationOperation { TargetMethod: { Name: nameof(ToString) } } invocation && + HasNonImplicitInstance(invocation) && + !syntaxFacts.IsBaseExpression(invocation.Instance!.Syntax) && + !invocation.Instance.Type!.IsRefLikeType) + { + if (invocation.Arguments.Length == 1 + || (invocation.Arguments.Length == 2 && UsesInvariantCultureReferenceInsideFormattableStringInvariant(invocation, formatProviderArgumentIndex: 1))) + { + if (invocation.Arguments[0].Value is ILiteralOperation { ConstantValue: { HasValue: true, Value: string value } } literal && + FindType(expression.SemanticModel) is { } systemIFormattable && + invocation.Instance.Type.Implements(systemIFormattable)) + { + unwrapped = invocation.Instance; + formatString = value; + + unnecessarySpans.AddRange(invocation.Syntax.Span + .Subtract(GetPreservedInterpolationExpressionSyntax(invocation.Instance).FullSpan) + .Subtract(GetSpanWithinLiteralQuotes(virtualCharService, literal.Syntax.GetFirstToken()))); + return; + } + } + + if (IsObjectToStringOverride(invocation.TargetMethod) + || (invocation.Arguments.Length == 1 && UsesInvariantCultureReferenceInsideFormattableStringInvariant(invocation, formatProviderArgumentIndex: 0))) + { + // A call to `.ToString()` at the end of the interpolation. This is unnecessary. + // Just remove entirely. + unwrapped = invocation.Instance; + formatString = ""; + + unnecessarySpans.AddRange(invocation.Syntax.Span + .Subtract(GetPreservedInterpolationExpressionSyntax(invocation.Instance).FullSpan)); + return; + } + } + + unwrapped = expression; + formatString = null; + } + + private static bool IsObjectToStringOverride(IMethodSymbol method) + { + while (method.OverriddenMethod is not null) + method = method.OverriddenMethod; + + return method.ContainingType.SpecialType == SpecialType.System_Object + && method.Name == nameof(ToString); + } + + private static bool UsesInvariantCultureReferenceInsideFormattableStringInvariant(IInvocationOperation invocation, int formatProviderArgumentIndex) + { + return IsInvariantCultureReference(invocation.Arguments[formatProviderArgumentIndex].Value) + && IsInsideFormattableStringInvariant(invocation); + } + + private static bool IsInvariantCultureReference(IOperation operation) + { + Contract.ThrowIfNull(operation.SemanticModel); + + if (Unwrap(operation) is IPropertyReferenceOperation { Member: { } member }) + { + if (member.Name == nameof(CultureInfo.InvariantCulture)) + { + return IsType(member.ContainingType, operation.SemanticModel); + } + + if (member.Name == "InvariantInfo") + { + return IsType(member.ContainingType, operation.SemanticModel) + || IsType(member.ContainingType, operation.SemanticModel); + } + } + + return false; + } + + private static bool IsInsideFormattableStringInvariant(IOperation operation) + { + Contract.ThrowIfNull(operation.SemanticModel); + + var interpolatedStringOperation = AncestorsAndSelf(operation).OfType().FirstOrDefault(); + + return Unwrap(interpolatedStringOperation?.Parent, towardsParent: true) is IArgumentOperation + { + Parent: IInvocationOperation + { + TargetMethod: { Name: nameof(FormattableString.Invariant), ContainingType: var containingType }, + }, + } && IsType(containingType, operation.SemanticModel); + } + + private static bool IsType(INamedTypeSymbol type, SemanticModel semanticModel) + { + return SymbolEqualityComparer.Default.Equals(type, FindType(semanticModel)); + } + + private static INamedTypeSymbol? FindType(SemanticModel semanticModel) + { + return semanticModel.Compilation.GetTypeByMetadataName(typeof(T).FullName!); + } + + private static IEnumerable AncestorsAndSelf(IOperation operation) + { + for (var current = operation; current is not null; current = current.Parent) + { + yield return current; + } + } + + private static TextSpan GetSpanWithinLiteralQuotes(IVirtualCharService virtualCharService, SyntaxToken formatToken) + { + var sequence = virtualCharService.TryConvertToVirtualChars(formatToken); + return sequence.IsDefaultOrEmpty + ? default + : TextSpan.FromBounds(sequence.First().Span.Start, sequence.Last().Span.End); + } + + private void UnwrapAlignmentPadding( + IOperation expression, out IOperation unwrapped, + out TExpressionSyntax? alignment, out bool negate, List unnecessarySpans) + where TExpressionSyntax : SyntaxNode + { + if (expression is IInvocationOperation invocation && + HasNonImplicitInstance(invocation)) + { + var targetName = invocation.TargetMethod.Name; + if (targetName is nameof(string.PadLeft) or nameof(string.PadRight)) + { + var argCount = invocation.Arguments.Length; + if (argCount is 1 or 2) + { + if (argCount == 1 || + IsSpaceChar(invocation.Arguments[1])) + { + var alignmentOp = invocation.Arguments[0].Value; + + if (PermitNonLiteralAlignmentComponents + ? alignmentOp is { ConstantValue: { HasValue: true } } + : alignmentOp is { Kind: OperationKind.Literal }) + { + var alignmentSyntax = alignmentOp.Syntax; + + unwrapped = invocation.Instance!; + alignment = alignmentSyntax as TExpressionSyntax; + negate = targetName == nameof(string.PadRight); + + unnecessarySpans.AddRange(invocation.Syntax.Span + .Subtract(GetPreservedInterpolationExpressionSyntax(invocation.Instance!).FullSpan) + .Subtract(alignmentSyntax.FullSpan)); + return; + } + } + } + } + } + + unwrapped = expression; + alignment = null; + negate = false; + } + + private static bool HasNonImplicitInstance(IInvocationOperation invocation) + => invocation.Instance != null && !invocation.Instance.IsImplicit; + + private static bool IsSpaceChar(IArgumentOperation argument) + => argument.Value.ConstantValue is { HasValue: true, Value: ' ' }; + } +} diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs deleted file mode 100644 index 9e8606d72f1fc..0000000000000 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; -using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.Operations; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.SimplifyInterpolation -{ - internal static class Helpers - { - private static SyntaxNode GetPreservedInterpolationExpressionSyntax( - IOperation operation) - where TConditionalExpressionSyntax : SyntaxNode - where TParenthesizedExpressionSyntax : SyntaxNode - { - return operation.Syntax switch - { - TConditionalExpressionSyntax { Parent: TParenthesizedExpressionSyntax parent } => parent, - var syntax => syntax, - }; - } - - public static void UnwrapInterpolation( - IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IInterpolationOperation interpolation, - out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, out bool negate, - out string? formatString, out ImmutableArray unnecessaryLocations) - where TInterpolationSyntax : SyntaxNode - where TExpressionSyntax : SyntaxNode - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax - { - alignment = null; - negate = false; - formatString = null; - - var unnecessarySpans = new List(); - - var expression = Unwrap(interpolation.Expression); - if (interpolation.Alignment == null) - { - UnwrapAlignmentPadding( - expression, out expression, out alignment, out negate, unnecessarySpans); - } - - if (interpolation.FormatString == null) - { - UnwrapFormatString( - virtualCharService, syntaxFacts, expression, out expression, out formatString, unnecessarySpans); - } - - unwrapped = GetPreservedInterpolationExpressionSyntax(expression) as TExpressionSyntax; - - unnecessaryLocations = - unnecessarySpans.OrderBy(t => t.Start) - .SelectAsArray(interpolation.Syntax.SyntaxTree.GetLocation); - } - - private static IOperation Unwrap(IOperation expression) - { - while (true) - { - switch (expression) - { - case IParenthesizedOperation parenthesized: - expression = parenthesized.Operand; - continue; - case IConversionOperation { IsImplicit: true } conversion: - expression = conversion.Operand; - continue; - default: - return expression; - } - } - } - - private static void UnwrapFormatString( - IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IOperation expression, out IOperation unwrapped, - out string? formatString, List unnecessarySpans) - where TConditionalExpressionSyntax : SyntaxNode - where TParenthesizedExpressionSyntax : SyntaxNode - { - if (expression is IInvocationOperation { TargetMethod: { Name: nameof(ToString) } } invocation && - HasNonImplicitInstance(invocation) && - !syntaxFacts.IsBaseExpression(invocation.Instance!.Syntax) && - !invocation.Instance.Type!.IsRefLikeType) - { - if (invocation.Arguments.Length == 1 && - invocation.Arguments[0].Value is ILiteralOperation { ConstantValue: { HasValue: true, Value: string value } } literal && - invocation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.IFormattable).FullName!) is { } systemIFormattable && - invocation.Instance.Type.Implements(systemIFormattable)) - { - unwrapped = invocation.Instance; - formatString = value; - - var unwrappedSyntax = GetPreservedInterpolationExpressionSyntax(unwrapped); - unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan) - .Subtract(GetSpanWithinLiteralQuotes(virtualCharService, literal.Syntax.GetFirstToken()))); - return; - } - - var method = invocation.TargetMethod; - while (method.OverriddenMethod != null) - { - method = method.OverriddenMethod; - } - - if (method.ContainingType.SpecialType == SpecialType.System_Object && - method.Name == nameof(ToString)) - { - // A call to `.ToString()` at the end of the interpolation. This is unnecessary. - // Just remove entirely. - unwrapped = invocation.Instance; - formatString = ""; - - var unwrappedSyntax = GetPreservedInterpolationExpressionSyntax(unwrapped); - unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan)); - return; - } - } - - unwrapped = expression; - formatString = null; - } - - private static TextSpan GetSpanWithinLiteralQuotes(IVirtualCharService virtualCharService, SyntaxToken formatToken) - { - var sequence = virtualCharService.TryConvertToVirtualChars(formatToken); - return sequence.IsDefaultOrEmpty - ? default - : TextSpan.FromBounds(sequence.First().Span.Start, sequence.Last().Span.End); - } - - private static void UnwrapAlignmentPadding( - IOperation expression, out IOperation unwrapped, - out TExpressionSyntax? alignment, out bool negate, List unnecessarySpans) - where TExpressionSyntax : SyntaxNode - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax - { - if (expression is IInvocationOperation invocation && - HasNonImplicitInstance(invocation)) - { - var targetName = invocation.TargetMethod.Name; - if (targetName == nameof(string.PadLeft) || targetName == nameof(string.PadRight)) - { - var argCount = invocation.Arguments.Length; - if (argCount == 1 || argCount == 2) - { - if (argCount == 1 || - IsSpaceChar(invocation.Arguments[1])) - { - var alignmentOp = invocation.Arguments[0].Value; - if (alignmentOp != null && alignmentOp.ConstantValue.HasValue) - { - var alignmentSyntax = alignmentOp.Syntax; - - unwrapped = invocation.Instance!; - alignment = alignmentSyntax as TExpressionSyntax; - negate = targetName == nameof(string.PadRight); - - var unwrappedSyntax = GetPreservedInterpolationExpressionSyntax(unwrapped); - unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan) - .Subtract(alignmentSyntax.FullSpan)); - return; - } - } - } - } - } - - unwrapped = expression; - alignment = null; - negate = false; - } - - private static bool HasNonImplicitInstance(IInvocationOperation invocation) - => invocation.Instance != null && !invocation.Instance.IsImplicit; - - private static bool IsSpaceChar(IArgumentOperation argument) - => argument.Value.ConstantValue is { HasValue: true, Value: ' ' }; - } -} diff --git a/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs index 0df2992c585d0..3442eb30ba475 100644 --- a/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs @@ -79,7 +79,7 @@ protected void AnalyzeProperty( var cancellationToken = context.CancellationToken; var semanticModel = context.SemanticModel; - if (!(semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken) is IPropertySymbol property)) + if (semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken) is not IPropertySymbol property) { return; } @@ -205,7 +205,7 @@ protected void AnalyzeProperty( } var fieldReference = getterField.DeclaringSyntaxReferences[0]; - if (!(fieldReference.GetSyntax(cancellationToken) is TVariableDeclarator variableDeclarator)) + if (fieldReference.GetSyntax(cancellationToken) is not TVariableDeclarator variableDeclarator) { return; } @@ -216,7 +216,7 @@ protected void AnalyzeProperty( return; } - if (!(variableDeclarator.Parent?.Parent is TFieldDeclaration fieldDeclaration)) + if (variableDeclarator.Parent?.Parent is not TFieldDeclaration fieldDeclaration) { return; } diff --git a/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs index a27831675e58c..aa1738669697e 100644 --- a/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs @@ -35,6 +35,7 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; protected abstract ISyntaxFacts GetSyntaxFacts(); + protected abstract bool IsTargetTyped(SemanticModel semanticModel, TConditionalExpressionSyntax conditional, System.Threading.CancellationToken cancellationToken); protected override void InitializeWorker(AnalysisContext context) { @@ -45,13 +46,12 @@ protected override void InitializeWorker(AnalysisContext context) private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { + var cancellationToken = context.CancellationToken; var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var option = context.GetOption(CodeStyleOptions2.PreferCoalesceExpression, conditionalExpression.Language); if (!option.Value) - { return; - } var syntaxFacts = GetSyntaxFacts(); syntaxFacts.GetPartsOfConditionalExpression( @@ -61,18 +61,14 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh); var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh); - if (!(conditionNode is TBinaryExpressionSyntax condition)) - { + if (conditionNode is not TBinaryExpressionSyntax condition) return; - } var syntaxKinds = syntaxFacts.SyntaxKinds; var isEquals = syntaxKinds.ReferenceEqualsExpression == condition.RawKind; var isNotEquals = syntaxKinds.ReferenceNotEqualsExpression == condition.RawKind; if (!isEquals && !isNotEquals) - { return; - } syntaxFacts.GetPartsOfBinaryExpression(condition, out var conditionLeftHigh, out var conditionRightHigh); @@ -89,9 +85,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) } if (!conditionRightIsNull && !conditionLeftIsNull) - { return; - } if (!syntaxFacts.AreEquivalent( conditionRightIsNull ? conditionLeftLow : conditionRightLow, @@ -100,9 +94,15 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) return; } + // Coalesce expression cannot be target typed. So if we had a ternary that was target typed + // that means the individual parts themselves had no best common type, which would not work + // for a coalesce expression. var semanticModel = context.SemanticModel; + if (IsTargetTyped(semanticModel, conditionalExpression, cancellationToken)) + return; + var conditionType = semanticModel.GetTypeInfo( - conditionLeftIsNull ? conditionRightLow : conditionLeftLow, context.CancellationToken).Type; + conditionLeftIsNull ? conditionRightLow : conditionLeftLow, cancellationToken).Type; if (conditionType != null && !conditionType.IsReferenceType) { diff --git a/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs index f32184ed1acde..e6b08a4307230 100644 --- a/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs @@ -71,7 +71,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) conditionNode = syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode); } - if (!(conditionNode is TMemberAccessExpression conditionMemberAccess)) + if (conditionNode is not TMemberAccessExpression conditionMemberAccess) { return; } @@ -85,7 +85,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) } var whenPartToCheck = notHasValueExpression ? whenFalseNodeLow : whenTrueNodeLow; - if (!(whenPartToCheck is TMemberAccessExpression whenPartMemberAccess)) + if (whenPartToCheck is not TMemberAccessExpression whenPartMemberAccess) { return; } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs index d1bd00cff5f84..b7a214e910edf 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs @@ -99,7 +99,7 @@ private bool TryInitializeVariableDeclarationCase() return false; } - if (!(_objectCreationExpression.Parent.Parent is TVariableDeclaratorSyntax containingDeclarator)) + if (_objectCreationExpression.Parent.Parent is not TVariableDeclaratorSyntax containingDeclarator) { return false; } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs index d1805625dbddb..7095342b9bbed 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs @@ -80,7 +80,7 @@ protected override void AddMatches(ArrayBuilder matc return; } - if (!(child.AsNode() is TExpressionStatementSyntax statement)) + if (child.AsNode() is not TExpressionStatementSyntax statement) { return; } @@ -173,7 +173,7 @@ private bool TryAnalyzeAddInvocation( out SyntaxNode instance) { instance = null; - if (!(_syntaxFacts.GetExpressionOfExpressionStatement(statement) is TInvocationExpressionSyntax invocationExpression)) + if (_syntaxFacts.GetExpressionOfExpressionStatement(statement) is not TInvocationExpressionSyntax invocationExpression) { return false; } @@ -198,7 +198,7 @@ private bool TryAnalyzeAddInvocation( } } - if (!(_syntaxFacts.GetExpressionOfInvocationExpression(invocationExpression) is TMemberAccessExpressionSyntax memberAccess)) + if (_syntaxFacts.GetExpressionOfInvocationExpression(invocationExpression) is not TMemberAccessExpressionSyntax memberAccess) { return false; } diff --git a/src/Analyzers/Core/Analyzers/UseCompoundAssignment/AbstractUseCompoundAssignmentDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCompoundAssignment/AbstractUseCompoundAssignmentDiagnosticAnalyzer.cs index 6d5ba6a55503c..76ddfc484e7a3 100644 --- a/src/Analyzers/Core/Analyzers/UseCompoundAssignment/AbstractUseCompoundAssignmentDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCompoundAssignment/AbstractUseCompoundAssignmentDiagnosticAnalyzer.cs @@ -87,7 +87,7 @@ private void AnalyzeAssignment(SyntaxNodeAnalysisContext context) // has to be of the form: a = b op c // op has to be a form we could convert into op= - if (!(assignmentRight is TBinaryExpressionSyntax binaryExpression)) + if (assignmentRight is not TBinaryExpressionSyntax binaryExpression) { return; } diff --git a/src/Analyzers/Core/Analyzers/UseConditionalExpression/AbstractUseConditionalExpressionDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseConditionalExpression/AbstractUseConditionalExpressionDiagnosticAnalyzer.cs index 8d52df7b67838..8f9f90435ae0d 100644 --- a/src/Analyzers/Core/Analyzers/UseConditionalExpression/AbstractUseConditionalExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseConditionalExpression/AbstractUseConditionalExpressionDiagnosticAnalyzer.cs @@ -44,7 +44,7 @@ protected sealed override void InitializeWorker(AnalysisContext context) private void AnalyzeOperation(OperationAnalysisContext context) { var ifOperation = (IConditionalOperation)context.Operation; - if (!(ifOperation.Syntax is TIfStatementSyntax ifStatement)) + if (ifOperation.Syntax is not TIfStatementSyntax ifStatement) { return; } diff --git a/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs b/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs index db2d7c47689b3..379c4afb28e54 100644 --- a/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs +++ b/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs @@ -43,7 +43,7 @@ public static bool TryMatchPattern( if (falseStatement == null) { - if (!(ifOperation.Parent is IBlockOperation parentBlock)) + if (ifOperation.Parent is not IBlockOperation parentBlock) return false; var ifIndex = parentBlock.Operations.IndexOf(ifOperation); diff --git a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs index 83ee75f08c92d..1a43358bf74ba 100644 --- a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs @@ -148,7 +148,7 @@ private void AnalyzeSyntax( var type = semanticModel.GetTypeInfo(conditionalExpression).Type; if (type?.IsValueType == true) { - if (!(type is INamedTypeSymbol namedType) || namedType.ConstructedFrom.SpecialType != SpecialType.System_Nullable_T) + if (type is not INamedTypeSymbol namedType || namedType.ConstructedFrom.SpecialType != SpecialType.System_Nullable_T) { // User has something like: If(str is nothing, nothing, str.Length) // In this case, converting to str?.Length changes the type of this from @@ -315,8 +315,8 @@ private static bool TryAnalyzeInvocationCondition( return null; } - if (current is TMemberAccessExpression || - current is TElementAccessExpression) + if (current is TMemberAccessExpression or + TElementAccessExpression) { if (syntaxFacts.AreEquivalent(unwrapped, expressionToMatch)) { diff --git a/src/Analyzers/Core/Analyzers/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs index 1d0179d592e94..9885830989983 100644 --- a/src/Analyzers/Core/Analyzers/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs @@ -77,7 +77,7 @@ protected override void AddMatches(ArrayBuilder public (bool accessesBase, ImmutableArray members, ImmutableArray statements) GetHashedMembers(ISymbol? owningSymbol, IOperation? operation) { - if (!(operation is IBlockOperation blockOperation)) + if (operation is not IBlockOperation blockOperation) return default; // Owning symbol has to be an override of Object.GetHashCode. - if (!(owningSymbol is IMethodSymbol { Name: nameof(GetHashCode) } method)) + if (owningSymbol is not IMethodSymbol { Name: nameof(GetHashCode) } method) return default; if (method.Locations.Length != 1 || method.DeclaringSyntaxReferences.Length != 1) @@ -99,7 +99,7 @@ public static bool TryGetAnalyzer(Compilation compilation, [NotNullWhen(true)] o return null; } - if (!(statements[0] is IReturnOperation { ReturnedValue: { } returnedValue })) + if (statements[0] is not IReturnOperation { ReturnedValue: { } returnedValue }) { return null; } @@ -133,7 +133,7 @@ public static bool TryGetAnalyzer(Compilation compilation, [NotNullWhen(true)] o // First statement has to be the declaration of the accumulator. // Last statement has to be the return of it. - if (!(statements.First() is IVariableDeclarationGroupOperation varDeclStatement) || + if (statements.First() is not IVariableDeclarationGroupOperation varDeclStatement || !(statements.Last() is IReturnOperation { ReturnedValue: { } returnedValue })) { return null; @@ -200,8 +200,8 @@ public static bool TryGetAnalyzer(Compilation compilation, [NotNullWhen(true)] o for (var i = 1; i < statements.Length - 1; i++) { var statement = statements[i]; - if (!(statement is IExpressionStatementOperation expressionStatement) || - !(expressionStatement.Operation is ISimpleAssignmentOperation simpleAssignment) || + if (statement is not IExpressionStatementOperation expressionStatement || + expressionStatement.Operation is not ISimpleAssignmentOperation simpleAssignment || !IsLocalReference(simpleAssignment.Target, hashCodeVariable) || !valueAnalyzer.TryAddHashedSymbol(simpleAssignment.Value, seenHash: false)) { diff --git a/src/Analyzers/Core/Analyzers/UseThrowExpression/AbstractUseThrowExpressionDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseThrowExpression/AbstractUseThrowExpressionDiagnosticAnalyzer.cs index 97cf124bf91e5..2dd41cdd8d6a4 100644 --- a/src/Analyzers/Core/Analyzers/UseThrowExpression/AbstractUseThrowExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseThrowExpression/AbstractUseThrowExpressionDiagnosticAnalyzer.cs @@ -105,7 +105,7 @@ private void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol return; } - if (!(ifOperation.Parent is IBlockOperation containingBlock)) + if (ifOperation.Parent is not IBlockOperation containingBlock) { return; } @@ -222,7 +222,7 @@ private bool TryDecomposeIfCondition( localOrParameter = null; var condition = ifStatement.Condition; - if (!(condition is IBinaryOperation binaryOperator)) + if (condition is not IBinaryOperation binaryOperator) { return false; } diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf index 7925b42d1de34..c1b0a11100c03 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Nepoužívejte několik prázdných řádků. @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Mezi blokem a následným příkazem se vyžaduje prázdný řádek. Change namespace to match folder structure - Change namespace to match folder structure + Změnit namespace tak, aby odpovídal struktuře složek {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + Namespace {0} neodpovídá struktuře složek, očekávalo se {1}. {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Namespace neodpovídá struktuře složek. {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Soukromý člen {0} se může odebrat, jak jeho přiřazená hodnota se nikdy nečte. + Soukromý člen {0} se může odebrat, jeho přiřazená hodnota se nikdy nečte. Private member '{0}' is unused - Soukromý člen {0} se nepoužívá. + Soukromý člen {0} se nepoužívá. @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Zjednodušit výraz LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf index 2674018160303..b1ad584c85054 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Vermeiden Sie mehrere Leerzeilen. @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Zwischen dem Block und der nachfolgenden Anweisung ist eine leere Zeile erforderlich. Change namespace to match folder structure - Change namespace to match folder structure + Ändern Sie "namespace", sodass er der Ordnerstruktur entspricht. {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + Der Namespace "{0}" entspricht nicht der Ordnerstruktur. Erwartet: {1} {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Der Namespace entspricht stimmt nicht der Ordnerstruktur. {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Der private Member "{0}" kann entfernt werden, da der zugewiesene Wert nie gelesen wird. + Der private Member "{0}" kann entfernt werden, weil der zugewiesene Wert nie gelesen wird. Private member '{0}' is unused - Der private Member "{0}" wird nicht verwendet. + Der private Member "{0}" wird nicht verwendet. @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + LINQ-Ausdruck vereinfachen diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf index f5ff15cf29d38..9767c0e874214 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Evitar varias líneas en blanco @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Se requiere una línea en blanco entre el bloque y la instrucción subsiguiente Change namespace to match folder structure - Change namespace to match folder structure + Cambiar namespace para que coincida con la estructura de carpetas {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + El espacio de nombres "{0}" no coincide con la estructura de carpetas, se esperaba "{1}" {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + El espacio de nombres no coincide con la estructura de carpetas {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Un miembro privado "{0}" puede retirarse porque el valor asignado a él no se lee nunca. + El miembro privado "{0}" puede quitarse porque el valor que tiene asignado no se lee nunca Private member '{0}' is unused - Un miembro privado "{0}" está sin usar. + El miembro privado "{0}" no se usa @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Simplificar la expresión LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf index 28f815d2a7d00..f0b48a56dcb6f 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Éviter plusieurs lignes vides @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Ligne vide nécessaire entre le bloc et l'instruction qui suit Change namespace to match folder structure - Change namespace to match folder structure + Changer namespace pour le faire correspondre à la structure de dossiers {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + Le namespace "{0}" ne correspond pas à la structure de dossiers. Résultat attendu : "{1}" {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Le namespace ne correspond pas à la structure de dossiers {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Vous pouvez supprimer le membre privé '{0}', car la valeur qui lui est attribuée n'est jamais lue. + Vous pouvez supprimer le membre privé '{0}', car la valeur qui lui est affectée n'est jamais lue Private member '{0}' is unused - Le membre privé '{0}' n'est pas utilisé. + Le membre privé '{0}' n'est pas utilisé @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Simplifier l'expression LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf index d7265e19c99e4..a1619f712d798 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Evitare più righe vuote @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + È richiesta una riga vuota tra il blocco e l'istruzione successiva Change namespace to match folder structure - Change namespace to match folder structure + Cambia namespace in modo che corrisponda alla struttura di cartelle {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + La parola chiave namespace "{0}" non corrisponde alla struttura di cartelle. Valore previsto: "{1}" {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + La parola chiave namespace non corrisponde alla struttura di cartelle {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - È possibile rimuovere il membro privato '{0}' perché il valore assegnato ad esso non viene mai letto. + È possibile rimuovere il membro privato '{0}' perché il valore assegnato ad esso non viene mai letto Private member '{0}' is unused - Il membro privato '{0}' è inutilizzato. + Il membro privato '{0}' è inutilizzato @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Semplifica l'espressione LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf index 55e3881bdcc43..7905d84091643 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + 複数の空白行は使用できません @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + ブロックと後続のステートメントの間に空白行が必要です Change namespace to match folder structure - Change namespace to match folder structure + フォルダー構造に合わせて namespace を変更してください {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + Namespace "{0}" はフォルダー構造と一致しません。"{1}" が必要です {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Namespace がフォルダー構造と一致しません {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - 割り当てられている値が読み取られることがないようにプライベートメンバー '{0}' を削除できます。 + 割り当てられた値が読み取られることがないので、プライベート メンバー '{0}' を削除できます Private member '{0}' is unused - プライベート メンバー '{0}' は使用されていません。 + プライベート メンバー '{0}' は使用されていません @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + LINQ 式を簡略化する diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf index 0785df09fb9b8..a7ce5c6196177 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + 여러 빈 줄 방지 @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + 블록과 후속 문 사이에 빈 줄이 필요함 Change namespace to match folder structure - Change namespace to match folder structure + 폴더 구조가 일치하도록 namespace 변경 {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + 네임스페이스 "{0}"이(가) 폴더 구조와 일치하지 않습니다. "{1}"이(가) 필요합니다. {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + 네임스페이스가 폴더 구조와 일치하지 않습니다. {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Private 멤버 '{0}'에 할당된 값을 읽을 수 없으므로 이 멤버를 제거할 수 있습니다. + 프라이빗 멤버 '{0}'에 할당된 값을 읽을 수 없으므로 이 멤버를 제거할 수 있습니다. Private member '{0}' is unused - Private 멤버 '{0}'을(를) 사용하지 않습니다. + 프라이빗 멤버 '{0}'을(를) 사용하지 않습니다. @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + LINQ 식 단순화 diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf index ef415191d4b5a..ff9c7bbad8382 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Unikaj wielu pustych wierszy @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Wymagany jest pusty wiersz między blokiem a kolejną instrukcją Change namespace to match folder structure - Change namespace to match folder structure + Zmień element namespace, aby była zgodna ze strukturą folderów {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + Przestrzeń nazw „{0}” nie jest zgodna z strukturą folderów, oczekiwano „{1}” {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Przestrzeń nazw nie jest zgodna ze strukturą folderów {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Prywatną składową „{0}” można usunąć, ponieważ przypisana do niej wartość nie jest nigdy odczytywana. + Prywatną składową „{0}” można usunąć, ponieważ przypisana do niej wartość nie jest nigdy odczytywana Private member '{0}' is unused - Prywatna składowa „{0}” jest nieużywana. + Prywatna składowa „{0}” jest nieużywana @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Uprość wyrażenie LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf index 8982822efffcd..aace8a2b030f4 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Evite várias linhas em branco @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Linha em branco necessária entre o bloco e a próxima instrução Change namespace to match folder structure - Change namespace to match folder structure + Alterar o namespace para corresponder à estrutura da pasta {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + O namespace "{0}" não corresponde à estrutura da pasta. Esperava-se "{1}" {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + O namespace não corresponde à estrutura da pasta {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - O membro particular '{0}' pode ser removido pois o valor atribuído a ele nunca é lido. + O membro privado '{0}' pode ser removido, pois o valor atribuído a ele nunca é lido Private member '{0}' is unused - O membro privado '{0}' não é utilizado. + O membro privado '{0}' não é usado @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Simplificar a expressão LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf index 2344800ca8d2a..70bc2d0e834ba 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Избегайте использования нескольких пустых строк @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Необходима пустая линия между блоком и следующим оператором. Change namespace to match folder structure - Change namespace to match folder structure + Измените пространство имен (namespace) для соответствия структуре папок. {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + Пространство имен (namespace) "{0}" не соответствует структуре папок, ожидается "{1}". {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Пространство имен (namespace) не соответствует структуре папок. {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Закрытый элемент "{0}" может быть удален, так как присвоенное ему значение никогда не читается. + Закрытый элемент "{0}" может быть удален, так как присвоенное ему значение никогда не считывается. Private member '{0}' is unused - Закрытый член «{0}» не используется. + Закрытый элемент "{0}" не используется. @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + Упростите выражение LINQ diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf index 896a815b83c53..5d9b6eec35c28 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + Birden çok boş satırdan kaçının @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + Blok ve sonraki ifade arasında boş satır gerekir Change namespace to match folder structure - Change namespace to match folder structure + namespace'i klasör yapısıyla eşlenecek şekilde değiştirir {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + "{0}" ad alanı klasör yapısıyla eşleşmiyor, "{1}" bekleniyor {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + Ad alanı, klasör yapısıyla eşleşmiyor {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - Kendisine atanmış değeri hiç okurken özel üye '{0}'-ebilmek var olmak çıkarmak. + Kendisine atanmış değer hiç okunmadığından, özel üye '{0}' kaldırılabilir. Private member '{0}' is unused - Özel üye '{0}' kullanılmamış olur. + Özel üye '{0}' kullanılmıyor @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + LINQ ifadesini basitleştir diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf index 3ce75e837ef1e..ec8ed9955e47d 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + 避免出现多个空白行 @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + 块与后续语句之间需要有空白行 Change namespace to match folder structure - Change namespace to match folder structure + 更改 namespace 以匹配文件夹结构 {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + 命名空间“{0}”与文件夹结构不匹配,应为“{1}” {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + 命名空间与文件夹结构不匹配 {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - 可删除私有成员“{0}”,因为永不会读取分配给它的值。 + 可删除私有成员“{0}”,因为永不会读取分配给它的值 Private member '{0}' is unused - 未使用私有成员“{0}”。 + 未使用私有成员“{0}” @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + 简化 LINQ 表达式 diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf index d0f0d09fb9f5d..d1201028be14c 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -49,7 +49,7 @@ Avoid multiple blank lines - Avoid multiple blank lines + 避免多個空白行 @@ -64,12 +64,12 @@ Blank line required between block and subsequent statement - Blank line required between block and subsequent statement + 區塊與後續陳述式之間必須有空白行 Change namespace to match folder structure - Change namespace to match folder structure + 變更 namespace 以符合資料夾結構 {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -164,12 +164,12 @@ Namespace "{0}" does not match folder structure, expected "{1}" - Namespace "{0}" does not match folder structure, expected "{1}" + 命名空間 "{0}" 與資料夾結構不相符,必須為 "{1}" {Locked="namespace"} "namespace" is a keyword and should not be localized. Namespace does not match folder structure - Namespace does not match folder structure + 命名空間與資料夾結構不相符 {Locked="namespace"} "namespace" is a keyword and should not be localized. @@ -229,12 +229,12 @@ Private member '{0}' can be removed as the value assigned to it is never read - 因為永遠不會讀取指派給私用成員 '{0}' 的值,所以可移除該成員。 + 因為永遠不會讀取指派給 Private 成員 '{0}' 的值,所以可移除該成員 Private member '{0}' is unused - 未使用私用成員 '{0}'。 + 未使用 Private 成員 '{0}' @@ -299,7 +299,7 @@ Simplify LINQ expression - Simplify LINQ expression + 簡化 LINQ 運算式 diff --git a/src/Analyzers/Core/CodeFixes/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs index bafcea3333939..48a0c41710c0b 100644 --- a/src/Analyzers/Core/CodeFixes/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs @@ -123,7 +123,7 @@ private void ReplaceWithTuple(SyntaxEditor editor, TAnonymousObjectCreationExpre { // Use the callback form as anonymous types may be nested, and we want to // properly replace them even in that case. - if (!(current is TAnonymousObjectCreationExpressionSyntax anonCreation)) + if (current is not TAnonymousObjectCreationExpressionSyntax anonCreation) { return current; } diff --git a/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.cs index 7c5d4643d1644..94a21a782d2e5 100644 --- a/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.cs @@ -24,9 +24,12 @@ internal abstract partial class AbstractChangeNamespaceToMatchFolderCodeFixProvi public override Task RegisterCodeFixesAsync(CodeFixContext context) { - context.RegisterCodeFix( - new MyCodeAction(AnalyzersResources.Change_namespace_to_match_folder_structure, cancellationToken => FixAllInDocumentAsync(context.Document, context.Diagnostics, cancellationToken)), - context.Diagnostics); + if (context.Document.Project.Solution.Workspace.CanApplyChange(ApplyChangesKind.ChangeDocumentInfo)) + { + context.RegisterCodeFix( + new MyCodeAction(AnalyzersResources.Change_namespace_to_match_folder_structure, cancellationToken => FixAllInDocumentAsync(context.Document, context.Diagnostics, cancellationToken)), + context.Diagnostics); + } return Task.CompletedTask; } diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index 257d2ae8ed071..d5117cdd151ca 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -77,6 +77,7 @@ internal static class PredefinedCodeFixProviderNames public const string RemoveIn = nameof(RemoveIn); public const string SimplifyLinqExpression = nameof(SimplifyLinqExpression); public const string ChangeNamespaceToMatchFolder = nameof(ChangeNamespaceToMatchFolder); + public const string SimplifyObjectCreation = nameof(SimplifyObjectCreation); public const string ConvertAnonymousTypeToTuple = nameof(ConvertAnonymousTypeToTuple); public const string AddRequiredParentheses = nameof(AddRequiredParentheses); public const string AddAccessibilityModifiers = nameof(AddAccessibilityModifiers); diff --git a/src/Analyzers/Core/CodeFixes/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesCodeFixProvider.cs index fe7c09969e87d..290d41b568876 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesCodeFixProvider.cs @@ -26,7 +26,8 @@ public override ImmutableArray FixableDiagnosticIds internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle; - protected abstract bool CanRemoveParentheses(TParenthesizedExpressionSyntax current, SemanticModel semanticModel); + protected abstract bool CanRemoveParentheses( + TParenthesizedExpressionSyntax current, SemanticModel semanticModel, CancellationToken cancellationToken); public override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -48,7 +49,7 @@ protected override Task FixAllAsync( return editor.ApplyExpressionLevelSemanticEditsAsync( document, originalNodes, - (semanticModel, current) => current != null && CanRemoveParentheses(current, semanticModel), + (semanticModel, current) => current != null && CanRemoveParentheses(current, semanticModel, cancellationToken), (_, currentRoot, current) => currentRoot.ReplaceNode(current, syntaxFacts.Unparenthesize(current)), cancellationToken); } diff --git a/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs index 1c214116cd044..47d4aa9417fa8 100644 --- a/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs @@ -23,22 +23,20 @@ internal abstract class AbstractSimplifyInterpolationCodeFixProvider< TExpressionSyntax, TInterpolationAlignmentClause, TInterpolationFormatClause, - TInterpolatedStringExpressionSyntax, - TConditionalExpressionSyntax, - TParenthesizedExpressionSyntax> : SyntaxEditorBasedCodeFixProvider + TInterpolatedStringExpressionSyntax> : SyntaxEditorBasedCodeFixProvider where TInterpolationSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode where TInterpolationAlignmentClause : SyntaxNode where TInterpolationFormatClause : SyntaxNode where TInterpolatedStringExpressionSyntax : TExpressionSyntax - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax { public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(IDEDiagnosticIds.SimplifyInterpolationId); internal override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle; + protected abstract AbstractSimplifyInterpolationHelpers GetHelpers(); + protected abstract TInterpolationSyntax WithExpression(TInterpolationSyntax interpolation, TExpressionSyntax expression); protected abstract TInterpolationSyntax WithAlignmentClause(TInterpolationSyntax interpolation, TInterpolationAlignmentClause alignmentClause); protected abstract TInterpolationSyntax WithFormatClause(TInterpolationSyntax interpolation, TInterpolationFormatClause? formatClause); @@ -59,6 +57,8 @@ protected override async Task FixAllAsync( var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var generator = editor.Generator; var generatorInternal = document.GetRequiredLanguageService(); + var helpers = GetHelpers(); + foreach (var diagnostic in diagnostics) { var loc = diagnostic.AdditionalLocations[0]; @@ -66,10 +66,10 @@ protected override async Task FixAllAsync( if (interpolation?.Syntax is TInterpolationSyntax interpolationSyntax && interpolationSyntax.Parent is TInterpolatedStringExpressionSyntax interpolatedString) { - Helpers.UnwrapInterpolation( + helpers.UnwrapInterpolation( document.GetRequiredLanguageService(), - document.GetRequiredLanguageService(), interpolation, out var unwrapped, - out var alignment, out var negate, out var formatString, out _); + document.GetRequiredLanguageService(), + interpolation, out var unwrapped, out var alignment, out var negate, out var formatString, out _); if (unwrapped == null) continue; diff --git a/src/Analyzers/Core/CodeFixes/UseConditionalExpression/ForAssignment/AbstractUseConditionalExpressionForAssignmentCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseConditionalExpression/ForAssignment/AbstractUseConditionalExpressionForAssignmentCodeFixProvider.cs index bbecf91b3c74f..3d75945a9b589 100644 --- a/src/Analyzers/Core/CodeFixes/UseConditionalExpression/ForAssignment/AbstractUseConditionalExpressionForAssignmentCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseConditionalExpression/ForAssignment/AbstractUseConditionalExpressionForAssignmentCodeFixProvider.cs @@ -159,7 +159,7 @@ private bool TryFindMatchingLocalDeclarationImmediatelyAbove( ILocalSymbol? local = null; if (trueAssignment != null) { - if (!(trueAssignment.Target is ILocalReferenceOperation trueLocal)) + if (trueAssignment.Target is not ILocalReferenceOperation trueLocal) return false; local = trueLocal.Local; @@ -167,7 +167,7 @@ private bool TryFindMatchingLocalDeclarationImmediatelyAbove( if (falseAssignment != null) { - if (!(falseAssignment.Target is ILocalReferenceOperation falseLocal)) + if (falseAssignment.Target is not ILocalReferenceOperation falseLocal) return false; // See if both assignments are to the same local. @@ -182,7 +182,7 @@ private bool TryFindMatchingLocalDeclarationImmediatelyAbove( return false; // If so, see if that local was declared immediately above the if-statement. - if (!(ifOperation.Parent is IBlockOperation parentBlock)) + if (ifOperation.Parent is not IBlockOperation parentBlock) { return false; } @@ -230,8 +230,8 @@ private bool TryFindMatchingLocalDeclarationImmediatelyAbove( var unwrapped = UnwrapImplicitConversion(variableInitializer.Value); // the variable has to either not have an initializer, or it needs to be basic // literal/default expression. - if (!(unwrapped is ILiteralOperation) && - !(unwrapped is IDefaultValueOperation)) + if (unwrapped is not ILiteralOperation and + not IDefaultValueOperation) { return false; } diff --git a/src/Analyzers/Core/CodeFixes/UseSystemHashCode/UseSystemHashCodeCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseSystemHashCode/UseSystemHashCodeCodeFixProvider.cs index 7a533be0b45e1..64370ae8d7ec8 100644 --- a/src/Analyzers/Core/CodeFixes/UseSystemHashCode/UseSystemHashCodeCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseSystemHashCode/UseSystemHashCodeCodeFixProvider.cs @@ -82,7 +82,7 @@ protected override async Task FixAllAsync( // so that we generate the same. var containingType = accessesBase ? method!.ContainingType : null; var components = generator.GetGetHashCodeComponents( - semanticModel.Compilation, containingType, members, justMemberReference: true); + generatorInternal, semanticModel.Compilation, containingType, members, justMemberReference: true); var updatedDecl = generator.WithStatements( methodBlock, diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf index 22291c7db14b1..40c62dc5ef12f 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Přidat za blok prázdný řádek @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Odebrat nadbytečné prázdné řádky diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf index a3708f62fae80..000aff4828ade 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Leere Zeile nach Block hinzufügen @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Zusätzliche Leerzeilen entfernen diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf index 51f0802c2b2f3..d2b05ccb949ec 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Agregar una línea en blanco después del bloque @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Quitar líneas en blanco adicionales diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf index 8aec2b98d8d83..1bc7ba0cde0b2 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Ajouter une ligne vide après le bloc @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Supprimer les lignes vides supplémentaires diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf index 6f025134498bc..9750c818396b8 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Aggiungi una riga vuota dopo il blocco @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Rimuovere le righe vuote aggiuntive diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf index befe504237829..9d30fbab31577 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + ブロックの後に空白行を追加する @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + 余分な空白行を削除する diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf index 1dbf93f116076..a317562db51bb 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + 블록 뒤에 빈 줄 추가 @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + 여분의 빈 줄 제거 diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf index 56a0a4761d6b6..2cac41bc9fb3c 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Dodaj pusty wiersz po bloku @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Usuń dodatkowe puste wiersze diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf index 1b1fed36d273c..4cd02227a233b 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Adicionar uma linha em branco após o bloco @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Remover as linhas em branco extras diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf index cf250dd5f3589..6fadec564d3da 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Добавить пустую строку после блока @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Удалите лишние пустые строки diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf index 7f4f98070ac5b..7b2433913090a 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + Bloktan sonra boş satır ekleme @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + Fazladan boş satırları kaldır diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf index 33f345d51ad58..554cb184a0b58 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + 在块后添加空白行 @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + 删除多余的空白行 diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf index d15725ff5ef3c..02ceb76def6b6 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf @@ -1,10 +1,10 @@ - + Add blank line after block - Add blank line after block + 在區塊後面新增空白行 @@ -34,7 +34,7 @@ Remove extra blank lines - Remove extra blank lines + 移除額外的空白行 diff --git a/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesDiagnosticAnalyzer.vb index 5c191276c419b..3798b6c2baf0f 100644 --- a/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesDiagnosticAnalyzer.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses Imports Microsoft.CodeAnalysis.Precedence Imports Microsoft.CodeAnalysis.LanguageServices +Imports System.Threading Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryParentheses @@ -24,7 +25,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryParentheses End Function Protected Overrides Function CanRemoveParentheses( - parenthesizedExpression As ParenthesizedExpressionSyntax, semanticModel As SemanticModel, + parenthesizedExpression As ParenthesizedExpressionSyntax, + semanticModel As SemanticModel, cancellationToken As CancellationToken, ByRef precedence As PrecedenceKind, ByRef clarifiesPrecedence As Boolean) As Boolean Return CanRemoveParenthesesHelper( diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb index b88ee1f3db3ae..5699175fc243c 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb @@ -13,11 +13,11 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Friend Class VisualBasicSimplifyInterpolationDiagnosticAnalyzer - Inherits AbstractSimplifyInterpolationDiagnosticAnalyzer(Of - InterpolationSyntax, - ExpressionSyntax, - TernaryConditionalExpressionSyntax, - ParenthesizedExpressionSyntax) + Inherits AbstractSimplifyInterpolationDiagnosticAnalyzer(Of InterpolationSyntax, ExpressionSyntax) + + Protected Overrides Function GetHelpers() As AbstractSimplifyInterpolationHelpers + Return VisualBasicSimplifyInterpolationHelpers.Instance + End Function Protected Overrides Function GetVirtualCharService() As IVirtualCharService Return VisualBasicVirtualCharService.Instance diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb new file mode 100644 index 0000000000000..e78561490f34d --- /dev/null +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb @@ -0,0 +1,22 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.SimplifyInterpolation + +Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation + Friend NotInheritable Class VisualBasicSimplifyInterpolationHelpers + Inherits AbstractSimplifyInterpolationHelpers + + Public Shared ReadOnly Property Instance As New VisualBasicSimplifyInterpolationHelpers + + Private Sub New() + End Sub + + Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + + Protected Overrides Function GetPreservedInterpolationExpressionSyntax(operation As IOperation) As SyntaxNode + Return operation.Syntax + End Function + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb new file mode 100644 index 0000000000000..047c0a2b797cf --- /dev/null +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb @@ -0,0 +1,66 @@ +' 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 Microsoft.CodeAnalysis.CodeStyle +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic.CodeStyle +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation + + Friend NotInheritable Class VisualBasicSimplifyObjectCreationDiagnosticAnalyzer + Inherits AbstractBuiltInCodeStyleDiagnosticAnalyzer + + Public Sub New() + MyBase.New( + diagnosticId:=IDEDiagnosticIds.SimplifyObjectCreationDiagnosticId, + enforceOnBuild:=EnforceOnBuildValues.SimplifyObjectCreation, + [option]:=VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, + language:=LanguageNames.VisualBasic, + title:=New LocalizableResourceString(NameOf(VisualBasicAnalyzersResources.Object_creation_can_be_simplified), VisualBasicAnalyzersResources.ResourceManager, GetType(VisualBasicAnalyzersResources))) + End Sub + + Protected Overrides Sub InitializeWorker(context As AnalysisContext) + context.RegisterSyntaxNodeAction(AddressOf AnalyzeVariableDeclarator, SyntaxKind.VariableDeclarator) + End Sub + + Public Overrides Function GetAnalyzerCategory() As DiagnosticAnalyzerCategory + Return DiagnosticAnalyzerCategory.SemanticSpanAnalysis + End Function + + Private Sub AnalyzeVariableDeclarator(context As SyntaxNodeAnalysisContext) + ' Finds and reports syntax on the form: + ' Dim x As SomeType = New SomeType() + ' which can be simplified to + ' Dim x As New SomeType() + + Dim node = context.Node + Dim tree = node.SyntaxTree + Dim cancellationToken = context.CancellationToken + + Dim styleOption = context.Options.GetOption(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, tree, cancellationToken) + If Not styleOption.Value Then + Return + End If + + Dim variableDeclarator = DirectCast(node, VariableDeclaratorSyntax) + Dim asClauseType = variableDeclarator.AsClause?.Type() + If asClauseType Is Nothing Then + Return + End If + + Dim objectCreation = TryCast(variableDeclarator.Initializer?.Value, ObjectCreationExpressionSyntax) + If objectCreation Is Nothing Then + Return + End If + + Dim symbolInfo = context.SemanticModel.GetTypeInfo(objectCreation, cancellationToken) + If symbolInfo.Type IsNot Nothing AndAlso symbolInfo.Type.Equals(symbolInfo.ConvertedType, SymbolEqualityComparer.Default) Then + context.ReportDiagnostic(DiagnosticHelper.Create(Descriptor, variableDeclarator.GetLocation(), styleOption.Notification.Severity, + additionalLocations:=Nothing, + properties:=Nothing)) + End If + End Sub + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/Analyzers/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb index a2ffd98fb9dea..4eb00f398c68d 100644 --- a/src/Analyzers/VisualBasic/Analyzers/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb @@ -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. +Imports System.Threading Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.LanguageServices Imports Microsoft.CodeAnalysis.UseCoalesceExpression @@ -20,5 +21,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCoalesceExpression Protected Overrides Function GetSyntaxFacts() As ISyntaxFacts Return VisualBasicSyntaxFacts.Instance End Function + + Protected Overrides Function IsTargetTyped(semanticModel As SemanticModel, conditional As TernaryConditionalExpressionSyntax, cancellationToken As CancellationToken) As Boolean + ' VB does not have target typed conditionals. + Return False + End Function End Class End Namespace diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems index 6d5f7b35a94c2..acbdb7df3bd8a 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems @@ -36,8 +36,10 @@ + + diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx index a171b866ea844..5b9f84d179fc3 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx @@ -140,4 +140,7 @@ Use 'IsNot' expression {locked: IsNot}This is a Visual Basic keyword and should not be localized or have its casing changed + + Object creation can be simplified + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf index 7aed16da9d690..8ef303a6c4934 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Příkaz Imports není potřebný. + + Object creation can be simplified + Vytváření objektů se dá zjednodušit. + + 'ByVal' keyword is unnecessary and can be removed. Klíčové slovo ByVal není zapotřebí a dá se odebrat. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf index 63d87b6def34e..bf1259c2f6dc6 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Imports-Anweisung ist unnötig. + + Object creation can be simplified + Objekterstellung kann vereinfacht werden + + 'ByVal' keyword is unnecessary and can be removed. Das ByVal-Schlüsselwort ist unnötig und kann entfernt werden. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf index 57e1fe2784cc8..9e84d3f7a3c91 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ La declaración de importaciones no es necesaria. + + Object creation can be simplified + Se pueden simplificar las creaciones de objetos + + 'ByVal' keyword is unnecessary and can be removed. La palabra clave "ByVal" no es necesaria y se puede quitar. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf index b35686fb49a03..80bf41c9a54d4 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ L'instruction Imports n'est pas utile. + + Object creation can be simplified + La création d’objets peut être simplifiée + + 'ByVal' keyword is unnecessary and can be removed. Le mot clé 'ByVal' est inutile et peut être supprimé. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf index 0139c32109f91..f8cd6e34a0749 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ L'istruzione Imports non è necessaria. + + Object creation can be simplified + La creazione di oggetti può essere semplificata + + 'ByVal' keyword is unnecessary and can be removed. La parola chiave 'ByVal' non è necessaria e può essere rimossa. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf index c0e2f334e81b2..0e433aa03dc96 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Imports ステートメントは不要です。 + + Object creation can be simplified + オブジェクト作成を簡略化できます + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' キーワードは必要ありません。削除することができます。 diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf index 8bd041a14cf53..07ed06d45c35d 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Imports 문은 필요하지 않습니다. + + Object creation can be simplified + 개체 만들기를 간소화할 수 있습니다. + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' 키워드는 불필요하며 제거할 수 있습니다. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf index 81be8dd4c48b8..b0c6a5e0054f3 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Instrukcja imports jest niepotrzebna. + + Object creation can be simplified + Utworzenie obiektu można uprościć + + 'ByVal' keyword is unnecessary and can be removed. Słowo kluczowe „ByVal” jest niepotrzebne i można je usunąć. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf index f17e36330f71f..bb50e9ca95c4c 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ A instrução Imports é desnecessária. + + Object creation can be simplified + A criação de objetos pode ser simplificada + + 'ByVal' keyword is unnecessary and can be removed. A palavra-chave 'ByVal' é desnecessária e pode ser removida. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf index fb3b6dcade26f..e0bfdcd9662fd 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Оператор Imports не нужен. + + Object creation can be simplified + Создание объектов можно упростить + + 'ByVal' keyword is unnecessary and can be removed. Ключевое слово "ByVal" является необязательным и может быть удалено. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf index 7473a7934ba20..0f8c338aac43b 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Imports deyimi gerekli değildir. + + Object creation can be simplified + Nesne oluşturma basitleştirilebilir + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' anahtar sözcüğü gereksizdir ve kaldırılabilir. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf index 3127cb198c10d..c49f9a8b70067 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ 'If' statement can be simplified - 可简化“If”语句 + 可简化 "if" 语句 @@ -17,6 +17,11 @@ Imports 语句是不需要的。 + + Object creation can be simplified + 可简化对象创建 + + 'ByVal' keyword is unnecessary and can be removed. "ByVal" 关键字是不必要的,可以删除。 diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf index 218c8750bacae..3ada6968f6665 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ 無須 Imports 陳述式。 + + Object creation can be simplified + 可簡化物件建立 + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' 關鍵字非必要,可以移除。 diff --git a/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesCodeFixProvider.vb index 8193ec30bc989..3ce6d3c905b6a 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryParentheses/VisualBasicRemoveUnnecessaryParenthesesCodeFixProvider.vb @@ -4,6 +4,7 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis +Imports System.Threading Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -18,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryParentheses Public Sub New() End Sub - Protected Overrides Function CanRemoveParentheses(current As ParenthesizedExpressionSyntax, semanticModel As SemanticModel) As Boolean + Protected Overrides Function CanRemoveParentheses(current As ParenthesizedExpressionSyntax, semanticModel As SemanticModel, cancellationtoken As CancellationToken) As Boolean Return VisualBasicRemoveUnnecessaryParenthesesDiagnosticAnalyzer.CanRemoveParenthesesHelper( current, semanticModel, precedence:=Nothing, clarifiesPrecedence:=Nothing) End Function diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb index c8724c588d0f1..86d55a847a7aa 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb @@ -13,14 +13,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Friend Class VisualBasicSimplifyInterpolationCodeFixProvider Inherits AbstractSimplifyInterpolationCodeFixProvider(Of InterpolationSyntax, ExpressionSyntax, InterpolationAlignmentClauseSyntax, - InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax, - TernaryConditionalExpressionSyntax, ParenthesizedExpressionSyntax) + InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax) Public Sub New() End Sub + Protected Overrides Function GetHelpers() As AbstractSimplifyInterpolationHelpers + Return VisualBasicSimplifyInterpolationHelpers.Instance + End Function + Protected Overrides Function WithExpression(interpolation As InterpolationSyntax, expression As ExpressionSyntax) As InterpolationSyntax Return interpolation.WithExpression(expression) End Function diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb new file mode 100644 index 0000000000000..dc95518148ff1 --- /dev/null +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb @@ -0,0 +1,60 @@ +' 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.Collections.Immutable +Imports System.Composition +Imports System.Threading +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation + + Friend Class VisualBasicSimplifyObjectCreationCodeFixProvider + Inherits SyntaxEditorBasedCodeFixProvider + + + + Public Sub New() + End Sub + + Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(IDEDiagnosticIds.SimplifyObjectCreationDiagnosticId) + + Friend Overrides ReadOnly Property CodeFixCategory As CodeFixCategory = CodeFixCategory.CodeStyle + + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + For Each diagnostic In context.Diagnostics + context.RegisterCodeFix(New MyCodeAction( + VisualBasicCodeFixesResources.Simplify_object_creation, + Function(ct) FixAsync(context.Document, diagnostic, ct)), + diagnostic) + Next + Return Task.CompletedTask + End Function + + Protected Overrides Async Function FixAllAsync(document As Document, diagnostics As ImmutableArray(Of Diagnostic), editor As SyntaxEditor, cancellationToken As CancellationToken) As Task + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + For Each diagnostic In diagnostics + Dim node = DirectCast(root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie:=True), VariableDeclaratorSyntax) + Dim asNewClause = SyntaxFactory.AsNewClause(node.AsClause.AsKeyword, DirectCast(node.Initializer.Value, NewExpressionSyntax)) + Dim newNode = node.Update( + names:=node.Names, + asClause:=asNewClause, + initializer:=Nothing) + editor.ReplaceNode(node, newNode) + Next + End Function + + Private Class MyCodeAction + Inherits CustomCodeActions.DocumentChangeAction + + Friend Sub New(title As String, createChangedDocument As Func(Of CancellationToken, Task(Of Document))) + MyBase.New(title, createChangedDocument, NameOf(VisualBasicCodeFixesResources.Simplify_object_creation)) + End Sub + End Class + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems index 7a2e82a8ab6c5..23087c51f179c 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems +++ b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems @@ -29,6 +29,7 @@ + diff --git a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx index 4ee2ffe18b245..55cf90dc087a5 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx +++ b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx @@ -126,4 +126,7 @@ Convert 'GetType' to 'NameOf' + + Simplify object creation + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf index 633380f139348..bcd3d4b087769 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Odebrat nepotřebné importy + + Simplify object creation + Zjednodušit vytváření objektů + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf index b5860ad1418f1..5900857d3ad53 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Unnötige Import-Direktiven entfernen + + Simplify object creation + Objekterstellung vereinfachen + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf index 1173f00588dea..02cfca7413ead 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Quitar instrucciones Import innecesarias + + Simplify object creation + Simplificar la creación de objetos + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf index 0331570831ce5..e627375b74c2e 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Supprimer les importations superflues + + Simplify object creation + Simplifier la création d’objets + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf index 0821f2420fde3..f7cef938c6d29 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Rimuovi Import non necessari + + Simplify object creation + Semplificare la creazione di oggetti + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf index 99c4d80c9f4fb..69d9986604d83 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ 不要なインポートの削除 + + Simplify object creation + オブジェクトの作成を簡略化 + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf index c75af0f32aa15..bd9836bfcf7f4 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ 불필요한 Imports 제거 + + Simplify object creation + 개체 만들기 간소화 + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf index 5e69cb9f7e4de..e213bf9dbbea6 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Usuń niepotrzebne importy + + Simplify object creation + Upraszczanie tworzenia obiektu + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf index e1c9a969ee748..d9aeb0518cc22 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Remover Importações Desnecessárias + + Simplify object creation + Simplifique a criação de objetos + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf index c506aff4a1480..242cc0cfa04cf 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Удалить ненужные импорты + + Simplify object creation + Упрощение создания объектов + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf index 21ba41a702369..49c43f78ce8c2 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ Gereksiz İçeri Aktarmaları Kaldır + + Simplify object creation + Nesne oluşturmayı basitleştir + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf index baec379a402f7..86a5268bedcc3 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ 删除不必要的导入 + + Simplify object creation + 简化对象创建 + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf index 2efd5c0ed170d..3e4f444233511 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -17,6 +17,11 @@ 移除不必要的匯入 + + Simplify object creation + 簡化物件建立 + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb index 5743d36ea01b9..e7af02d00ec12 100644 --- a/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb @@ -148,19 +148,13 @@ Class C End Class") End Function - - Public Async Function PadLeftWithComplexConstantExpression() As Task - Await TestInRegularAndScriptAsync(" -Class C - Sub M(someValue As String) - Const someConstant As Integer = 1 - Dim v = $""prefix {someValue{|Unnecessary:[||].PadLeft(|}CByte(3.3) + someConstant{|Unnecessary:)|}} suffix"" - End Sub -End Class", " + + Public Async Function PadLeftWithNonLiteralConstantExpression() As Task + Await TestMissingInRegularAndScriptAsync(" Class C Sub M(someValue As String) Const someConstant As Integer = 1 - Dim v = $""prefix {someValue,CByte(3.3) + someConstant} suffix"" + Dim v = $""prefix {someValue[||].PadLeft(someConstant)} suffix"" End Sub End Class") End Function @@ -215,19 +209,13 @@ Class C End Class") End Function - - Public Async Function PadRightWithComplexConstantExpressionRequiringParentheses() As Task - Await TestInRegularAndScriptAsync(" -Class C - Sub M(someValue As String) - Const someConstant As Integer = 1 - Dim v = $""prefix {someValue{|Unnecessary:[||].PadRight(|}CByte(3.3) + someConstant{|Unnecessary:)|}} suffix"" - End Sub -End Class", " + + Public Async Function PadRightWithNonLiteralConstantExpression() As Task + Await TestMissingInRegularAndScriptAsync(" Class C Sub M(someValue As String) Const someConstant As Integer = 1 - Dim v = $""prefix {someValue,-(CByte(3.3) + someConstant)} suffix"" + Dim v = $""prefix {someValue[||].PadRight(someConstant)} suffix"" End Sub End Class") End Function diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb new file mode 100644 index 0000000000000..da68833928203 --- /dev/null +++ b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb @@ -0,0 +1,206 @@ +' 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 Microsoft.CodeAnalysis.VisualBasic.CodeStyle +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier( + Of Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationDiagnosticAnalyzer, + Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationCodeFixProvider) + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SimplifyObjectCreation + Public Class SimplifyObjectCreationTests + + Public Async Function SimplifyObjectCreation() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S()|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result As New S() + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_CodeStyleOptionTurnedOn() As Task + Dim code = " +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S()|] + return result + End Function +End Class +" + Dim fixedCode = " +Public Class S + Public Shared Function Create() As S + Dim result As New S() + return result + End Function +End Class +" + Dim test = New VerifyVB.Test With + { + .TestCode = code, + .FixedCode = fixedCode + } + test.Options.Add(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, True) + Await test.RunAsync() + End Function + + + Public Async Function SimplifyObjectCreation_CodeStyleOptionTurnedOff() As Task + Dim code = " +Public Class S + Public Shared Function Create() As S + Dim result As S = New S() + return result + End Function +End Class +" + Dim test = New VerifyVB.Test With + { + .TestCode = code, + .FixedCode = code + } + test.Options.Add(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, False) + Await test.RunAsync() + End Function + + + Public Async Function SimplifyObjectCreation_CallCtorWithoutParenthesis() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result As New S + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_PreserveAsAndNewCasing() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result as S = NEW S()|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result as NEW S() + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_MultipleDeclarators() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result as S = NEW S()|], [|result2 As S = New S|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result as NEW S(), result2 As New S + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_WithInitializer() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public X As Integer + + Public Shared Function Create() As S + Dim [|result As S = New S() With { .X = 0 }|] + return result + End Function +End Class +", " +Public Class S + Public X As Integer + + Public Shared Function Create() As S + Dim result As New S() With { .X = 0 } + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_FromCollectionInitializer() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Imports System.Collections.Generic + +Public Class S + Public Shared Function Create() As List(Of Integer) + Dim [|result As List(Of Integer) = New List(Of Integer)() From { 0, 1, 2, 3 }|] + return result + End Function +End Class +", " +Imports System.Collections.Generic + +Public Class S + Public Shared Function Create() As List(Of Integer) + Dim result As New List(Of Integer)() From { 0, 1, 2, 3 } + return result + End Function +End Class +") + End Function + + + Public Async Function TypeIsConverted_NoDiagnostic() As Task + Await VerifyVB.VerifyAnalyzerAsync(" +Public Interface IInterface +End Interface + +Public Class S : Implements IInterface + Public Shared Function Create() As S + Dim result as IInterface = NEW S() + return result + End Function +End Class +") + End Function + + + Public Async Function ArrayCreation_NoDiagnostic() As Task + Await VerifyVB.VerifyAnalyzerAsync(" +Public Class C + Public Sub M() + Dim x As String() = New String() { } + End Sub +End Class +") + End Function + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems b/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems index bd3fca0fe0f8d..12f9e5342d913 100644 --- a/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems +++ b/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems @@ -41,6 +41,7 @@ + diff --git a/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs b/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs index 2d97daa95719e..f7e4503bb47d1 100644 --- a/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs +++ b/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs @@ -97,8 +97,8 @@ private static void ComputeDeclarations( return; } - case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: { if (associatedSymbol is IMethodSymbol ctor) { diff --git a/src/Compilers/CSharp/Portable/Binder/AliasAndExternAliasDirective.cs b/src/Compilers/CSharp/Portable/Binder/AliasAndExternAliasDirective.cs index 220c1a4238de8..71b1576a353a4 100644 --- a/src/Compilers/CSharp/Portable/Binder/AliasAndExternAliasDirective.cs +++ b/src/Compilers/CSharp/Portable/Binder/AliasAndExternAliasDirective.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.Collections.Generic; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -16,12 +14,16 @@ namespace Microsoft.CodeAnalysis.CSharp internal struct AliasAndExternAliasDirective { public readonly AliasSymbol Alias; - public readonly ExternAliasDirectiveSyntax ExternAliasDirective; + public readonly SyntaxReference? ExternAliasDirectiveReference; + public readonly bool SkipInLookup; - public AliasAndExternAliasDirective(AliasSymbol alias, ExternAliasDirectiveSyntax externAliasDirective) + public AliasAndExternAliasDirective(AliasSymbol alias, ExternAliasDirectiveSyntax? externAliasDirective, bool skipInLookup) { this.Alias = alias; - this.ExternAliasDirective = externAliasDirective; + this.ExternAliasDirectiveReference = externAliasDirective?.GetReference(); + this.SkipInLookup = skipInLookup; } + + public ExternAliasDirectiveSyntax? ExternAliasDirective => (ExternAliasDirectiveSyntax?)ExternAliasDirectiveReference?.GetSyntax(); } } diff --git a/src/Compilers/CSharp/Portable/Binder/AliasAndUsingDirective.cs b/src/Compilers/CSharp/Portable/Binder/AliasAndUsingDirective.cs index 7c2d4df042564..484204277cc3f 100644 --- a/src/Compilers/CSharp/Portable/Binder/AliasAndUsingDirective.cs +++ b/src/Compilers/CSharp/Portable/Binder/AliasAndUsingDirective.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 Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -12,12 +10,14 @@ namespace Microsoft.CodeAnalysis.CSharp internal struct AliasAndUsingDirective { public readonly AliasSymbol Alias; - public readonly UsingDirectiveSyntax UsingDirective; + public readonly SyntaxReference? UsingDirectiveReference; - public AliasAndUsingDirective(AliasSymbol alias, UsingDirectiveSyntax usingDirective) + public AliasAndUsingDirective(AliasSymbol alias, UsingDirectiveSyntax? usingDirective) { this.Alias = alias; - this.UsingDirective = usingDirective; + this.UsingDirectiveReference = usingDirective?.GetReference(); } + + public UsingDirectiveSyntax? UsingDirective => (UsingDirectiveSyntax?)UsingDirectiveReference?.GetSyntax(); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs b/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs index 5e03ce4ba2657..f81b856f417dc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -31,6 +32,7 @@ public QueryUnboundLambdaState(Binder binder, RangeVariableMap rangeVariableMap, public override string ParameterName(int index) { return _parameters[index].Name; } public override bool ParameterIsDiscard(int index) { return false; } + public override SyntaxList ParameterAttributes(int index) => default; public override bool HasNames { get { return true; } } public override bool HasSignature { get { return true; } } public override bool HasExplicitlyTypedParameterList { get { return false; } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index fa45e0252ad1b..1a86b26b87e61 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1076,7 +1076,8 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV if (setMethod is null) { var containing = this.ContainingMemberOrLambda; - if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing)) + if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing) + && !isAllowedDespiteReadonly(receiver)) { Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol); return false; @@ -1203,6 +1204,17 @@ bool reportUseSite(MethodSymbol accessor) return false; } + static bool isAllowedDespiteReadonly(BoundExpression receiver) + { + // ok: anonymousType with { Property = ... } + if (receiver is BoundObjectOrCollectionValuePlaceholder && receiver.Type.IsAnonymousType) + { + return true; + } + + return false; + } + bool isAllowedInitOnlySet(BoundExpression receiver) { // ok: new C() { InitOnlyProperty = ... } @@ -2636,6 +2648,12 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin return escape; + case BoundKind.WithExpression: + var withExpression = (BoundWithExpression)expr; + + return Math.Max(GetValEscape(withExpression.Receiver, scopeOfTheContainingExpression), + GetValEscape(withExpression.InitializerExpression, scopeOfTheContainingExpression)); + case BoundKind.UnaryOperator: return GetValEscape(((BoundUnaryOperator)expr).Operand, scopeOfTheContainingExpression); @@ -3006,37 +3024,50 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint isRefEscape: false); case BoundKind.ObjectCreationExpression: - var objectCreation = (BoundObjectCreationExpression)expr; - var constructorSymbol = objectCreation.Constructor; + { + var objectCreation = (BoundObjectCreationExpression)expr; + var constructorSymbol = objectCreation.Constructor; + + var escape = CheckInvocationEscape( + objectCreation.Syntax, + constructorSymbol, + null, + constructorSymbol.Parameters, + objectCreation.Arguments, + objectCreation.ArgumentRefKindsOpt, + objectCreation.ArgsToParamsOpt, + checkingReceiver, + escapeFrom, + escapeTo, + diagnostics, + isRefEscape: false); + + var initializerExpr = objectCreation.InitializerExpressionOpt; + if (initializerExpr != null) + { + escape = escape && + CheckValEscape( + initializerExpr.Syntax, + initializerExpr, + escapeFrom, + escapeTo, + checkingReceiver: false, + diagnostics: diagnostics); + } - var escape = CheckInvocationEscape( - objectCreation.Syntax, - constructorSymbol, - null, - constructorSymbol.Parameters, - objectCreation.Arguments, - objectCreation.ArgumentRefKindsOpt, - objectCreation.ArgsToParamsOpt, - checkingReceiver, - escapeFrom, - escapeTo, - diagnostics, - isRefEscape: false); + return escape; + } - var initializerExpr = objectCreation.InitializerExpressionOpt; - if (initializerExpr != null) + case BoundKind.WithExpression: { - escape = escape && - CheckValEscape( - initializerExpr.Syntax, - initializerExpr, - escapeFrom, - escapeTo, - checkingReceiver: false, - diagnostics: diagnostics); - } + var withExpr = (BoundWithExpression)expr; + var escape = CheckValEscape(node, withExpr.Receiver, escapeFrom, escapeTo, checkingReceiver: false, diagnostics); - return escape; + var initializerExpr = withExpr.InitializerExpression; + escape = escape && CheckValEscape(initializerExpr.Syntax, initializerExpr, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics); + + return escape; + } case BoundKind.UnaryOperator: var unary = (BoundUnaryOperator)expr; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.cs b/src/Compilers/CSharp/Portable/Binder/Binder.cs index 17e6446a380c8..5ef85c1593ef0 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.cs @@ -407,12 +407,6 @@ internal virtual QuickAttributeChecker QuickAttributeChecker } } - internal virtual Imports GetImports(ConsList? basesBeingResolved) - { - RoslynDebug.Assert(Next is object); - return Next.GetImports(basesBeingResolved); - } - protected virtual bool InExecutableBinder { get diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index a59338a7047f5..3ed7b30065002 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -816,13 +816,13 @@ internal Binder VisitNamespaceDeclaration(NamespaceDeclarationSyntax parent, int return result; } - private Binder MakeNamespaceBinder(CSharpSyntaxNode node, NameSyntax name, Binder outer, bool inUsing) + private static Binder MakeNamespaceBinder(CSharpSyntaxNode node, NameSyntax name, Binder outer, bool inUsing) { - QualifiedNameSyntax dotted; - while ((dotted = name as QualifiedNameSyntax) != null) + if (name is QualifiedNameSyntax dotted) { outer = MakeNamespaceBinder(dotted.Left, dotted.Left, outer, inUsing: false); name = dotted.Right; + Debug.Assert(name is not QualifiedNameSyntax); } NamespaceOrTypeSymbol container; @@ -839,7 +839,17 @@ private Binder MakeNamespaceBinder(CSharpSyntaxNode node, NameSyntax name, Binde NamespaceSymbol ns = ((NamespaceSymbol)container).GetNestedNamespace(name); if ((object)ns == null) return outer; - return new InContainerBinder(ns, outer, node, inUsing: inUsing); + + if (node is NamespaceDeclarationSyntax namespaceDecl) + { + outer = AddInImportsBinders((SourceNamespaceSymbol)outer.Compilation.SourceModule.GetModuleNamespace(ns), namespaceDecl, outer, inUsing); + } + else + { + Debug.Assert(!inUsing); + } + + return new InContainerBinder(ns, outer); } public override Binder VisitCompilationUnit(CompilationUnitSyntax parent) @@ -883,29 +893,22 @@ internal Binder VisitCompilationUnit(CompilationUnitSyntax compilationUnit, bool // bool isSubmissionTree = compilation.IsSubmissionSyntaxTree(compilationUnit.SyntaxTree); - if (!isSubmissionTree) - { - result = result.WithAdditionalFlags(BinderFlags.InLoadedSyntaxTree); - } - - // This is declared here so it can be captured. It's initialized below. - InContainerBinder scriptClassBinder = null; + var scriptClass = compilation.ScriptClass; + bool isSubmissionClass = scriptClass.IsSubmissionClass; - if (inUsing) - { - result = result.WithAdditionalFlags(BinderFlags.InScriptUsing); - } - else + if (!inUsing) { - result = new InContainerBinder(container: null, next: result, imports: compilation.GlobalImports); - - // NB: This binder has a full Imports object, but only the non-alias imports are - // ever consumed. Aliases are actually checked in scriptClassBinder (below). - // Note: #loaded trees don't consume previous submission imports. - result = compilation.PreviousSubmission == null || !isSubmissionTree - ? new InContainerBinder(result, basesBeingResolved => scriptClassBinder.GetImports(basesBeingResolved)) - : new InContainerBinder(result, basesBeingResolved => - compilation.GetPreviousSubmissionImports().Concat(scriptClassBinder.GetImports(basesBeingResolved))); + result = WithUsingNamespacesAndTypesBinder.Create(compilation.GlobalImports, result, withImportChainEntry: true); + + if (isSubmissionClass) + { + // NB: Only the non-alias imports are + // ever consumed. Aliases are actually checked in InSubmissionClassBinder (below). + // Note: #loaded trees don't consume previous submission imports. + result = WithUsingNamespacesAndTypesBinder.Create((SourceNamespaceSymbol)compilation.SourceModule.GlobalNamespace, compilationUnit, result, + withPreviousSubmissionImports: compilation.PreviousSubmission != null && isSubmissionTree, + withImportChainEntry: true); + } } result = new InContainerBinder(compilation.GlobalNamespace, result); @@ -915,17 +918,28 @@ internal Binder VisitCompilationUnit(CompilationUnitSyntax compilationUnit, bool result = new HostObjectModelBinder(result); } - scriptClassBinder = new InContainerBinder(compilation.ScriptClass, result, compilationUnit, inUsing: inUsing); - result = scriptClassBinder; + if (isSubmissionClass) + { + result = new InSubmissionClassBinder(scriptClass, result, compilationUnit, inUsing); + } + else + { + result = AddInImportsBinders((SourceNamespaceSymbol)compilation.SourceModule.GlobalNamespace, compilationUnit, result, inUsing); + result = new InContainerBinder(scriptClass, result); + } } else { // // Binder chain in regular code: // - // + global namespace with top-level imports + // + compilation unit imported namespaces and types + // + compilation unit extern and using aliases + // + global namespace // - result = new InContainerBinder(compilation.GlobalNamespace, result, compilationUnit, inUsing: inUsing); + var globalNamespace = compilation.GlobalNamespace; + result = AddInImportsBinders((SourceNamespaceSymbol)compilation.SourceModule.GlobalNamespace, compilationUnit, result, inUsing); + result = new InContainerBinder(globalNamespace, result); if (!inUsing && SimpleProgramNamedTypeSymbol.GetSimpleProgramEntryPoint(compilation, compilationUnit, fallbackToMainEntryPoint: true) is SynthesizedSimpleProgramEntryPointSymbol simpleProgram) @@ -941,6 +955,22 @@ internal Binder VisitCompilationUnit(CompilationUnitSyntax compilationUnit, bool return result; } + private static Binder AddInImportsBinders(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next, bool inUsing) + { + Debug.Assert(declarationSyntax.IsKind(SyntaxKind.CompilationUnit) || declarationSyntax.IsKind(SyntaxKind.NamespaceDeclaration)); + + if (inUsing) + { + // Extern aliases are in scope + return WithExternAliasesBinder.Create(declaringSymbol, declarationSyntax, next); + } + else + { + // All imports are in scope + return WithExternAndUsingAliasesBinder.Create(declaringSymbol, declarationSyntax, WithUsingNamespacesAndTypesBinder.Create(declaringSymbol, declarationSyntax, next)); + } + } + internal static BinderCacheKey CreateBinderCacheKey(CSharpSyntaxNode node, NodeUsage usage) { Debug.Assert(BitArithmeticUtilities.CountBits((uint)usage) <= 1, "Not a flags enum."); @@ -1139,14 +1169,14 @@ private Binder GetParameterNameAttributeValueBinder(MemberDeclarationSyntax memb return new WithParametersBinder(method.Parameters, nextBinder); } - if (memberSyntax is RecordDeclarationSyntax { ParameterList: { ParameterCount: > 0 } } recordDeclSyntax) + if (memberSyntax is RecordDeclarationSyntax { ParameterList: { ParameterCount: > 0 } }) { Binder outerBinder = VisitCore(memberSyntax); - SourceNamedTypeSymbol recordType = ((NamespaceOrTypeSymbol)outerBinder.ContainingMemberOrLambda).GetSourceTypeMember(recordDeclSyntax); + SourceNamedTypeSymbol recordType = ((NamespaceOrTypeSymbol)outerBinder.ContainingMemberOrLambda).GetSourceTypeMember((TypeDeclarationSyntax)memberSyntax); var primaryConstructor = recordType.GetMembersUnordered().OfType().SingleOrDefault(); - if (primaryConstructor.SyntaxRef.SyntaxTree == recordDeclSyntax.SyntaxTree && - primaryConstructor.GetSyntax() == recordDeclSyntax) + if (primaryConstructor.SyntaxRef.SyntaxTree == memberSyntax.SyntaxTree && + primaryConstructor.GetSyntax() == memberSyntax) { return new WithParametersBinder(primaryConstructor.Parameters, nextBinder); } diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs index e648f7e3eac02..fd5996f50e13c 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs @@ -140,16 +140,17 @@ internal Binder GetBinder(SyntaxNode node, int position, CSharpSyntaxNode member internal InMethodBinder GetRecordConstructorInMethodBinder(SynthesizedRecordConstructor constructor) { - RecordDeclarationSyntax typeDecl = constructor.GetSyntax(); + var recordDecl = constructor.GetSyntax(); + Debug.Assert(recordDecl.IsKind(SyntaxKind.RecordDeclaration)); var extraInfo = NodeUsage.ConstructorBodyOrInitializer; - var key = BinderFactoryVisitor.CreateBinderCacheKey(typeDecl, extraInfo); + var key = BinderFactoryVisitor.CreateBinderCacheKey(recordDecl, extraInfo); if (!_binderCache.TryGetValue(key, out Binder resultBinder)) { // Ctors cannot be generic Debug.Assert(constructor.Arity == 0, "Generic Ctor, What to do?"); - resultBinder = new InMethodBinder(constructor, GetInRecordBodyBinder(typeDecl)); + resultBinder = new InMethodBinder(constructor, GetInRecordBodyBinder(recordDecl)); _binderCache.TryAdd(key, resultBinder); } @@ -159,6 +160,8 @@ internal InMethodBinder GetRecordConstructorInMethodBinder(SynthesizedRecordCons internal Binder GetInRecordBodyBinder(RecordDeclarationSyntax typeDecl) { + Debug.Assert(typeDecl.Kind() is SyntaxKind.RecordDeclaration); + BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); visitor.Initialize(position: typeDecl.SpanStart, memberDeclarationOpt: null, memberOpt: null); Binder resultBinder = visitor.VisitTypeDeclarationCore(typeDecl, NodeUsage.NamedTypeBodyOrTypeParameters); @@ -167,15 +170,7 @@ internal Binder GetInRecordBodyBinder(RecordDeclarationSyntax typeDecl) return resultBinder; } - /// - /// Returns binder that binds usings and aliases - /// - /// - /// Specify imports in the corresponding namespace, or - /// for top-level imports. - /// - /// True if the binder will be used to bind a using directive. - internal Binder GetImportsBinder(CSharpSyntaxNode unit, bool inUsing = false) + internal Binder GetInNamespaceBinder(CSharpSyntaxNode unit) { switch (unit.Kind()) { @@ -183,7 +178,7 @@ internal Binder GetImportsBinder(CSharpSyntaxNode unit, bool inUsing = false) { BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); visitor.Initialize(0, null, null); - Binder result = visitor.VisitNamespaceDeclaration((NamespaceDeclarationSyntax)unit, unit.SpanStart, inBody: true, inUsing: inUsing); + Binder result = visitor.VisitNamespaceDeclaration((NamespaceDeclarationSyntax)unit, unit.SpanStart, inBody: true, inUsing: false); _binderFactoryVisitorPool.Free(visitor); return result; } @@ -193,7 +188,7 @@ internal Binder GetImportsBinder(CSharpSyntaxNode unit, bool inUsing = false) { BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); visitor.Initialize(0, null, null); - Binder result = visitor.VisitCompilationUnit((CompilationUnitSyntax)unit, inUsing: inUsing, inScript: InScript); + Binder result = visitor.VisitCompilationUnit((CompilationUnitSyntax)unit, inUsing: false, inScript: InScript); _binderFactoryVisitorPool.Free(visitor); return result; } diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFlags.cs b/src/Compilers/CSharp/Portable/Binder/BinderFlags.cs index 7511bb5ae4af3..11c2a21d90c88 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFlags.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFlags.cs @@ -91,32 +91,21 @@ internal enum BinderFlags : uint /// IgnoreCorLibraryDuplicatedTypes = 1 << 26, - /// - /// When binding imports in scripts/submissions, using aliases (other than from the current submission) - /// are considered but other imports are not. - /// - InScriptUsing = 1 << 27, - - /// - /// In a file that has been included in the compilation via #load. - /// - InLoadedSyntaxTree = 1 << 28, - /// /// This is a , or has as its parent. /// - InContextualAttributeBinder = 1 << 29, + InContextualAttributeBinder = 1 << 27, /// /// Are we binding for the purpose of an Expression Evaluator /// - InEEMethodBinder = 1U << 30, + InEEMethodBinder = 1 << 28, /// /// Skip binding type arguments (we use instead). /// For example, currently used when type constraints are bound in some scenarios. /// - SuppressTypeArgumentBinding = 1u << 31, + SuppressTypeArgumentBinding = 1 << 29, // Groups diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 4bf44d7dbdf01..5debab2cea64a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -110,7 +109,6 @@ protected BoundExpression CreateConversion( if (conversion.Kind == ConversionKind.SwitchExpression) { - var convertedSwitch = ConvertSwitchExpression((BoundUnconvertedSwitchExpression)source, destination, conversionIfTargetTyped: conversion, diagnostics); return new BoundConversion( syntax, @@ -503,7 +501,7 @@ private BoundExpression CreateUserDefinedConversion( return finalConversion; } - private static BoundExpression CreateAnonymousFunctionConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics) + private BoundExpression CreateAnonymousFunctionConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics) { // We have a successful anonymous function conversion; rather than producing a node // which is a conversion on top of an unbound lambda, replace it with the bound @@ -513,19 +511,58 @@ private static BoundExpression CreateAnonymousFunctionConversion(SyntaxNode synt // UNDONE: is converted to a delegate that does not match. What to surface then? var unboundLambda = (UnboundLambda)source; - var boundLambda = unboundLambda.Bind((NamedTypeSymbol)destination); - diagnostics.AddRange(boundLambda.Diagnostics); + if (destination.SpecialType == SpecialType.System_Delegate || destination.IsNonGenericExpressionType()) + { + CheckFeatureAvailability(syntax, MessageID.IDS_FeatureInferredDelegateType, diagnostics); + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + var delegateType = unboundLambda.InferDelegateType(ref useSiteInfo); + BoundLambda boundLambda; + if (delegateType is { }) + { + if (destination.IsNonGenericExpressionType()) + { + delegateType = Compilation.GetWellKnownType(WellKnownType.System_Linq_Expressions_Expression_T).Construct(delegateType); + delegateType.AddUseSiteInfo(ref useSiteInfo); + } + boundLambda = unboundLambda.Bind(delegateType); + } + else + { + diagnostics.Add(ErrorCode.ERR_CannotInferDelegateType, syntax.GetLocation()); + delegateType = CreateErrorType(); + boundLambda = unboundLambda.BindForErrorRecovery(); + } + diagnostics.AddRange(boundLambda.Diagnostics); + var expr = createAnonymousFunctionConversion(syntax, source, boundLambda, conversion, isCast, conversionGroup, delegateType); + conversion = Conversions.ClassifyConversionFromExpression(expr, destination, ref useSiteInfo); + diagnostics.Add(syntax, useSiteInfo); + return CreateConversion(syntax, expr, conversion, isCast, conversionGroup, destination, diagnostics); + } + else + { +#if DEBUG + // Test inferring a delegate type for all callers. + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + _ = unboundLambda.InferDelegateType(ref discardedUseSiteInfo); +#endif + var boundLambda = unboundLambda.Bind((NamedTypeSymbol)destination); + diagnostics.AddRange(boundLambda.Diagnostics); + return createAnonymousFunctionConversion(syntax, source, boundLambda, conversion, isCast, conversionGroup, destination); + } - return new BoundConversion( - syntax, - boundLambda, - conversion, - @checked: false, - explicitCastInCode: isCast, - conversionGroup, - constantValueOpt: ConstantValue.NotAvailable, - type: destination) - { WasCompilerGenerated = source.WasCompilerGenerated }; + static BoundConversion createAnonymousFunctionConversion(SyntaxNode syntax, BoundExpression source, BoundLambda boundLambda, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination) + { + return new BoundConversion( + syntax, + boundLambda, + conversion, + @checked: false, + explicitCastInCode: isCast, + conversionGroup, + constantValueOpt: ConstantValue.NotAvailable, + type: destination) + { WasCompilerGenerated = source.WasCompilerGenerated }; + } } private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics) @@ -544,7 +581,29 @@ private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpr hasErrors = true; } - return new BoundConversion(syntax, group, conversion, @checked: false, explicitCastInCode: isCast, conversionGroup, constantValueOpt: ConstantValue.NotAvailable, type: destination, hasErrors: hasErrors) { WasCompilerGenerated = source.WasCompilerGenerated }; + if (destination.SpecialType == SpecialType.System_Delegate) + { + CheckFeatureAvailability(syntax, MessageID.IDS_FeatureInferredDelegateType, diagnostics); + // https://github.com/dotnet/roslyn/issues/52869: Avoid calculating the delegate type multiple times during conversion. + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + var delegateType = GetMethodGroupDelegateType(group, ref useSiteInfo); + var expr = createMethodGroupConversion(syntax, group, conversion, isCast, conversionGroup, delegateType!, hasErrors); + conversion = Conversions.ClassifyConversionFromExpression(expr, destination, ref useSiteInfo); + diagnostics.Add(syntax, useSiteInfo); + return CreateConversion(syntax, expr, conversion, isCast, conversionGroup, destination, diagnostics); + } + +#if DEBUG + // Test inferring a delegate type for all callers. + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + _ = GetMethodGroupDelegateType(group, ref discardedUseSiteInfo); +#endif + return createMethodGroupConversion(syntax, group, conversion, isCast, conversionGroup, destination, hasErrors); + + static BoundConversion createMethodGroupConversion(SyntaxNode syntax, BoundMethodGroup group, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, bool hasErrors) + { + return new BoundConversion(syntax, group, conversion, @checked: false, explicitCastInCode: isCast, conversionGroup, constantValueOpt: ConstantValue.NotAvailable, type: destination, hasErrors: hasErrors) { WasCompilerGenerated = group.WasCompilerGenerated }; + } } private BoundExpression CreateStackAllocConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics) @@ -1121,16 +1180,19 @@ private bool MethodGroupConversionHasErrors( TypeSymbol delegateOrFuncPtrType, BindingDiagnosticBag diagnostics) { - Debug.Assert(delegateOrFuncPtrType.TypeKind == TypeKind.Delegate || delegateOrFuncPtrType.TypeKind == TypeKind.FunctionPointer); + Debug.Assert(delegateOrFuncPtrType.SpecialType == SpecialType.System_Delegate || delegateOrFuncPtrType.TypeKind == TypeKind.Delegate || delegateOrFuncPtrType.TypeKind == TypeKind.FunctionPointer); Debug.Assert(conversion.Method is object); MethodSymbol selectedMethod = conversion.Method; var location = syntax.Location; - if (!MethodIsCompatibleWithDelegateOrFunctionPointer(receiverOpt, isExtensionMethod, selectedMethod, delegateOrFuncPtrType, location, diagnostics) || - MemberGroupFinalValidation(receiverOpt, selectedMethod, syntax, diagnostics, isExtensionMethod)) + if (delegateOrFuncPtrType.SpecialType != SpecialType.System_Delegate) { - return true; + if (!MethodIsCompatibleWithDelegateOrFunctionPointer(receiverOpt, isExtensionMethod, selectedMethod, delegateOrFuncPtrType, location, diagnostics) || + MemberGroupFinalValidation(receiverOpt, selectedMethod, syntax, diagnostics, isExtensionMethod)) + { + return true; + } } if (selectedMethod.IsConditional) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index bb69a83783e20..d09ed9112d04e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1322,7 +1322,6 @@ private BoundExpression BindTypeOf(TypeOfExpressionSyntax node, BindingDiagnosti BoundTypeExpression boundType = new BoundTypeExpression(typeSyntax, alias, typeWithAnnotations, type.IsErrorType()); return new BoundTypeOfOperator(node, boundType, null, this.GetWellKnownType(WellKnownType.System_Type, diagnostics, node), hasError); } -#nullable disable private BoundExpression BindSizeOf(SizeOfExpressionSyntax node, BindingDiagnosticBag diagnostics) { @@ -1340,7 +1339,6 @@ private BoundExpression BindSizeOf(SizeOfExpressionSyntax node, BindingDiagnosti this.GetSpecialType(SpecialType.System_Int32, diagnostics, node), hasErrors); } -#nullable enable /// true if managed type-related errors were found, otherwise false. internal static bool CheckManagedAddr(CSharpCompilation compilation, TypeSymbol type, Location location, BindingDiagnosticBag diagnostics) { @@ -3029,7 +3027,7 @@ private void CoerceArguments( } } - private TypeWithAnnotations GetCorrespondingParameterTypeWithAnnotations(ref MemberAnalysisResult result, ImmutableArray parameters, int arg) + private static TypeWithAnnotations GetCorrespondingParameterTypeWithAnnotations(ref MemberAnalysisResult result, ImmutableArray parameters, int arg) { int paramNum = result.ParameterFromArgument(arg); var type = parameters[paramNum].TypeWithAnnotations; @@ -3176,7 +3174,7 @@ private BoundExpression BindArrayDimension(ExpressionSyntax dimension, BindingDi var size = BindValue(dimension, diagnostics, BindValueKind.RValue); if (!size.HasAnyErrors) { - size = ConvertToArrayIndex(size, dimension, diagnostics, allowIndexAndRange: false); + size = ConvertToArrayIndex(size, diagnostics, allowIndexAndRange: false); if (IsNegativeConstantForArraySize(size)) { Error(diagnostics, ErrorCode.ERR_NegativeArraySize, dimension); @@ -5930,7 +5928,7 @@ private bool IsUsingAliasInScope(string name) var isSemanticModel = this.IsSemanticModelBinder; for (var chain = this.ImportChain; chain != null; chain = chain.ParentOpt) { - if (chain.Imports.IsUsingAlias(name, isSemanticModel)) + if (IsUsingAlias(chain.Imports.UsingAliases, name, isSemanticModel)) { return true; } @@ -7340,7 +7338,7 @@ private BoundExpression BindArrayAccess(ExpressionSyntax node, BoundExpression e { BoundExpression argument = arguments.Arguments[i]; - BoundExpression index = ConvertToArrayIndex(argument, node, diagnostics, allowIndexAndRange: rank == 1); + BoundExpression index = ConvertToArrayIndex(argument, diagnostics, allowIndexAndRange: rank == 1); convertedArguments[i] = index; // NOTE: Dev10 only warns if rank == 1 @@ -7369,7 +7367,7 @@ private BoundExpression BindArrayAccess(ExpressionSyntax node, BoundExpression e : new BoundArrayAccess(node, expr, convertedArguments.AsImmutableOrNull(), resultType, hasErrors: false); } - private BoundExpression ConvertToArrayIndex(BoundExpression index, SyntaxNode node, BindingDiagnosticBag diagnostics, bool allowIndexAndRange) + private BoundExpression ConvertToArrayIndex(BoundExpression index, BindingDiagnosticBag diagnostics, bool allowIndexAndRange) { Debug.Assert(index != null); @@ -7382,6 +7380,7 @@ private BoundExpression ConvertToArrayIndex(BoundExpression index, SyntaxNode no return ((BoundDiscardExpression)index).FailInference(this, diagnostics); } + var node = index.Syntax; var result = TryImplicitConversionToArrayIndex(index, SpecialType.System_Int32, node, diagnostics) ?? TryImplicitConversionToArrayIndex(index, SpecialType.System_UInt32, node, diagnostics) ?? @@ -7424,7 +7423,7 @@ private BoundExpression ConvertToArrayIndex(BoundExpression index, SyntaxNode no GenerateImplicitConversionError(diagnostics, node, failedConversion, index, int32); // Suppress any additional diagnostics - return CreateConversion(index.Syntax, index, failedConversion, isCast: false, conversionGroupOpt: null, destination: int32, diagnostics: BindingDiagnosticBag.Discarded); + return CreateConversion(node, index, failedConversion, isCast: false, conversionGroupOpt: null, destination: int32, diagnostics: BindingDiagnosticBag.Discarded); } return result; @@ -7533,7 +7532,7 @@ private BoundExpression BindPointerElementAccess(ExpressionSyntax node, BoundExp BoundExpression index = arguments[0]; - index = ConvertToArrayIndex(index, index.Syntax, diagnostics, allowIndexAndRange: false); + index = ConvertToArrayIndex(index, diagnostics, allowIndexAndRange: false); return new BoundPointerElementAccess(node, expr, index, CheckOverflowAtRuntime, pointedAtType, hasErrors); } @@ -7928,6 +7927,8 @@ candidate is PropertySymbol property && isIntNotByRef(original.Parameters[0])) { CheckImplicitThisCopyInReadOnlyMember(receiverOpt, lengthOrCountProperty.GetMethod, diagnostics); + ReportDiagnosticsIfObsolete(diagnostics, property, syntax, hasBaseReceiver: false); + ReportDiagnosticsIfObsolete(diagnostics, lengthOrCountProperty, syntax, hasBaseReceiver: false); // note: implicit copy check on the indexer accessor happens in CheckPropertyValueKind patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( syntax, @@ -7989,6 +7990,8 @@ method.OriginalDefinition is var original && { CheckImplicitThisCopyInReadOnlyMember(receiverOpt, lengthOrCountProperty.GetMethod, diagnostics); CheckImplicitThisCopyInReadOnlyMember(receiverOpt, method, diagnostics); + ReportDiagnosticsIfObsolete(diagnostics, method, syntax, hasBaseReceiver: false); + ReportDiagnosticsIfObsolete(diagnostics, lengthOrCountProperty, syntax, hasBaseReceiver: false); patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( syntax, receiverOpt, @@ -8314,7 +8317,151 @@ private MethodGroupResolution ResolveDefaultMethodGroup( } } - internal bool ReportDelegateInvokeUseSiteDiagnostic(BindingDiagnosticBag diagnostics, TypeSymbol possibleDelegateType, +#nullable enable + internal NamedTypeSymbol? GetMethodGroupDelegateType(BoundMethodGroup node, ref CompoundUseSiteInfo useSiteInfo) + { + if (GetUniqueSignatureFromMethodGroup(node) is { } method && + GetMethodGroupOrLambdaDelegateType(method.RefKind, method.ReturnsVoid ? default : method.ReturnTypeWithAnnotations, method.ParameterRefKinds, method.ParameterTypesWithAnnotations, ref useSiteInfo) is { } delegateType) + { + return delegateType; + } + return null; + } + + /// + /// Returns one of the methods from the method group if all methods in the method group + /// have the same signature, ignoring parameter names and custom modifiers. The particular + /// method returned is not important since the caller is interested in the signature only. + /// + private MethodSymbol? GetUniqueSignatureFromMethodGroup(BoundMethodGroup node) + { + MethodSymbol? method = null; + foreach (var m in node.Methods) + { + switch (node.ReceiverOpt) + { + case BoundTypeExpression: + if (!m.IsStatic) continue; + break; + case BoundThisReference { WasCompilerGenerated: true }: + break; + default: + if (m.IsStatic) continue; + break; + } + if (!isCandidateUnique(ref method, m)) + { + return null; + } + } + if (node.SearchExtensionMethods) + { + var receiver = node.ReceiverOpt!; + foreach (var scope in new ExtensionMethodScopes(this)) + { + var methodGroup = MethodGroup.GetInstance(); + PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, node.TypeArgumentsOpt, BindingDiagnosticBag.Discarded); + foreach (var m in methodGroup.Methods) + { + if (m.ReduceExtensionMethod(receiver.Type, Compilation) is { } reduced && + !isCandidateUnique(ref method, reduced)) + { + methodGroup.Free(); + return null; + } + } + methodGroup.Free(); + } + } + if (method is null) + { + return null; + } + int n = node.TypeArgumentsOpt.IsDefaultOrEmpty ? 0 : node.TypeArgumentsOpt.Length; + if (method.Arity != n) + { + return null; + } + else if (n > 0) + { + method = method.ConstructedFrom.Construct(node.TypeArgumentsOpt); + } + return method; + + static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) + { + if (method is null) + { + method = candidate; + return true; + } + if (MemberSignatureComparer.MethodGroupSignatureComparer.Equals(method, candidate)) + { + return true; + } + method = null; + return false; + } + } + + // This method was adapted from LoweredDynamicOperationFactory.GetDelegateType(). + // Consider using that method directly since it also synthesizes delegates if necessary. + internal NamedTypeSymbol? GetMethodGroupOrLambdaDelegateType( + RefKind returnRefKind, + TypeWithAnnotations returnTypeOpt, + ImmutableArray parameterRefKinds, + ImmutableArray parameterTypes, + ref CompoundUseSiteInfo useSiteInfo) + { + if (returnRefKind == RefKind.None && + (parameterRefKinds.IsDefault || parameterRefKinds.All(refKind => refKind == RefKind.None))) + { + var wkDelegateType = returnTypeOpt.HasType ? + WellKnownTypes.GetWellKnownFunctionDelegate(invokeArgumentCount: parameterTypes.Length) : + WellKnownTypes.GetWellKnownActionDelegate(invokeArgumentCount: parameterTypes.Length); + + if (wkDelegateType != WellKnownType.Unknown) + { + var delegateType = Compilation.GetWellKnownType(wkDelegateType); + delegateType.AddUseSiteInfo(ref useSiteInfo); + if (returnTypeOpt.HasType) + { + parameterTypes = parameterTypes.Add(returnTypeOpt); + } + if (parameterTypes.Length == 0) + { + return delegateType; + } + if (checkConstraints(Compilation, Conversions, delegateType, parameterTypes)) + { + return delegateType.Construct(parameterTypes); + } + } + } + + return null; + + static bool checkConstraints(CSharpCompilation compilation, ConversionsBase conversions, NamedTypeSymbol delegateType, ImmutableArray typeArguments) + { + var diagnosticsBuilder = ArrayBuilder.GetInstance(); + var typeParameters = delegateType.TypeParameters; + var substitution = new TypeMap(typeParameters, typeArguments); + ArrayBuilder? useSiteDiagnosticsBuilder = null; + var result = delegateType.CheckConstraints( + new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, NoLocation.Singleton, diagnostics: null, template: CompoundUseSiteInfo.Discarded), + substitution, + typeParameters, + typeArguments, + diagnosticsBuilder, + nullabilityDiagnosticsBuilderOpt: null, + ref useSiteDiagnosticsBuilder); + diagnosticsBuilder.Free(); + return result; + } + } +#nullable disable + + internal static bool ReportDelegateInvokeUseSiteDiagnostic(BindingDiagnosticBag diagnostics, TypeSymbol possibleDelegateType, Location location = null, SyntaxNode node = null) { Debug.Assert((location == null) ^ (node == null)); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs index f0b34370ee83c..7eb03207090c9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp @@ -42,12 +41,18 @@ private UnboundLambda AnalyzeAnonymousFunction( var names = default(ImmutableArray); var refKinds = default(ImmutableArray); var types = default(ImmutableArray); + var attributes = default(ImmutableArray>); var namesBuilder = ArrayBuilder.GetInstance(); ImmutableArray discardsOpt = default; SeparatedSyntaxList? parameterSyntaxList = null; bool hasSignature; + if (syntax is LambdaExpressionSyntax lambdaSyntax) + { + checkAttributes(syntax, lambdaSyntax.AttributeLists, diagnostics); + } + switch (syntax.Kind()) { default: @@ -87,6 +92,7 @@ private UnboundLambda AnalyzeAnonymousFunction( var typesBuilder = ArrayBuilder.GetInstance(); var refKindsBuilder = ArrayBuilder.GetInstance(); + var attributesBuilder = ArrayBuilder>.GetInstance(); // In the batch compiler case we probably should have given a syntax error if the // user did something like (int x, y)=>x+y -- but in the IDE scenario we might be in @@ -104,10 +110,7 @@ private UnboundLambda AnalyzeAnonymousFunction( underscoresCount++; } - foreach (var attributeList in p.AttributeLists) - { - Error(diagnostics, ErrorCode.ERR_AttributesNotAllowed, attributeList); - } + checkAttributes(syntax, p.AttributeLists, diagnostics); if (p.Default != null) { @@ -167,6 +170,7 @@ private UnboundLambda AnalyzeAnonymousFunction( namesBuilder.Add(p.Identifier.ValueText); typesBuilder.Add(type); refKindsBuilder.Add(refKind); + attributesBuilder.Add(syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression ? p.AttributeLists : default); } discardsOpt = computeDiscards(parameterSyntaxList.Value, underscoresCount); @@ -181,8 +185,14 @@ private UnboundLambda AnalyzeAnonymousFunction( refKinds = refKindsBuilder.ToImmutable(); } + if (attributesBuilder.Any(a => a.Count > 0)) + { + attributes = attributesBuilder.ToImmutable(); + } + typesBuilder.Free(); refKindsBuilder.Free(); + attributesBuilder.Free(); } if (hasSignature) @@ -192,7 +202,7 @@ private UnboundLambda AnalyzeAnonymousFunction( namesBuilder.Free(); - return new UnboundLambda(syntax, this, diagnostics.AccumulatesDependencies, refKinds, types, names, discardsOpt, isAsync, isStatic); + return new UnboundLambda(syntax, this, diagnostics.AccumulatesDependencies, attributes, refKinds, types, names, discardsOpt, isAsync, isStatic); static ImmutableArray computeDiscards(SeparatedSyntaxList parameters, int underscoresCount) { @@ -210,9 +220,24 @@ static ImmutableArray computeDiscards(SeparatedSyntaxList return discardsBuilder.ToImmutableAndFree(); } + + static void checkAttributes(AnonymousFunctionExpressionSyntax syntax, SyntaxList attributeLists, BindingDiagnosticBag diagnostics) + { + foreach (var attributeList in attributeLists) + { + if (syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression) + { + MessageID.IDS_FeatureLambdaAttributes.CheckFeatureAvailability(diagnostics, attributeList); + } + else + { + Error(diagnostics, syntax.Kind() == SyntaxKind.SimpleLambdaExpression ? ErrorCode.ERR_AttributesRequireParenthesizedLambdaExpression : ErrorCode.ERR_AttributesNotAllowed, attributeList); + } + } + } } - private void CheckParenthesizedLambdaParameters( + private static void CheckParenthesizedLambdaParameters( SeparatedSyntaxList parameterSyntaxList, BindingDiagnosticBag diagnostics) { if (parameterSyntaxList.Count > 0) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index ca2a7485cfc4c..944d773b9c6b1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -194,11 +195,8 @@ protected void LookupMembersInType(LookupResult result, TypeSymbol type, string case TypeKind.Delegate: case TypeKind.Array: case TypeKind.Dynamic: - this.LookupMembersInClass(result, type, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); - break; - case TypeKind.Submission: - this.LookupMembersInSubmissions(result, type, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); + this.LookupMembersInClass(result, type, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); break; case TypeKind.Error: @@ -254,32 +252,35 @@ private void LookupMembersInErrorType(LookupResult result, ErrorTypeSymbol error /// /// Note that indexers are not supported in script but we deal with them here to handle errors. /// - private void LookupMembersInSubmissions(LookupResult result, TypeSymbol submissionClass, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) + protected void LookupMembersInSubmissions(LookupResult result, TypeSymbol submissionClass, CompilationUnitSyntax declarationSyntax, bool inUsings, string name, int arity, ConsList basesBeingResolved, + LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) { LookupResult submissionSymbols = LookupResult.GetInstance(); LookupResult nonViable = LookupResult.GetInstance(); SymbolKind? lookingForOverloadsOfKind = null; + bool isSubmissionTree = Compilation.IsSubmissionSyntaxTree(declarationSyntax.SyntaxTree); + // TODO: optimize lookup (there might be many interactions in the chain) for (CSharpCompilation submission = Compilation; submission != null; submission = submission.PreviousSubmission) { submissionSymbols.Clear(); var isCurrentSubmission = submission == Compilation; - var considerUsings = !(isCurrentSubmission && this.Flags.Includes(BinderFlags.InScriptUsing)); + var considerUsings = !(isCurrentSubmission && inUsings); Imports submissionImports; if (!considerUsings) { submissionImports = Imports.Empty; } - else if (!this.Flags.Includes(BinderFlags.InLoadedSyntaxTree)) + else if (isSubmissionTree) { submissionImports = submission.GetSubmissionImports(); } else if (isCurrentSubmission) { - submissionImports = this.GetImports(basesBeingResolved); + submissionImports = ((SourceNamespaceSymbol)Compilation.SourceModule.GlobalNamespace).GetImports(declarationSyntax, basesBeingResolved); } else { @@ -295,7 +296,7 @@ private void LookupMembersInSubmissions(LookupResult result, TypeSymbol submissi // NB: It doesn't matter that submissionImports hasn't been expanded since we're not actually using the alias target. if (submissionSymbols.IsMultiViable && considerUsings && - submissionImports.IsUsingAlias(name, originalBinder.IsSemanticModelBinder)) + IsUsingAlias(submissionImports.UsingAliases, name, originalBinder.IsSemanticModelBinder)) { // using alias is ambiguous with another definition within the same submission iff the other definition is a 0-ary type or a non-type: Symbol existingDefinition = submissionSymbols.Symbols.First(); @@ -318,7 +319,7 @@ private void LookupMembersInSubmissions(LookupResult result, TypeSymbol submissi // NB: We diverge from InContainerBinder here and only look in aliases. // In submissions, regular usings are bubbled up to the outermost scope. - submissionImports.LookupSymbolInAliases(originalBinder, submissionSymbols, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); + LookupSymbolInAliases(submissionImports.UsingAliases, submissionImports.ExternAliases, originalBinder, submissionSymbols, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); } if (lookingForOverloadsOfKind == null) @@ -369,6 +370,76 @@ private void LookupMembersInSubmissions(LookupResult result, TypeSymbol submissi nonViable.Free(); } + protected bool IsUsingAlias(ImmutableDictionary usingAliases, string name, bool callerIsSemanticModel) + { + AliasAndUsingDirective node; + if (usingAliases.TryGetValue(name, out node)) + { + // This method is called by InContainerBinder.LookupSymbolsInSingleBinder to see if + // there's a conflict between an alias and a member. As a conflict may cause a + // speculative lambda binding to fail this is semantically relevant and we need to + // mark this using alias as referenced (and thus not something that can be removed). + MarkImportDirective(node.UsingDirectiveReference, callerIsSemanticModel); + return true; + } + + return false; + } + + protected void MarkImportDirective(SyntaxReference directive, bool callerIsSemanticModel) + { + if (directive != null && !callerIsSemanticModel) + { + Compilation.MarkImportDirectiveAsUsed(directive); + } + } + + protected void LookupSymbolInAliases( + ImmutableDictionary usingAliases, + ImmutableArray externAliases, + Binder originalBinder, + LookupResult result, + string name, + int arity, + ConsList basesBeingResolved, + LookupOptions options, + bool diagnose, + ref CompoundUseSiteInfo useSiteInfo) + { + bool callerIsSemanticModel = originalBinder.IsSemanticModelBinder; + + AliasAndUsingDirective alias; + if (usingAliases.TryGetValue(name, out alias)) + { + // Found a match in our list of normal aliases. Mark the alias as being seen so that + // it won't be reported to the user as something that can be removed. + var res = originalBinder.CheckViability(alias.Alias, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved); + if (res.Kind == LookupResultKind.Viable) + { + MarkImportDirective(alias.UsingDirectiveReference, callerIsSemanticModel); + } + + result.MergeEqual(res); + } + + foreach (var a in externAliases) + { + if (!a.SkipInLookup && a.Alias.Name == name) + { + // Found a match in our list of extern aliases. Mark the extern alias as being + // seen so that it won't be reported to the user as something that can be + // removed. + var res = originalBinder.CheckViability(a.Alias, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved); + if (res.Kind == LookupResultKind.Viable) + { + MarkImportDirective(a.ExternAliasDirectiveReference, callerIsSemanticModel); + } + + result.MergeEqual(res); + } + } + } + private static void LookupMembersInNamespace(LookupResult result, NamespaceSymbol ns, string name, int arity, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) { var members = GetCandidateMembers(ns, name, options, originalBinder); @@ -390,7 +461,7 @@ private void LookupExtensionMethodsInSingleBinder(ExtensionMethodScope scope, Lo { var methods = ArrayBuilder.GetInstance(); var binder = scope.Binder; - binder.GetCandidateExtensionMethods(scope.SearchUsingsNotNamespace, methods, name, arity, options, this); + binder.GetCandidateExtensionMethods(methods, name, arity, options, this); foreach (var method in methods) { @@ -692,7 +763,6 @@ internal virtual bool SupportsExtensionMethods /// to search the available members list in binding types that represent types, namespaces, and usings. /// internal virtual void GetCandidateExtensionMethods( - bool searchUsingsNotNamespace, ArrayBuilder methods, string name, int arity, @@ -1723,16 +1793,13 @@ private void AddMemberLookupSymbolsInfoInType(LookupSymbolsInfo result, TypeSymb case TypeKind.Delegate: case TypeKind.Array: case TypeKind.Dynamic: - this.AddMemberLookupSymbolsInfoInClass(result, type, options, originalBinder, type); - break; - case TypeKind.Submission: - this.AddMemberLookupSymbolsInfoInSubmissions(result, type, options, originalBinder); + this.AddMemberLookupSymbolsInfoInClass(result, type, options, originalBinder, type); break; } } - private void AddMemberLookupSymbolsInfoInSubmissions(LookupSymbolsInfo result, TypeSymbol scriptClass, LookupOptions options, Binder originalBinder) + protected void AddMemberLookupSymbolsInfoInSubmissions(LookupSymbolsInfo result, TypeSymbol scriptClass, bool inUsings, LookupOptions options, Binder originalBinder) { // TODO: we need tests // TODO: optimize lookup (there might be many interactions in the chain) @@ -1746,7 +1813,7 @@ private void AddMemberLookupSymbolsInfoInSubmissions(LookupSymbolsInfo result, T bool isCurrentSubmission = submission == Compilation; // If we are looking only for labels we do not need to search through the imports. - if ((options & LookupOptions.LabelsOnly) == 0 && !(isCurrentSubmission && this.Flags.Includes(BinderFlags.InScriptUsing))) + if ((options & LookupOptions.LabelsOnly) == 0 && !(isCurrentSubmission && inUsings)) { var submissionImports = submission.GetSubmissionImports(); if (!isCurrentSubmission) @@ -1754,9 +1821,41 @@ private void AddMemberLookupSymbolsInfoInSubmissions(LookupSymbolsInfo result, T submissionImports = Imports.ExpandPreviousSubmissionImports(submissionImports, Compilation); } - // NB: We diverge from InContainerBinder here and only look in aliases. + // NB: Here we only look in aliases. // In submissions, regular usings are bubbled up to the outermost scope. - submissionImports.AddLookupSymbolsInfoInAliases(result, options, originalBinder); + AddLookupSymbolsInfoInAliases(submissionImports.UsingAliases, submissionImports.ExternAliases, result, options, originalBinder); + } + } + } + + protected void AddLookupSymbolsInfoInAliases( + ImmutableDictionary usingAliases, + ImmutableArray externAliases, + LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) + { + // If we are looking only for labels we do not need to search through the imports. + if ((options & LookupOptions.LabelsOnly) == 0) + { + foreach (var pair in usingAliases) + { + addAliasSymbolToResult(result, pair.Value.Alias, options, originalBinder); + } + + foreach (var externAlias in externAliases) + { + if (!externAlias.SkipInLookup) + { + addAliasSymbolToResult(result, externAlias.Alias, options, originalBinder); + } + } + } + + static void addAliasSymbolToResult(LookupSymbolsInfo result, AliasSymbol aliasSymbol, LookupOptions options, Binder originalBinder) + { + var targetSymbol = aliasSymbol.GetAliasTarget(basesBeingResolved: null); + if (originalBinder.CanAddLookupSymbolInfo(targetSymbol, options, result, accessThroughType: null, aliasSymbol: aliasSymbol)) + { + result.AddSymbol(aliasSymbol, aliasSymbol.Name, 0); } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 61c74b3b5bb33..233fcb3033edf 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -1864,7 +1863,7 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag if (reason == LambdaConversionResult.ExpressionTreeFromAnonymousMethod) { - Debug.Assert(targetType.IsExpressionTree()); + Debug.Assert(targetType.IsGenericOrNonGenericExpressionType(out _)); Error(diagnostics, ErrorCode.ERR_AnonymousMethodToExpressionTree, syntax); return; } @@ -2261,7 +2260,19 @@ void reportMethodGroupErrors(BoundMethodGroup methodGroup, bool fromAddressOf) errorCode = ErrorCode.ERR_MethDelegateMismatch; break; default: - errorCode = fromAddressOf ? ErrorCode.ERR_AddressOfToNonFunctionPointer : ErrorCode.ERR_MethGrpToNonDel; + if (fromAddressOf) + { + errorCode = ErrorCode.ERR_AddressOfToNonFunctionPointer; + } + else if (targetType.SpecialType == SpecialType.System_Delegate) + { + Error(diagnostics, ErrorCode.ERR_CannotInferDelegateType, location); + return; + } + else + { + errorCode = ErrorCode.ERR_MethGrpToNonDel; + } break; } @@ -3338,12 +3349,13 @@ private BoundNode BindSimpleProgramCompilationUnit(CompilationUnitSyntax compila private BoundNode BindRecordConstructorBody(RecordDeclarationSyntax recordDecl, BindingDiagnosticBag diagnostics) { Debug.Assert(recordDecl.ParameterList is object); + Debug.Assert(recordDecl.IsKind(SyntaxKind.RecordDeclaration)); Binder bodyBinder = this.GetBinder(recordDecl); Debug.Assert(bodyBinder != null); BoundExpressionStatement initializer = null; - if (recordDecl.PrimaryConstructorBaseType is PrimaryConstructorBaseTypeSyntax baseWithArguments) + if (recordDecl.PrimaryConstructorBaseTypeIfClass is PrimaryConstructorBaseTypeSyntax baseWithArguments) { initializer = bodyBinder.BindConstructorInitializer(baseWithArguments, diagnostics); } @@ -3446,6 +3458,32 @@ internal virtual ImmutableArray Labels } } + /// + /// If this binder owns the scope that can declare extern aliases, a set of declared aliases should be returned (even if empty). + /// Otherwise, a default instance should be returned. + /// + internal virtual ImmutableArray ExternAliases + { + get + { + return default; + } + } + + /// + /// If this binder owns the scope that can declare using aliases, a set of declared aliases should be returned (even if empty). + /// Otherwise, a default instance should be returned. + /// Note, only aliases syntactically declared within the enclosing declaration are included. For example, global aliases + /// declared in a different compilation units are not included. + /// + internal virtual ImmutableArray UsingAliases + { + get + { + return default; + } + } + /// /// Perform a lookup for the specified method on the specified expression by attempting to invoke it /// diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs index 82c9aa8f10d22..933265fb66052 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs @@ -32,7 +32,15 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, BindingD } MethodSymbol? cloneMethod = null; - if (!receiverType.IsErrorType()) + if (receiverType.IsValueType && !receiverType.IsPointerOrFunctionPointer()) + { + CheckFeatureAvailability(syntax, MessageID.IDS_FeatureWithOnStructs, diagnostics); + } + else if (receiverType.IsAnonymousType) + { + CheckFeatureAvailability(syntax, MessageID.IDS_FeatureWithOnAnonymousTypes, diagnostics); + } + else if (!receiverType.IsErrorType()) { CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); @@ -40,7 +48,7 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, BindingD if (cloneMethod is null) { hasErrors = true; - diagnostics.Add(ErrorCode.ERR_NoSingleCloneMethod, syntax.Expression.Location, receiverType); + diagnostics.Add(ErrorCode.ERR_CannotClone, syntax.Expression.Location, receiverType); } else { @@ -57,7 +65,7 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, BindingD isForNewInstance: true, diagnostics); - // N.B. Since we only don't parse nested initializers in syntax there should be no extra + // N.B. Since we don't parse nested initializers in syntax there should be no extra // errors we need to check for here. return new BoundWithExpression( diff --git a/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs b/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs index bd8d1c5fd1d8d..5c78fd6bb40a3 100644 --- a/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs @@ -40,11 +40,6 @@ internal override QuickAttributeChecker QuickAttributeChecker } } - internal override Imports GetImports(ConsList? basesBeingResolved) - { - return Imports.Empty; - } - protected override SourceLocalSymbol? LookupLocal(SyntaxToken nameToken) { return null; diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 9f9f94b24a6ff..d154c99e833e4 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -651,7 +651,47 @@ private BoundDecisionDag MakeBoundDecisionDag(SyntaxNode syntax, ImmutableArray< var rootDecisionDagNode = decisionDag.RootNode.Dag; RoslynDebug.Assert(rootDecisionDagNode != null); - return new BoundDecisionDag(rootDecisionDagNode.Syntax, rootDecisionDagNode); + var boundDecisionDag = new BoundDecisionDag(rootDecisionDagNode.Syntax, rootDecisionDagNode); +#if DEBUG + // Note that this uses the custom equality in `BoundDagEvaluation` + // to make "equivalent" evaluation nodes share the same ID. + var nextTempNumber = 0; + var tempIdentifierMap = PooledDictionary.GetInstance(); + + var sortedBoundDagNodes = boundDecisionDag.TopologicallySortedNodes; + for (int i = 0; i < sortedBoundDagNodes.Length; i++) + { + var node = sortedBoundDagNodes[i]; + node.Id = i; + switch (node) + { + case BoundEvaluationDecisionDagNode { Evaluation: { Id: -1 } evaluation }: + evaluation.Id = tempIdentifier(evaluation); + // Note that "equivalent" evaluations may be different object instances. + // Therefore we have to dig into the Input.Source of evaluations and tests to set their IDs. + if (evaluation.Input.Source is { Id: -1 } source) + { + source.Id = tempIdentifier(source); + } + break; + case BoundTestDecisionDagNode { Test: var test }: + if (test.Input.Source is { Id: -1 } testSource) + { + testSource.Id = tempIdentifier(testSource); + } + break; + } + } + tempIdentifierMap.Free(); + + int tempIdentifier(BoundDagEvaluation e) + { + return tempIdentifierMap.TryGetValue(e, out int value) + ? value + : tempIdentifierMap[e] = ++nextTempNumber; + } +#endif + return boundDecisionDag; } /// diff --git a/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs b/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs index 048543be96a21..1e1f691e5c28c 100644 --- a/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs @@ -398,8 +398,9 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no public override void VisitRecordDeclaration(RecordDeclarationSyntax node) { Debug.Assert(node.ParameterList is object); + Debug.Assert(node.IsKind(SyntaxKind.RecordDeclaration)); - if (node.PrimaryConstructorBaseType is PrimaryConstructorBaseTypeSyntax baseWithArguments) + if (node.PrimaryConstructorBaseTypeIfClass is PrimaryConstructorBaseTypeSyntax baseWithArguments) { VisitNodeToBind(baseWithArguments); } diff --git a/src/Compilers/CSharp/Portable/Binder/ExtensionMethodScope.cs b/src/Compilers/CSharp/Portable/Binder/ExtensionMethodScope.cs index 3e2890c2fa8de..3a9f41a8d1242 100644 --- a/src/Compilers/CSharp/Portable/Binder/ExtensionMethodScope.cs +++ b/src/Compilers/CSharp/Portable/Binder/ExtensionMethodScope.cs @@ -14,12 +14,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal struct ExtensionMethodScope { public readonly Binder Binder; - public readonly bool SearchUsingsNotNamespace; - public ExtensionMethodScope(Binder binder, bool searchUsingsNotNamespace) + public ExtensionMethodScope(Binder binder) { this.Binder = binder; - this.SearchUsingsNotNamespace = searchUsingsNotNamespace; } } @@ -70,17 +68,8 @@ public bool MoveNext() else { var binder = _current.Binder; - if (!_current.SearchUsingsNotNamespace) - { - // Return a scope for the same Binder that was previously exposed - // for the namespace, this time exposed for the usings. - _current = new ExtensionMethodScope(binder, searchUsingsNotNamespace: true); - } - else - { - // Return a scope for the next Binder that supports extension methods. - _current = GetNextScope(binder.Next); - } + // Return a scope for the next Binder that supports extension methods. + _current = GetNextScope(binder.Next); } return (_current.Binder != null); @@ -92,7 +81,7 @@ private static ExtensionMethodScope GetNextScope(Binder binder) { if (scope.SupportsExtensionMethods) { - return new ExtensionMethodScope(scope, searchUsingsNotNamespace: false); + return new ExtensionMethodScope(scope); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Imports.cs b/src/Compilers/CSharp/Portable/Binder/Imports.cs index 7ec8af1b7afcd..e9fe2d7767f0d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Imports.cs +++ b/src/Compilers/CSharp/Portable/Binder/Imports.cs @@ -25,37 +25,25 @@ namespace Microsoft.CodeAnalysis.CSharp internal sealed class Imports { internal static readonly Imports Empty = new Imports( - null, ImmutableDictionary.Empty, ImmutableArray.Empty, - ImmutableArray.Empty, - null); - - private readonly CSharpCompilation _compilation; - private readonly DiagnosticBag _diagnostics; - - // completion state that tracks whether validation was done/not done/currently in process. - private SymbolCompletionState _state; + ImmutableArray.Empty); public readonly ImmutableDictionary UsingAliases; public readonly ImmutableArray Usings; public readonly ImmutableArray ExternAliases; private Imports( - CSharpCompilation compilation, ImmutableDictionary usingAliases, ImmutableArray usings, - ImmutableArray externs, - DiagnosticBag diagnostics) + ImmutableArray externs) { Debug.Assert(usingAliases != null); Debug.Assert(!usings.IsDefault); Debug.Assert(!externs.IsDefault); - _compilation = compilation; this.UsingAliases = usingAliases; this.Usings = usings; - _diagnostics = diagnostics; this.ExternAliases = externs; } @@ -68,283 +56,6 @@ internal string GetDebuggerDisplay() } - public static Imports FromSyntax( - CSharpSyntaxNode declarationSyntax, - InContainerBinder binder, - ConsList basesBeingResolved, - bool inUsing) - { - SyntaxList usingDirectives; - SyntaxList externAliasDirectives; - if (declarationSyntax.Kind() == SyntaxKind.CompilationUnit) - { - var compilationUnit = (CompilationUnitSyntax)declarationSyntax; - // using directives are not in scope within using directives - usingDirectives = inUsing ? default(SyntaxList) : compilationUnit.Usings; - externAliasDirectives = compilationUnit.Externs; - } - else if (declarationSyntax.Kind() == SyntaxKind.NamespaceDeclaration) - { - var namespaceDecl = (NamespaceDeclarationSyntax)declarationSyntax; - // using directives are not in scope within using directives - usingDirectives = inUsing ? default(SyntaxList) : namespaceDecl.Usings; - externAliasDirectives = namespaceDecl.Externs; - } - else - { - return Empty; - } - - if (usingDirectives.Count == 0 && externAliasDirectives.Count == 0) - { - return Empty; - } - - // define all of the extern aliases first. They may used by the target of a using - - // using Bar=Goo::Bar; - // using Goo::Baz; - // extern alias Goo; - - var diagnostics = new DiagnosticBag(); - - var compilation = binder.Compilation; - - var externAliases = BuildExternAliases(externAliasDirectives, binder, diagnostics); - var usings = ArrayBuilder.GetInstance(); - ImmutableDictionary.Builder usingAliases = null; - if (usingDirectives.Count > 0) - { - // A binder that contains the extern aliases but not the usings. The resolution of the target of a using directive or alias - // should not make use of other peer usings. - Binder usingsBinder; - if (declarationSyntax.SyntaxTree.Options.Kind != SourceCodeKind.Regular) - { - usingsBinder = compilation.GetBinderFactory(declarationSyntax.SyntaxTree).GetImportsBinder(declarationSyntax, inUsing: true); - } - else - { - var imports = externAliases.Length == 0 - ? Empty - : new Imports( - compilation, - ImmutableDictionary.Empty, - ImmutableArray.Empty, - externAliases, - diagnostics: null); - usingsBinder = new InContainerBinder(binder.Container, binder.Next, imports); - } - - var uniqueUsings = SpecializedSymbolCollections.GetPooledSymbolHashSetInstance(); - - foreach (var usingDirective in usingDirectives) - { - compilation.RecordImport(usingDirective); - - if (usingDirective.Alias != null) - { - SyntaxToken identifier = usingDirective.Alias.Name.Identifier; - Location location = usingDirective.Alias.Name.Location; - - if (identifier.ContextualKind() == SyntaxKind.GlobalKeyword) - { - diagnostics.Add(ErrorCode.WRN_GlobalAliasDefn, location); - } - - if (usingDirective.StaticKeyword != default(SyntaxToken)) - { - diagnostics.Add(ErrorCode.ERR_NoAliasHere, location); - } - - SourceMemberContainerTypeSymbol.ReportTypeNamedRecord(identifier.Text, compilation, diagnostics, location); - - string identifierValueText = identifier.ValueText; - if (usingAliases != null && usingAliases.ContainsKey(identifierValueText)) - { - // Suppress diagnostics if we're already broken. - if (!usingDirective.Name.IsMissing) - { - // The using alias '{0}' appeared previously in this namespace - diagnostics.Add(ErrorCode.ERR_DuplicateAlias, location, identifierValueText); - } - } - else - { - // an O(m*n) algorithm here but n (number of extern aliases) will likely be very small. - foreach (var externAlias in externAliases) - { - if (externAlias.Alias.Name == identifierValueText) - { - // The using alias '{0}' appeared previously in this namespace - diagnostics.Add(ErrorCode.ERR_DuplicateAlias, usingDirective.Location, identifierValueText); - break; - } - } - - if (usingAliases == null) - { - usingAliases = ImmutableDictionary.CreateBuilder(); - } - - // construct the alias sym with the binder for which we are building imports. That - // way the alias target can make use of extern alias definitions. - usingAliases.Add(identifierValueText, new AliasAndUsingDirective(new AliasSymbol(usingsBinder, usingDirective.Name, usingDirective.Alias), usingDirective)); - } - } - else - { - if (usingDirective.Name.IsMissing) - { - //don't try to lookup namespaces inserted by parser error recovery - continue; - } - - var directiveDiagnostics = BindingDiagnosticBag.GetInstance(); - - var declarationBinder = usingsBinder.WithAdditionalFlags(BinderFlags.SuppressConstraintChecks); - var imported = declarationBinder.BindNamespaceOrTypeSymbol(usingDirective.Name, directiveDiagnostics, basesBeingResolved).NamespaceOrTypeSymbol; - - if (imported.Kind == SymbolKind.Namespace) - { - Debug.Assert(directiveDiagnostics.DependenciesBag.IsEmpty()); - - if (usingDirective.StaticKeyword != default(SyntaxToken)) - { - diagnostics.Add(ErrorCode.ERR_BadUsingType, usingDirective.Name.Location, imported); - } - else if (!uniqueUsings.Add(imported)) - { - diagnostics.Add(ErrorCode.WRN_DuplicateUsing, usingDirective.Name.Location, imported); - } - else - { - usings.Add(new NamespaceOrTypeAndUsingDirective(imported, usingDirective, dependencies: default)); - } - } - else if (imported.Kind == SymbolKind.NamedType) - { - if (usingDirective.StaticKeyword == default(SyntaxToken)) - { - diagnostics.Add(ErrorCode.ERR_BadUsingNamespace, usingDirective.Name.Location, imported); - } - else - { - var importedType = (NamedTypeSymbol)imported; - if (uniqueUsings.Contains(importedType)) - { - diagnostics.Add(ErrorCode.WRN_DuplicateUsing, usingDirective.Name.Location, importedType); - } - else - { - declarationBinder.ReportDiagnosticsIfObsolete(diagnostics, importedType, usingDirective.Name, hasBaseReceiver: false); - - uniqueUsings.Add(importedType); - usings.Add(new NamespaceOrTypeAndUsingDirective(importedType, usingDirective, directiveDiagnostics.DependenciesBag.ToImmutableArray())); - } - } - } - else if (imported.Kind != SymbolKind.ErrorType) - { - // Do not report additional error if the symbol itself is erroneous. - - // error: '' is a '' but is used as 'type or namespace' - diagnostics.Add(ErrorCode.ERR_BadSKknown, usingDirective.Name.Location, - usingDirective.Name, - imported.GetKindText(), - MessageID.IDS_SK_TYPE_OR_NAMESPACE.Localize()); - } - - diagnostics.AddRange(directiveDiagnostics.DiagnosticBag); - directiveDiagnostics.Free(); - } - } - - uniqueUsings.Free(); - } - - if (diagnostics.IsEmptyWithoutResolution) - { - diagnostics = null; - } - - return new Imports(compilation, usingAliases.ToImmutableDictionaryOrEmpty(), usings.ToImmutableAndFree(), externAliases, diagnostics); - } - - public static Imports FromGlobalUsings(CSharpCompilation compilation) - { - var usings = compilation.Options.Usings; - - if (usings.Length == 0 && compilation.PreviousSubmission == null) - { - return Empty; - } - - var diagnostics = new DiagnosticBag(); - var usingsBinder = new InContainerBinder(compilation.GlobalNamespace, new BuckStopsHereBinder(compilation)); - var boundUsings = ArrayBuilder.GetInstance(); - var uniqueUsings = PooledHashSet.GetInstance(); - - foreach (string @using in usings) - { - if (!@using.IsValidClrNamespaceName()) - { - continue; - } - - string[] identifiers = @using.Split('.'); - NameSyntax qualifiedName = SyntaxFactory.IdentifierName(identifiers[0]); - - for (int j = 1; j < identifiers.Length; j++) - { - qualifiedName = SyntaxFactory.QualifiedName(left: qualifiedName, right: SyntaxFactory.IdentifierName(identifiers[j])); - } - - var directiveDiagnostics = BindingDiagnosticBag.GetInstance(); - - var imported = usingsBinder.BindNamespaceOrTypeSymbol(qualifiedName, directiveDiagnostics).NamespaceOrTypeSymbol; - if (uniqueUsings.Add(imported)) - { - boundUsings.Add(new NamespaceOrTypeAndUsingDirective(imported, null, dependencies: directiveDiagnostics.DependenciesBag.ToImmutableArray())); - } - - diagnostics.AddRange(directiveDiagnostics.DiagnosticBag); - directiveDiagnostics.Free(); - } - - if (diagnostics.IsEmptyWithoutResolution) - { - diagnostics = null; - } - - var previousSubmissionImports = compilation.PreviousSubmission?.GlobalImports; - if (previousSubmissionImports != null) - { - // Currently, only usings are supported. - Debug.Assert(previousSubmissionImports.UsingAliases.IsEmpty); - Debug.Assert(previousSubmissionImports.ExternAliases.IsEmpty); - - var expandedImports = ExpandPreviousSubmissionImports(previousSubmissionImports, compilation); - - foreach (var previousUsing in expandedImports.Usings) - { - if (uniqueUsings.Add(previousUsing.NamespaceOrType)) - { - boundUsings.Add(previousUsing); - } - } - } - - uniqueUsings.Free(); - - if (boundUsings.Count == 0) - { - boundUsings.Free(); - return Empty; - } - - return new Imports(compilation, ImmutableDictionary.Empty, boundUsings.ToImmutableAndFree(), ImmutableArray.Empty, diagnostics); - } - // TODO (https://github.com/dotnet/roslyn/issues/5517): skip namespace expansion if references haven't changed. internal static Imports ExpandPreviousSubmissionImports(Imports previousSubmissionImports, CSharpCompilation newSubmission) { @@ -354,11 +65,8 @@ internal static Imports ExpandPreviousSubmissionImports(Imports previousSubmissi } Debug.Assert(previousSubmissionImports != null); - Debug.Assert(previousSubmissionImports._compilation.IsSubmission); Debug.Assert(newSubmission.IsSubmission); - var expandedGlobalNamespace = newSubmission.GlobalNamespace; - var expandedAliases = ImmutableDictionary.Empty; if (!previousSubmissionImports.UsingAliases.IsEmpty) { @@ -372,11 +80,24 @@ internal static Imports ExpandPreviousSubmissionImports(Imports previousSubmissi expandedAliases = expandedAliasesBuilder.ToImmutable(); } - var expandedUsings = ImmutableArray.Empty; - if (!previousSubmissionImports.Usings.IsEmpty) + var expandedUsings = ExpandPreviousSubmissionImports(previousSubmissionImports.Usings, newSubmission); + + return Imports.Create( + expandedAliases, + expandedUsings, + previousSubmissionImports.ExternAliases); + } + + internal static ImmutableArray ExpandPreviousSubmissionImports(ImmutableArray previousSubmissionUsings, CSharpCompilation newSubmission) + { + Debug.Assert(newSubmission.IsSubmission); + + if (!previousSubmissionUsings.IsEmpty) { - var expandedUsingsBuilder = ArrayBuilder.GetInstance(previousSubmissionImports.Usings.Length); - foreach (var previousUsing in previousSubmissionImports.Usings) + var expandedUsingsBuilder = ArrayBuilder.GetInstance(previousSubmissionUsings.Length); + var expandedGlobalNamespace = newSubmission.GlobalNamespace; + + foreach (var previousUsing in previousSubmissionUsings) { var previousTarget = previousUsing.NamespaceOrType; if (previousTarget.IsType) @@ -389,15 +110,11 @@ internal static Imports ExpandPreviousSubmissionImports(Imports previousSubmissi expandedUsingsBuilder.Add(new NamespaceOrTypeAndUsingDirective(expandedNamespace, previousUsing.UsingDirective, dependencies: default)); } } - expandedUsings = expandedUsingsBuilder.ToImmutableAndFree(); + + return expandedUsingsBuilder.ToImmutableAndFree(); } - return new Imports( - newSubmission, - expandedAliases, - expandedUsings, - previousSubmissionImports.ExternAliases, - diagnostics: null); + return previousSubmissionUsings; } internal static NamespaceSymbol ExpandPreviousSubmissionNamespace(NamespaceSymbol originalNamespace, NamespaceSymbol expandedGlobalNamespace) @@ -429,13 +146,21 @@ internal static NamespaceSymbol ExpandPreviousSubmissionNamespace(NamespaceSymbo return expandedNamespace; } - public static Imports FromCustomDebugInfo( - CSharpCompilation compilation, + public static Imports Create( ImmutableDictionary usingAliases, ImmutableArray usings, ImmutableArray externs) { - return new Imports(compilation, usingAliases, usings, externs, diagnostics: null); + Debug.Assert(usingAliases != null); + Debug.Assert(!usings.IsDefault); + Debug.Assert(!externs.IsDefault); + + if (usingAliases.IsEmpty && usings.IsEmpty && externs.IsEmpty) + { + return Empty; + } + + return new Imports(usingAliases, usings, externs); } /// @@ -455,13 +180,11 @@ internal Imports Concat(Imports otherImports) return this; } - Debug.Assert(_compilation == otherImports._compilation); - var usingAliases = this.UsingAliases.SetItems(otherImports.UsingAliases); // NB: SetItems, rather than AddRange var usings = this.Usings.AddRange(otherImports.Usings).Distinct(UsingTargetComparer.Instance); var externAliases = ConcatExternAliases(this.ExternAliases, otherImports.ExternAliases); - return new Imports(_compilation, usingAliases, usings, externAliases, diagnostics: null); + return Imports.Create(usingAliases, usings, externAliases); } private static ImmutableArray ConcatExternAliases(ImmutableArray externs1, ImmutableArray externs2) @@ -481,466 +204,6 @@ private static ImmutableArray ConcatExternAliases( return externs1.WhereAsArray((e, replacedExternAliases) => !replacedExternAliases.Contains(e.Alias.Name), replacedExternAliases).AddRange(externs2); } - private static ImmutableArray BuildExternAliases( - SyntaxList syntaxList, - InContainerBinder binder, - DiagnosticBag diagnostics) - { - CSharpCompilation compilation = binder.Compilation; - - var builder = ArrayBuilder.GetInstance(); - - foreach (ExternAliasDirectiveSyntax aliasSyntax in syntaxList) - { - compilation.RecordImport(aliasSyntax); - - // Extern aliases not allowed in interactive submissions: - if (compilation.IsSubmission) - { - diagnostics.Add(ErrorCode.ERR_ExternAliasNotAllowed, aliasSyntax.Location); - continue; - } - - // some n^2 action, but n should be very small. - foreach (var existingAlias in builder) - { - if (existingAlias.Alias.Name == aliasSyntax.Identifier.ValueText) - { - diagnostics.Add(ErrorCode.ERR_DuplicateAlias, existingAlias.Alias.Locations[0], existingAlias.Alias.Name); - break; - } - } - - if (aliasSyntax.Identifier.ContextualKind() == SyntaxKind.GlobalKeyword) - { - diagnostics.Add(ErrorCode.ERR_GlobalExternAlias, aliasSyntax.Identifier.GetLocation()); - } - - builder.Add(new AliasAndExternAliasDirective(new AliasSymbol(binder, aliasSyntax), aliasSyntax)); - } - - return builder.ToImmutableAndFree(); - } - - private void MarkImportDirective(CSharpSyntaxNode directive, bool callerIsSemanticModel) - { - MarkImportDirective(_compilation, directive, callerIsSemanticModel); - } - - private static void MarkImportDirective(CSharpCompilation compilation, CSharpSyntaxNode directive, bool callerIsSemanticModel) - { - Debug.Assert(compilation != null); // If any directives are used, then there must be a compilation. - if (directive != null && !callerIsSemanticModel) - { - compilation.MarkImportDirectiveAsUsed(directive); - } - } - - internal void Complete(CancellationToken cancellationToken) - { - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - var incompletePart = _state.NextIncompletePart; - switch (incompletePart) - { - case CompletionPart.StartValidatingImports: - { - if (_state.NotePartComplete(CompletionPart.StartValidatingImports)) - { - Validate(); - _state.NotePartComplete(CompletionPart.FinishValidatingImports); - } - } - break; - - case CompletionPart.FinishValidatingImports: - // some other thread has started validating imports (otherwise we would be in the case above) so - // we just wait for it to both finish and report the diagnostics. - Debug.Assert(_state.HasComplete(CompletionPart.StartValidatingImports)); - _state.SpinWaitComplete(CompletionPart.FinishValidatingImports, cancellationToken); - break; - - case CompletionPart.None: - return; - - default: - // any other values are completion parts intended for other kinds of symbols - _state.NotePartComplete(CompletionPart.All & ~CompletionPart.ImportsAll); - break; - } - - _state.SpinWaitComplete(incompletePart, cancellationToken); - } - } - - private void Validate() - { - if (this == Empty) - { - return; - } - - DiagnosticBag semanticDiagnostics = _compilation.DeclarationDiagnostics; - - // Check constraints within named aliases. - var diagnostics = BindingDiagnosticBag.GetInstance(); - - // Force resolution of named aliases. - foreach (var (_, alias) in UsingAliases) - { - NamespaceOrTypeSymbol target = alias.Alias.GetAliasTarget(basesBeingResolved: null); - - diagnostics.Clear(); - diagnostics.AddRange(alias.Alias.AliasTargetDiagnostics); - - alias.Alias.CheckConstraints(diagnostics); - - semanticDiagnostics.AddRange(diagnostics.DiagnosticBag); - recordImportDependencies(alias.UsingDirective, target); - } - - var corLibrary = _compilation.SourceAssembly.CorLibrary; - var conversions = new TypeConversions(corLibrary); - foreach (var @using in Usings) - { - diagnostics.Clear(); - diagnostics.AddDependencies(@using.Dependencies); - - NamespaceOrTypeSymbol target = @using.NamespaceOrType; - - // Check if `using static` directives meet constraints. - UsingDirectiveSyntax usingDirective = @using.UsingDirective; - if (target.IsType) - { - var typeSymbol = (TypeSymbol)target; - var location = usingDirective?.Name.Location ?? NoLocation.Singleton; - typeSymbol.CheckAllConstraints(_compilation, conversions, location, diagnostics); - } - - semanticDiagnostics.AddRange(diagnostics.DiagnosticBag); - recordImportDependencies(usingDirective, target); - } - - // Force resolution of extern aliases. - foreach (var alias in ExternAliases) - { - var target = (NamespaceSymbol)alias.Alias.GetAliasTarget(null); - Debug.Assert(target.IsGlobalNamespace); - - semanticDiagnostics.AddRange(alias.Alias.AliasTargetDiagnostics.DiagnosticBag); - - if (!Compilation.ReportUnusedImportsInTree(alias.ExternAliasDirective.SyntaxTree)) - { - diagnostics.Clear(); - diagnostics.AddAssembliesUsedByNamespaceReference(target); - _compilation.AddUsedAssemblies(diagnostics.DependenciesBag); - } - } - - if (_diagnostics != null && !_diagnostics.IsEmptyWithoutResolution) - { - semanticDiagnostics.AddRange(_diagnostics.AsEnumerable()); - } - - diagnostics.Free(); - - void recordImportDependencies(UsingDirectiveSyntax usingDirective, NamespaceOrTypeSymbol target) - { - if (usingDirective is object && Compilation.ReportUnusedImportsInTree(usingDirective.SyntaxTree)) - { - _compilation.RecordImportDependencies(usingDirective, diagnostics.DependenciesBag.ToImmutableArray()); - } - else - { - if (target.IsNamespace) - { - diagnostics.AddAssembliesUsedByNamespaceReference((NamespaceSymbol)target); - } - - _compilation.AddUsedAssemblies(diagnostics.DependenciesBag); - } - } - } - - internal bool IsUsingAlias(string name, bool callerIsSemanticModel) - { - AliasAndUsingDirective node; - if (this.UsingAliases.TryGetValue(name, out node)) - { - // This method is called by InContainerBinder.LookupSymbolsInSingleBinder to see if - // there's a conflict between an alias and a member. As a conflict may cause a - // speculative lambda binding to fail this is semantically relevant and we need to - // mark this using alias as referenced (and thus not something that can be removed). - MarkImportDirective(node.UsingDirective, callerIsSemanticModel); - return true; - } - - return false; - } - - internal void LookupSymbol( - Binder originalBinder, - LookupResult result, - string name, - int arity, - ConsList basesBeingResolved, - LookupOptions options, - bool diagnose, - ref CompoundUseSiteInfo useSiteInfo) - { - LookupSymbolInAliases(originalBinder, result, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); - - if (!result.IsMultiViable && (options & LookupOptions.NamespaceAliasesOnly) == 0) - { - LookupSymbolInUsings(this.Usings, originalBinder, result, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); - } - } - - internal void LookupSymbolInAliases( - Binder originalBinder, - LookupResult result, - string name, - int arity, - ConsList basesBeingResolved, - LookupOptions options, - bool diagnose, - ref CompoundUseSiteInfo useSiteInfo) - { - bool callerIsSemanticModel = originalBinder.IsSemanticModelBinder; - - AliasAndUsingDirective alias; - if (this.UsingAliases.TryGetValue(name, out alias)) - { - // Found a match in our list of normal aliases. Mark the alias as being seen so that - // it won't be reported to the user as something that can be removed. - var res = originalBinder.CheckViability(alias.Alias, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved); - if (res.Kind == LookupResultKind.Viable) - { - MarkImportDirective(alias.UsingDirective, callerIsSemanticModel); - } - - result.MergeEqual(res); - } - - foreach (var a in this.ExternAliases) - { - if (a.Alias.Name == name) - { - // Found a match in our list of extern aliases. Mark the extern alias as being - // seen so that it won't be reported to the user as something that can be - // removed. - var res = originalBinder.CheckViability(a.Alias, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved); - if (res.Kind == LookupResultKind.Viable) - { - MarkImportDirective(a.ExternAliasDirective, callerIsSemanticModel); - } - - result.MergeEqual(res); - } - } - } - - internal static void LookupSymbolInUsings( - ImmutableArray usings, - Binder originalBinder, - LookupResult result, - string name, - int arity, - ConsList basesBeingResolved, - LookupOptions options, - bool diagnose, - ref CompoundUseSiteInfo useSiteInfo) - { - if (originalBinder.Flags.Includes(BinderFlags.InScriptUsing)) - { - return; - } - - bool callerIsSemanticModel = originalBinder.IsSemanticModelBinder; - - foreach (var typeOrNamespace in usings) - { - ImmutableArray candidates = Binder.GetCandidateMembers(typeOrNamespace.NamespaceOrType, name, options, originalBinder: originalBinder); - foreach (Symbol symbol in candidates) - { - if (!IsValidLookupCandidateInUsings(symbol)) - { - continue; - } - - // Found a match in our list of normal using directives. Mark the directive - // as being seen so that it won't be reported to the user as something that - // can be removed. - var res = originalBinder.CheckViability(symbol, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved); - if (res.Kind == LookupResultKind.Viable) - { - MarkImportDirective(originalBinder.Compilation, typeOrNamespace.UsingDirective, callerIsSemanticModel); - } - - result.MergeEqual(res); - } - } - } - - private static bool IsValidLookupCandidateInUsings(Symbol symbol) - { - switch (symbol.Kind) - { - // lookup via "using namespace" ignores namespaces inside - case SymbolKind.Namespace: - return false; - - // lookup via "using static" ignores extension methods and non-static methods - case SymbolKind.Method: - if (!symbol.IsStatic || ((MethodSymbol)symbol).IsExtensionMethod) - { - return false; - } - - break; - - // types are considered static members for purposes of "using static" feature - // regardless of whether they are declared with "static" modifier or not - case SymbolKind.NamedType: - break; - - // lookup via "using static" ignores non-static members - default: - if (!symbol.IsStatic) - { - return false; - } - - break; - } - - return true; - } - - internal void LookupExtensionMethodsInUsings( - ArrayBuilder methods, - string name, - int arity, - LookupOptions options, - Binder originalBinder) - { - var binderFlags = originalBinder.Flags; - if (binderFlags.Includes(BinderFlags.InScriptUsing)) - { - return; - } - - Debug.Assert(methods.Count == 0); - - bool callerIsSemanticModel = binderFlags.Includes(BinderFlags.SemanticModel); - - // We need to avoid collecting multiple candidates for an extension method imported both through a namespace and a static class - // We will look for duplicates only if both of the following flags are set to true - bool seenNamespaceWithExtensionMethods = false; - bool seenStaticClassWithExtensionMethods = false; - - foreach (var nsOrType in this.Usings) - { - switch (nsOrType.NamespaceOrType.Kind) - { - case SymbolKind.Namespace: - { - var count = methods.Count; - ((NamespaceSymbol)nsOrType.NamespaceOrType).GetExtensionMethods(methods, name, arity, options); - - // If we found any extension methods, then consider this using as used. - if (methods.Count != count) - { - MarkImportDirective(nsOrType.UsingDirective, callerIsSemanticModel); - seenNamespaceWithExtensionMethods = true; - } - - break; - } - - case SymbolKind.NamedType: - { - var count = methods.Count; - ((NamedTypeSymbol)nsOrType.NamespaceOrType).GetExtensionMethods(methods, name, arity, options); - - // If we found any extension methods, then consider this using as used. - if (methods.Count != count) - { - MarkImportDirective(nsOrType.UsingDirective, callerIsSemanticModel); - seenStaticClassWithExtensionMethods = true; - } - - break; - } - } - } - - if (seenNamespaceWithExtensionMethods && seenStaticClassWithExtensionMethods) - { - methods.RemoveDuplicates(); - } - } - - // Note: we do not mark nodes when looking up arities or names. This is because these two - // types of lookup are only around to make the public - // SemanticModel.LookupNames/LookupSymbols work and do not count as usages of the directives - // when the actual code is bound. - - internal void AddLookupSymbolsInfo(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) - { - AddLookupSymbolsInfoInAliases(result, options, originalBinder); - - // Add types within namespaces imported through usings, but don't add nested namespaces. - LookupOptions usingOptions = (options & ~(LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly)) | LookupOptions.MustNotBeNamespace; - AddLookupSymbolsInfoInUsings(this.Usings, result, usingOptions, originalBinder); - } - - internal void AddLookupSymbolsInfoInAliases(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) - { - foreach (var (_, usingAlias) in this.UsingAliases) - { - AddAliasSymbolToResult(result, usingAlias.Alias, options, originalBinder); - } - - foreach (var externAlias in this.ExternAliases) - { - AddAliasSymbolToResult(result, externAlias.Alias, options, originalBinder); - } - } - - private static void AddAliasSymbolToResult(LookupSymbolsInfo result, AliasSymbol aliasSymbol, LookupOptions options, Binder originalBinder) - { - var targetSymbol = aliasSymbol.GetAliasTarget(basesBeingResolved: null); - if (originalBinder.CanAddLookupSymbolInfo(targetSymbol, options, result, accessThroughType: null, aliasSymbol: aliasSymbol)) - { - result.AddSymbol(aliasSymbol, aliasSymbol.Name, 0); - } - } - - private static void AddLookupSymbolsInfoInUsings( - ImmutableArray usings, LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) - { - if (originalBinder.Flags.Includes(BinderFlags.InScriptUsing)) - { - return; - } - - Debug.Assert(!options.CanConsiderNamespaces()); - - // look in all using namespaces - foreach (var namespaceSymbol in usings) - { - foreach (var member in namespaceSymbol.NamespaceOrType.GetMembersUnordered()) - { - if (IsValidLookupCandidateInUsings(member) && originalBinder.CanAddLookupSymbolInfo(member, options, result, null)) - { - result.AddSymbol(member, member.Name, member.GetArity()); - } - } - } - } - private class UsingTargetComparer : IEqualityComparer { public static readonly IEqualityComparer Instance = new UsingTargetComparer(); diff --git a/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs b/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs index f90f88a591014..0b8dfac37b64d 100644 --- a/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs @@ -16,68 +16,20 @@ namespace Microsoft.CodeAnalysis.CSharp { /// - /// A binder that places the members of a symbol in scope. If there is a container declaration - /// with using directives, those are merged when looking up names. + /// A binder that places the members of a symbol in scope. /// - internal sealed class InContainerBinder : Binder + internal class InContainerBinder : Binder { private readonly NamespaceOrTypeSymbol _container; - private readonly Func, Imports> _computeImports; - private Imports _lazyImports; - private ImportChain _lazyImportChain; - private QuickAttributeChecker _lazyQuickAttributeChecker; - private readonly SyntaxList _usingsSyntax; /// - /// Creates a binder for a container with imports (usings and extern aliases) that can be - /// retrieved from . + /// Creates a binder for a container. /// - internal InContainerBinder(NamespaceOrTypeSymbol container, Binder next, CSharpSyntaxNode declarationSyntax, bool inUsing) + internal InContainerBinder(NamespaceOrTypeSymbol container, Binder next) : base(next) { Debug.Assert((object)container != null); - Debug.Assert(declarationSyntax != null); - - _container = container; - _computeImports = basesBeingResolved => Imports.FromSyntax(declarationSyntax, this, basesBeingResolved, inUsing); - - if (!inUsing) - { - if (declarationSyntax.Kind() == SyntaxKind.CompilationUnit) - { - var compilationUnit = (CompilationUnitSyntax)declarationSyntax; - _usingsSyntax = compilationUnit.Usings; - } - else if (declarationSyntax.Kind() == SyntaxKind.NamespaceDeclaration) - { - var namespaceDecl = (NamespaceDeclarationSyntax)declarationSyntax; - _usingsSyntax = namespaceDecl.Usings; - } - } - } - - /// - /// Creates a binder with given imports. - /// - internal InContainerBinder(NamespaceOrTypeSymbol container, Binder next, Imports imports = null) - : base(next) - { - Debug.Assert((object)container != null || imports != null); - _container = container; - _lazyImports = imports ?? Imports.Empty; - } - - /// - /// Creates a binder with given import computation function. - /// - internal InContainerBinder(Binder next, Func, Imports> computeImports) - : base(next) - { - Debug.Assert(computeImports != null); - - _container = null; - _computeImports = computeImports; } internal NamespaceOrTypeSymbol Container @@ -88,94 +40,6 @@ internal NamespaceOrTypeSymbol Container } } - internal override Imports GetImports(ConsList basesBeingResolved) - { - Debug.Assert(_lazyImports != null || _computeImports != null, "Have neither imports nor a way to compute them."); - - if (_lazyImports == null) - { - Interlocked.CompareExchange(ref _lazyImports, _computeImports(basesBeingResolved), null); - } - - return _lazyImports; - } - - /// - /// Look for a type forwarder for the given type in any referenced assemblies, checking any using namespaces in - /// the current imports. - /// - /// The metadata name of the (potentially) forwarded type, without qualifiers. - /// Will be used to return the namespace of the found forwarder, - /// if any. - /// Will be used to report non-fatal errors during look up. - /// Location to report errors on. - /// Returns the Assembly to which the type is forwarded, or null if none is found. - /// - /// Since this method is intended to be used for error reporting, it stops as soon as it finds - /// any type forwarder (or an error to report). It does not check other assemblies for consistency or better results. - /// - protected override AssemblySymbol GetForwardedToAssemblyInUsingNamespaces(string name, ref NamespaceOrTypeSymbol qualifierOpt, BindingDiagnosticBag diagnostics, Location location) - { - var imports = GetImports(basesBeingResolved: null); - foreach (var typeOrNamespace in imports.Usings) - { - var fullName = typeOrNamespace.NamespaceOrType + "." + name; - var result = GetForwardedToAssembly(fullName, diagnostics, location); - if (result != null) - { - qualifierOpt = typeOrNamespace.NamespaceOrType; - return result; - } - } - - return base.GetForwardedToAssemblyInUsingNamespaces(name, ref qualifierOpt, diagnostics, location); - } - - internal override ImportChain ImportChain - { - get - { - if (_lazyImportChain == null) - { - ImportChain importChain = this.Next.ImportChain; - if ((object)_container == null || _container.Kind == SymbolKind.Namespace) - { - importChain = new ImportChain(GetImports(basesBeingResolved: null), importChain); - } - - Interlocked.CompareExchange(ref _lazyImportChain, importChain, null); - } - - Debug.Assert(_lazyImportChain != null); - - return _lazyImportChain; - } - } - - /// - /// Get that can be used to quickly - /// check for certain attribute applications in context of this binder. - /// - internal override QuickAttributeChecker QuickAttributeChecker - { - get - { - if (_lazyQuickAttributeChecker == null) - { - QuickAttributeChecker result = this.Next.QuickAttributeChecker; - - if ((object)_container == null || _container.Kind == SymbolKind.Namespace) - { - result = result.AddAliasesIfAny(_usingsSyntax); - } - - _lazyQuickAttributeChecker = result; - } - - return _lazyQuickAttributeChecker; - } - } - internal override Symbol ContainingMemberOrLambda { get @@ -185,14 +49,9 @@ internal override Symbol ContainingMemberOrLambda } } - private bool IsSubmissionClass - { - get { return (_container?.Kind == SymbolKind.NamedType) && ((NamedTypeSymbol)_container).IsSubmissionClass; } - } - private bool IsScriptClass { - get { return (_container?.Kind == SymbolKind.NamedType) && ((NamedTypeSymbol)_container).IsScriptClass; } + get { return (_container.Kind == SymbolKind.NamedType) && ((NamedTypeSymbol)_container).IsScriptClass; } } internal override bool IsAccessibleHelper(Symbol symbol, TypeSymbol accessThroughType, out bool failedThroughTypeCheck, ref CompoundUseSiteInfo useSiteInfo, ConsList basesBeingResolved) @@ -214,28 +73,16 @@ internal override bool SupportsExtensionMethods } internal override void GetCandidateExtensionMethods( - bool searchUsingsNotNamespace, ArrayBuilder methods, string name, int arity, LookupOptions options, Binder originalBinder) { - if (searchUsingsNotNamespace) - { - this.GetImports(basesBeingResolved: null).LookupExtensionMethodsInUsings(methods, name, arity, options, originalBinder); - } - else if (_container?.Kind == SymbolKind.Namespace) + if (_container.Kind == SymbolKind.Namespace) { ((NamespaceSymbol)_container).GetExtensionMethods(methods, name, arity, options); } - else if (IsSubmissionClass) - { - for (var submission = this.Compilation; submission != null; submission = submission.PreviousSubmission) - { - submission.ScriptClass?.GetExtensionMethods(methods, name, arity, options); - } - } } internal override TypeWithAnnotations GetIteratorElementType() @@ -258,51 +105,32 @@ internal override void LookupSymbolsInSingleBinder( { Debug.Assert(result.IsClear); - if (IsSubmissionClass) - { - this.LookupMembersInternal(result, _container, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); - return; - } - - var imports = GetImports(basesBeingResolved); - // first lookup members of the namespace - if ((options & LookupOptions.NamespaceAliasesOnly) == 0 && _container != null) + if ((options & LookupOptions.NamespaceAliasesOnly) == 0) { this.LookupMembersInternal(result, _container, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); if (result.IsMultiViable) { - // symbols cannot conflict with using alias names - if (arity == 0 && imports.IsUsingAlias(name, originalBinder.IsSemanticModelBinder)) + if (arity == 0) { - CSDiagnosticInfo diagInfo = new CSDiagnosticInfo(ErrorCode.ERR_ConflictAliasAndMember, name, _container); - var error = new ExtendedErrorTypeSymbol((NamespaceOrTypeSymbol)null, name, arity, diagInfo, unreported: true); - result.SetFrom(LookupResult.Good(error)); // force lookup to be done w/ error symbol as result + // symbols cannot conflict with using alias names + if (Next is WithExternAndUsingAliasesBinder withUsingAliases && withUsingAliases.IsUsingAlias(name, originalBinder.IsSemanticModelBinder, basesBeingResolved)) + { + CSDiagnosticInfo diagInfo = new CSDiagnosticInfo(ErrorCode.ERR_ConflictAliasAndMember, name, _container); + var error = new ExtendedErrorTypeSymbol((NamespaceOrTypeSymbol)null, name, arity, diagInfo, unreported: true); + result.SetFrom(LookupResult.Good(error)); // force lookup to be done w/ error symbol as result + } } return; } } - - // next try using aliases or symbols in imported namespaces - imports.LookupSymbol(originalBinder, result, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); } protected override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) { - if (_container != null) - { - this.AddMemberLookupSymbolsInfo(result, _container, options, originalBinder); - } - - // If we are looking only for labels we do not need to search through the imports. - // Submission imports are handled by AddMemberLookupSymbolsInfo (above). - if (!IsSubmissionClass && ((options & LookupOptions.LabelsOnly) == 0)) - { - var imports = GetImports(basesBeingResolved: null); - imports.AddLookupSymbolsInfo(result, options, originalBinder); - } + this.AddMemberLookupSymbolsInfo(result, _container, options, originalBinder); } protected override SourceLocalSymbol LookupLocal(SyntaxToken nameToken) diff --git a/src/Compilers/CSharp/Portable/Binder/InSubmissionClassBinder.cs b/src/Compilers/CSharp/Portable/Binder/InSubmissionClassBinder.cs new file mode 100644 index 0000000000000..472da5ace795c --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/InSubmissionClassBinder.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + /// + /// A binder that places the members of a submission class and aliases in scope. + /// + internal sealed class InSubmissionClassBinder : InContainerBinder + { + private readonly CompilationUnitSyntax _declarationSyntax; + private readonly bool _inUsings; + private QuickAttributeChecker? _lazyQuickAttributeChecker; + + internal InSubmissionClassBinder(NamedTypeSymbol submissionClass, Binder next, CompilationUnitSyntax declarationSyntax, bool inUsings) + : base(submissionClass, next) + { + Debug.Assert(submissionClass.IsSubmissionClass); + _declarationSyntax = declarationSyntax; + _inUsings = inUsings; + } + + internal override void GetCandidateExtensionMethods( + ArrayBuilder methods, + string name, + int arity, + LookupOptions options, + Binder originalBinder) + { + for (var submission = this.Compilation; submission != null; submission = submission.PreviousSubmission) + { + submission.ScriptClass?.GetExtensionMethods(methods, name, arity, options); + } + } + + internal override void LookupSymbolsInSingleBinder( + LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(result.IsClear); + + this.LookupMembersInSubmissions(result, (NamedTypeSymbol)Container, _declarationSyntax, _inUsings, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); + } + + protected override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) + { + this.AddMemberLookupSymbolsInfoInSubmissions(result, (NamedTypeSymbol)Container, _inUsings, options, originalBinder); + } + + internal override ImmutableArray ExternAliases => ((SourceNamespaceSymbol)Compilation.SourceModule.GlobalNamespace).GetExternAliases(_declarationSyntax); + + internal override ImmutableArray UsingAliases => ((SourceNamespaceSymbol)Compilation.SourceModule.GlobalNamespace).GetUsingAliases(_declarationSyntax, basesBeingResolved: null); + + /// + /// Get that can be used to quickly + /// check for certain attribute applications in context of this binder. + /// + internal override QuickAttributeChecker QuickAttributeChecker + { + get + { + if (_lazyQuickAttributeChecker == null) + { + QuickAttributeChecker result = this.Next!.QuickAttributeChecker; + result = result.AddAliasesIfAny(_declarationSyntax.Usings); + _lazyQuickAttributeChecker = result; + } + + return _lazyQuickAttributeChecker; + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs index 07c5b16e74263..d2a83280e672e 100644 --- a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs @@ -161,10 +161,11 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no public override void VisitRecordDeclaration(RecordDeclarationSyntax node) { Debug.Assert(node.ParameterList is object); + Debug.Assert(node.IsKind(SyntaxKind.RecordDeclaration)); Binder enclosing = new ExpressionVariableBinder(node, _enclosing); AddToMap(node, enclosing); - Visit(node.PrimaryConstructorBaseType, enclosing); + Visit(node.PrimaryConstructorBaseTypeIfClass, enclosing); } public override void VisitPrimaryConstructorBaseType(PrimaryConstructorBaseTypeSyntax node) diff --git a/src/Compilers/CSharp/Portable/Binder/NamespaceOrTypeAndUsingDirective.cs b/src/Compilers/CSharp/Portable/Binder/NamespaceOrTypeAndUsingDirective.cs index 31ec82b8af889..2596ad756ee92 100644 --- a/src/Compilers/CSharp/Portable/Binder/NamespaceOrTypeAndUsingDirective.cs +++ b/src/Compilers/CSharp/Portable/Binder/NamespaceOrTypeAndUsingDirective.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.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -13,14 +11,16 @@ namespace Microsoft.CodeAnalysis.CSharp internal struct NamespaceOrTypeAndUsingDirective { public readonly NamespaceOrTypeSymbol NamespaceOrType; - public readonly UsingDirectiveSyntax UsingDirective; + public readonly SyntaxReference? UsingDirectiveReference; public readonly ImmutableArray Dependencies; - public NamespaceOrTypeAndUsingDirective(NamespaceOrTypeSymbol namespaceOrType, UsingDirectiveSyntax usingDirective, ImmutableArray dependencies) + public NamespaceOrTypeAndUsingDirective(NamespaceOrTypeSymbol namespaceOrType, UsingDirectiveSyntax? usingDirective, ImmutableArray dependencies) { this.NamespaceOrType = namespaceOrType; - this.UsingDirective = usingDirective; + this.UsingDirectiveReference = usingDirective?.GetReference(); this.Dependencies = dependencies.NullToEmpty(); } + + public UsingDirectiveSyntax? UsingDirective => (UsingDirectiveSyntax?)UsingDirectiveReference?.GetSyntax(); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index f3d402007ac8d..74963ad00b10a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -43,21 +42,23 @@ protected override ConversionsBase WithNullabilityCore(bool includeNullability) public override Conversion GetMethodGroupDelegateConversion(BoundMethodGroup source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { // Must be a bona fide delegate type, not an expression tree type. - if (!destination.IsDelegateType()) + if (!(destination.IsDelegateType() || destination.SpecialType == SpecialType.System_Delegate)) { return Conversion.NoConversion; } - var (methodSymbol, isFunctionPointer, callingConventionInfo) = GetDelegateInvokeOrFunctionPointerMethodIfAvailable(destination); + var (methodSymbol, isFunctionPointer, callingConventionInfo) = GetDelegateInvokeOrFunctionPointerMethodIfAvailable(source, destination, ref useSiteInfo); if ((object)methodSymbol == null) { return Conversion.NoConversion; } + Debug.Assert(destination.SpecialType == SpecialType.System_Delegate || methodSymbol == ((NamedTypeSymbol)destination).DelegateInvokeMethod); + var resolution = ResolveDelegateOrFunctionPointerMethodGroup(_binder, source, methodSymbol, isFunctionPointer, callingConventionInfo, ref useSiteInfo); var conversion = (resolution.IsEmpty || resolution.HasAnyErrors) ? Conversion.NoConversion : - ToConversion(resolution.OverloadResolutionResult, resolution.MethodGroup, ((NamedTypeSymbol)destination).DelegateInvokeMethod.ParameterCount); + ToConversion(resolution.OverloadResolutionResult, resolution.MethodGroup, methodSymbol.ParameterCount); resolution.Free(); return conversion; } @@ -113,14 +114,21 @@ private static MethodGroupResolution ResolveDelegateOrFunctionPointerMethodGroup /// Return the Invoke method symbol if the type is a delegate /// type and the Invoke method is available, otherwise null. /// - private static (MethodSymbol, bool isFunctionPointer, CallingConventionInfo callingConventionInfo) GetDelegateInvokeOrFunctionPointerMethodIfAvailable(TypeSymbol type) + private (MethodSymbol, bool isFunctionPointer, CallingConventionInfo callingConventionInfo) GetDelegateInvokeOrFunctionPointerMethodIfAvailable( + BoundMethodGroup methodGroup, + TypeSymbol type, + ref CompoundUseSiteInfo useSiteInfo) { if (type is FunctionPointerTypeSymbol { Signature: { } signature }) { return (signature, true, new CallingConventionInfo(signature.CallingConvention, signature.GetCallingConventionModifiers())); } - var delegateType = type.GetDelegateType(); + var delegateType = (type.SpecialType == SpecialType.System_Delegate) ? + // https://github.com/dotnet/roslyn/issues/52869: Avoid calculating the delegate type multiple times during conversion. + _binder.GetMethodGroupDelegateType(methodGroup, ref useSiteInfo) : + type.GetDelegateType(); + if ((object)delegateType == null) { return (null, false, default); @@ -135,10 +143,10 @@ private static (MethodSymbol, bool isFunctionPointer, CallingConventionInfo call return (methodSymbol, false, default); } - public static bool ReportDelegateOrFunctionPointerMethodGroupDiagnostics(Binder binder, BoundMethodGroup expr, TypeSymbol targetType, BindingDiagnosticBag diagnostics) + public bool ReportDelegateOrFunctionPointerMethodGroupDiagnostics(Binder binder, BoundMethodGroup expr, TypeSymbol targetType, BindingDiagnosticBag diagnostics) { - var (invokeMethodOpt, isFunctionPointer, callingConventionInfo) = GetDelegateInvokeOrFunctionPointerMethodIfAvailable(targetType); CompoundUseSiteInfo useSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics); + var (invokeMethodOpt, isFunctionPointer, callingConventionInfo) = GetDelegateInvokeOrFunctionPointerMethodIfAvailable(expr, targetType, ref useSiteInfo); var resolution = ResolveDelegateOrFunctionPointerMethodGroup(binder, expr, invokeMethodOpt, isFunctionPointer, callingConventionInfo, ref useSiteInfo); diagnostics.Add(expr.Syntax, useSiteInfo); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 556afd3655231..a7a247c33fff5 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -1361,7 +1361,7 @@ private static LambdaConversionResult IsAnonymousFunctionCompatibleWithExpressio { Debug.Assert((object)anonymousFunction != null); Debug.Assert((object)type != null); - Debug.Assert(type.IsExpressionTree()); + Debug.Assert(type.IsGenericOrNonGenericExpressionType(out _)); // SPEC OMISSION: // @@ -1374,8 +1374,8 @@ private static LambdaConversionResult IsAnonymousFunctionCompatibleWithExpressio // This appears to be a spec omission; the intention is to make old-style anonymous methods not // convertible to expression trees. - var delegateType = type.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; - if (!delegateType.IsDelegateType()) + var delegateType = type.Arity == 0 ? null : type.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; + if (delegateType is { } && !delegateType.IsDelegateType()) { return LambdaConversionResult.ExpressionTreeMustHaveDelegateTypeArgument; } @@ -1385,6 +1385,11 @@ private static LambdaConversionResult IsAnonymousFunctionCompatibleWithExpressio return LambdaConversionResult.ExpressionTreeFromAnonymousMethod; } + if (delegateType is null) + { + return LambdaConversionResult.Success; + } + return IsAnonymousFunctionCompatibleWithDelegate(anonymousFunction, delegateType); } @@ -1393,11 +1398,15 @@ public static LambdaConversionResult IsAnonymousFunctionCompatibleWithType(Unbou Debug.Assert((object)anonymousFunction != null); Debug.Assert((object)type != null); - if (type.IsDelegateType()) + if (type.SpecialType == SpecialType.System_Delegate) + { + return LambdaConversionResult.Success; + } + else if (type.IsDelegateType()) { return IsAnonymousFunctionCompatibleWithDelegate(anonymousFunction, type); } - else if (type.IsExpressionTree()) + else if (type.IsGenericOrNonGenericExpressionType(out bool _)) { return IsAnonymousFunctionCompatibleWithExpressionTree(anonymousFunction, (NamedTypeSymbol)type); } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs index 35689e2f5b365..db382a0cad9f1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs @@ -806,7 +806,7 @@ private static void ReportBadNonTrailingNamedArgument( symbols), location); } - private void ReportDuplicateNamedArgument(MemberResolutionResult result, BindingDiagnosticBag diagnostics, AnalyzedArguments arguments) + private static void ReportDuplicateNamedArgument(MemberResolutionResult result, BindingDiagnosticBag diagnostics, AnalyzedArguments arguments) { Debug.Assert(result.Result.BadArgumentsOpt.Length == 1); IdentifierNameSyntax name = arguments.Names[result.Result.BadArgumentsOpt[0]]; @@ -1112,7 +1112,7 @@ private bool HadBadArguments( return true; } - private void ReportBadArgumentError( + private static void ReportBadArgumentError( BindingDiagnosticBag diagnostics, Binder binder, string name, @@ -1185,7 +1185,7 @@ private void ReportBadArgumentError( ((UnboundLambda)argument).GenerateAnonymousFunctionConversionError(diagnostics, parameterType); } else if (argument.Kind == BoundKind.MethodGroup && parameterType.TypeKind == TypeKind.Delegate && - Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(binder, (BoundMethodGroup)argument, parameterType, diagnostics)) + binder.Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(binder, (BoundMethodGroup)argument, parameterType, diagnostics)) { // a diagnostic has been reported by ReportDelegateOrFunctionPointerMethodGroupDiagnostics } @@ -1194,7 +1194,7 @@ private void ReportBadArgumentError( diagnostics.Add(ErrorCode.ERR_MissingAddressOf, sourceLocation); } else if (argument.Kind == BoundKind.UnconvertedAddressOfOperator && - Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(binder, ((BoundUnconvertedAddressOfOperator)argument).Operand, parameterType, diagnostics)) + binder.Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(binder, ((BoundUnconvertedAddressOfOperator)argument).Operand, parameterType, diagnostics)) { // a diagnostic has been reported by ReportDelegateOrFunctionPointerMethodGroupDiagnostics } diff --git a/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs new file mode 100644 index 0000000000000..afcd6d04cc841 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + /// + /// A binder that brings extern aliases into the scope and deals with looking up names in them. + /// + internal abstract class WithExternAliasesBinder : Binder + { + internal WithExternAliasesBinder(Binder next) + : base(next) + { + } + + internal abstract override ImmutableArray ExternAliases + { + get; + } + + internal override void LookupSymbolsInSingleBinder( + LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(result.IsClear); + + LookupSymbolInAliases( + ImmutableDictionary.Empty, + this.ExternAliases, + originalBinder, + result, + name, + arity, + basesBeingResolved, + options, + diagnose, + ref useSiteInfo); + } + + protected override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) + { + // If we are looking only for labels we do not need to search through the imports. + if ((options & LookupOptions.LabelsOnly) == 0) + { + AddLookupSymbolsInfoInAliases( + ImmutableDictionary.Empty, + this.ExternAliases, + result, options, originalBinder); + } + } + + protected sealed override SourceLocalSymbol? LookupLocal(SyntaxToken nameToken) + { + return null; + } + + protected sealed override LocalFunctionSymbol? LookupLocalFunction(SyntaxToken nameToken) + { + return null; + } + + internal sealed override uint LocalScopeDepth => Binder.ExternalScope; + + internal static WithExternAliasesBinder Create(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next) + { + return new FromSyntax(declaringSymbol, declarationSyntax, next); + } + + internal static WithExternAliasesBinder Create(ImmutableArray externAliases, Binder next) + { + return new FromSymbols(externAliases, next); + } + + private sealed class FromSyntax : WithExternAliasesBinder + { + private readonly SourceNamespaceSymbol _declaringSymbol; + private readonly CSharpSyntaxNode _declarationSyntax; + private ImmutableArray _lazyExternAliases; + + internal FromSyntax(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next) + : base(next) + { + Debug.Assert(declarationSyntax.IsKind(SyntaxKind.CompilationUnit) || declarationSyntax.IsKind(SyntaxKind.NamespaceDeclaration)); + _declaringSymbol = declaringSymbol; + _declarationSyntax = declarationSyntax; + } + + internal override ImmutableArray ExternAliases + { + get + { + if (_lazyExternAliases.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyExternAliases, _declaringSymbol.GetExternAliases(_declarationSyntax)); + } + + return _lazyExternAliases; + } + } + } + + private sealed class FromSymbols : WithExternAliasesBinder + { + private readonly ImmutableArray _externAliases; + + internal FromSymbols(ImmutableArray externAliases, Binder next) + : base(next) + { + Debug.Assert(!externAliases.IsDefault); + _externAliases = externAliases; + } + + internal override ImmutableArray ExternAliases + { + get + { + return _externAliases; + } + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Binder/WithExternAndUsingAliasesBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithExternAndUsingAliasesBinder.cs new file mode 100644 index 0000000000000..8c941649433af --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/WithExternAndUsingAliasesBinder.cs @@ -0,0 +1,266 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + /// + /// A binder that brings both extern and using aliases into the scope and deals with looking up names in them. + /// + internal abstract class WithExternAndUsingAliasesBinder : WithExternAliasesBinder + { + private ImportChain? _lazyImportChain; + + protected WithExternAndUsingAliasesBinder(WithUsingNamespacesAndTypesBinder next) + : base(next) + { +#if DEBUG + Debug.Assert(!next.WithImportChainEntry); +#endif + } + + internal abstract override ImmutableArray UsingAliases { get; } + + protected abstract ImmutableDictionary GetUsingAliasesMap(ConsList? basesBeingResolved); + + internal override void LookupSymbolsInSingleBinder( + LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(result.IsClear); + + LookupSymbolInAliases( + this.GetUsingAliasesMap(basesBeingResolved), + this.ExternAliases, + originalBinder, + result, + name, + arity, + basesBeingResolved, + options, + diagnose, + ref useSiteInfo); + } + + protected override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) + { + // If we are looking only for labels we do not need to search through the imports. + if ((options & LookupOptions.LabelsOnly) == 0) + { + AddLookupSymbolsInfoInAliases( + this.GetUsingAliasesMap(basesBeingResolved: null), + this.ExternAliases, + result, options, originalBinder); + } + } + + internal override ImportChain ImportChain + { + get + { + if (_lazyImportChain == null) + { + Debug.Assert(this.Next is WithUsingNamespacesAndTypesBinder); + Interlocked.CompareExchange(ref _lazyImportChain, BuildImportChain(), null); + } + + Debug.Assert(_lazyImportChain != null); + + return _lazyImportChain; + } + } + + protected abstract ImportChain BuildImportChain(); + + internal bool IsUsingAlias(string name, bool callerIsSemanticModel, ConsList? basesBeingResolved) + { + return IsUsingAlias(this.GetUsingAliasesMap(basesBeingResolved), name, callerIsSemanticModel); + } + + /// + /// This overload is added to shadow the one from the base. + /// + [Obsolete("Use other overloads", error: true)] + internal static new WithExternAndUsingAliasesBinder Create(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next) + { + throw ExceptionUtilities.Unreachable; + } + + internal static WithExternAndUsingAliasesBinder Create(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, WithUsingNamespacesAndTypesBinder next) + { + return new FromSyntax(declaringSymbol, declarationSyntax, next); + } + + internal static WithExternAndUsingAliasesBinder Create(ImmutableArray externAliases, ImmutableDictionary usingAliases, WithUsingNamespacesAndTypesBinder next) + { + return new FromSymbols(externAliases, usingAliases, next); + } + + private sealed class FromSyntax : WithExternAndUsingAliasesBinder + { + private readonly SourceNamespaceSymbol _declaringSymbol; + private readonly CSharpSyntaxNode _declarationSyntax; + private ImmutableArray _lazyExternAliases; + private ImmutableArray _lazyUsingAliases; + private ImmutableDictionary? _lazyUsingAliasesMap; + private QuickAttributeChecker? _lazyQuickAttributeChecker; + + internal FromSyntax(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, WithUsingNamespacesAndTypesBinder next) + : base(next) + { + Debug.Assert(declarationSyntax.IsKind(SyntaxKind.CompilationUnit) || declarationSyntax.IsKind(SyntaxKind.NamespaceDeclaration)); + _declaringSymbol = declaringSymbol; + _declarationSyntax = declarationSyntax; + } + + internal sealed override ImmutableArray ExternAliases + { + get + { + if (_lazyExternAliases.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyExternAliases, _declaringSymbol.GetExternAliases(_declarationSyntax)); + } + + return _lazyExternAliases; + } + } + + internal override ImmutableArray UsingAliases + { + get + { + if (_lazyUsingAliases.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyUsingAliases, _declaringSymbol.GetUsingAliases(_declarationSyntax, basesBeingResolved: null)); + } + + return _lazyUsingAliases; + } + } + + protected override ImmutableDictionary GetUsingAliasesMap(ConsList? basesBeingResolved) + { + if (_lazyUsingAliasesMap is null) + { + Interlocked.CompareExchange(ref _lazyUsingAliasesMap, _declaringSymbol.GetUsingAliasesMap(_declarationSyntax, basesBeingResolved), null); + } + + return _lazyUsingAliasesMap; + } + + /// + /// Get that can be used to quickly + /// check for certain attribute applications in context of this binder. + /// + internal override QuickAttributeChecker QuickAttributeChecker + { + get + { + if (_lazyQuickAttributeChecker == null) + { + QuickAttributeChecker result = this.Next!.QuickAttributeChecker; + + SyntaxList usingDirectives; + switch (_declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + // Take global aliases from other compilation units into account + foreach (var declaration in ((SourceNamespaceSymbol)Compilation.SourceModule.GlobalNamespace).MergedDeclaration.Declarations) + { + if (declaration.HasGlobalUsings && compilationUnit.SyntaxTree != declaration.SyntaxReference.SyntaxTree) + { + result = result.AddAliasesIfAny(((CompilationUnitSyntax)declaration.SyntaxReference.GetSyntax()).Usings, onlyGlobalAliases: true); + } + } + + usingDirectives = compilationUnit.Usings; + break; + + case NamespaceDeclarationSyntax namespaceDecl: + usingDirectives = namespaceDecl.Usings; + break; + + default: + throw ExceptionUtilities.UnexpectedValue(_declarationSyntax); + } + + result = result.AddAliasesIfAny(usingDirectives); + + _lazyQuickAttributeChecker = result; + } + + return _lazyQuickAttributeChecker; + } + } + + protected override ImportChain BuildImportChain() + { + var previous = Next!.ImportChain; + + if (_declarationSyntax is NamespaceDeclarationSyntax namespaceDecl) + { + // For each dotted name add an empty entry in the chain + var name = namespaceDecl.Name; + + while (name is QualifiedNameSyntax dotted) + { + previous = new ImportChain(Imports.Empty, previous); + name = dotted.Left; + } + } + + return new ImportChain(_declaringSymbol.GetImports(_declarationSyntax, basesBeingResolved: null), previous); + } + } + + private sealed class FromSymbols : WithExternAndUsingAliasesBinder + { + private readonly ImmutableArray _externAliases; + private readonly ImmutableDictionary _usingAliases; + + internal FromSymbols(ImmutableArray externAliases, ImmutableDictionary usingAliases, WithUsingNamespacesAndTypesBinder next) + : base(next) + { + Debug.Assert(!externAliases.IsDefault); + _externAliases = externAliases; + _usingAliases = usingAliases; + } + + internal override ImmutableArray ExternAliases + { + get + { + return _externAliases; + } + } + + internal override ImmutableArray UsingAliases + { + get + { + return _usingAliases.SelectAsArray(static pair => pair.Value); + } + } + + protected override ImmutableDictionary GetUsingAliasesMap(ConsList? basesBeingResolved) + { + return _usingAliases; + } + + protected override ImportChain BuildImportChain() + { + return new ImportChain(Imports.Create(_usingAliases, ((WithUsingNamespacesAndTypesBinder)Next!).GetUsings(basesBeingResolved: null), _externAliases), Next!.ImportChain); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs new file mode 100644 index 0000000000000..8ca9aa8054f37 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs @@ -0,0 +1,359 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + /// + /// A binder that represents a scope introduced by 'using' namespace or type directives and deals with looking up names in it. + /// + internal abstract class WithUsingNamespacesAndTypesBinder : Binder + { + private readonly bool _withImportChainEntry; + private ImportChain? _lazyImportChain; + + protected WithUsingNamespacesAndTypesBinder(Binder next, bool withImportChainEntry) + : base(next) + { + _withImportChainEntry = withImportChainEntry; + } + +#if DEBUG + internal bool WithImportChainEntry => _withImportChainEntry; +#endif + + internal abstract ImmutableArray GetUsings(ConsList? basesBeingResolved); + + /// + /// Look for a type forwarder for the given type in any referenced assemblies, checking any using namespaces in + /// the current imports. + /// + /// The metadata name of the (potentially) forwarded type, without qualifiers. + /// Will be used to return the namespace of the found forwarder, + /// if any. + /// Will be used to report non-fatal errors during look up. + /// Location to report errors on. + /// Returns the Assembly to which the type is forwarded, or null if none is found. + /// + /// Since this method is intended to be used for error reporting, it stops as soon as it finds + /// any type forwarder (or an error to report). It does not check other assemblies for consistency or better results. + /// + protected override AssemblySymbol? GetForwardedToAssemblyInUsingNamespaces(string name, ref NamespaceOrTypeSymbol qualifierOpt, BindingDiagnosticBag diagnostics, Location location) + { + foreach (var typeOrNamespace in GetUsings(basesBeingResolved: null)) + { + var fullName = typeOrNamespace.NamespaceOrType + "." + name; + var result = GetForwardedToAssembly(fullName, diagnostics, location); + if (result != null) + { + qualifierOpt = typeOrNamespace.NamespaceOrType; + return result; + } + } + + return base.GetForwardedToAssemblyInUsingNamespaces(name, ref qualifierOpt, diagnostics, location); + } + + internal override bool SupportsExtensionMethods + { + get { return true; } + } + + internal override void GetCandidateExtensionMethods( + ArrayBuilder methods, + string name, + int arity, + LookupOptions options, + Binder originalBinder) + { + Debug.Assert(methods.Count == 0); + + bool callerIsSemanticModel = originalBinder.IsSemanticModelBinder; + + // We need to avoid collecting multiple candidates for an extension method imported both through a namespace and a static class + // We will look for duplicates only if both of the following flags are set to true + bool seenNamespaceWithExtensionMethods = false; + bool seenStaticClassWithExtensionMethods = false; + + foreach (var nsOrType in this.GetUsings(basesBeingResolved: null)) + { + switch (nsOrType.NamespaceOrType.Kind) + { + case SymbolKind.Namespace: + { + var count = methods.Count; + ((NamespaceSymbol)nsOrType.NamespaceOrType).GetExtensionMethods(methods, name, arity, options); + + // If we found any extension methods, then consider this using as used. + if (methods.Count != count) + { + MarkImportDirective(nsOrType.UsingDirectiveReference, callerIsSemanticModel); + seenNamespaceWithExtensionMethods = true; + } + + break; + } + + case SymbolKind.NamedType: + { + var count = methods.Count; + ((NamedTypeSymbol)nsOrType.NamespaceOrType).GetExtensionMethods(methods, name, arity, options); + + // If we found any extension methods, then consider this using as used. + if (methods.Count != count) + { + MarkImportDirective(nsOrType.UsingDirectiveReference, callerIsSemanticModel); + seenStaticClassWithExtensionMethods = true; + } + + break; + } + } + } + + if (seenNamespaceWithExtensionMethods && seenStaticClassWithExtensionMethods) + { + methods.RemoveDuplicates(); + } + } + + internal override void LookupSymbolsInSingleBinder( + LookupResult result, string name, int arity, ConsList? basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(result.IsClear); + + bool callerIsSemanticModel = originalBinder.IsSemanticModelBinder; + + foreach (var typeOrNamespace in this.GetUsings(basesBeingResolved)) + { + ImmutableArray candidates = Binder.GetCandidateMembers(typeOrNamespace.NamespaceOrType, name, options, originalBinder: originalBinder); + foreach (Symbol symbol in candidates) + { + if (!IsValidLookupCandidateInUsings(symbol)) + { + continue; + } + + // Found a match in our list of normal using directives. Mark the directive + // as being seen so that it won't be reported to the user as something that + // can be removed. + var res = originalBinder.CheckViability(symbol, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved); + if (res.Kind == LookupResultKind.Viable) + { + MarkImportDirective(typeOrNamespace.UsingDirectiveReference, callerIsSemanticModel); + } + + result.MergeEqual(res); + } + } + } + + private static bool IsValidLookupCandidateInUsings(Symbol symbol) + { + switch (symbol.Kind) + { + // lookup via "using namespace" ignores namespaces inside + case SymbolKind.Namespace: + return false; + + // lookup via "using static" ignores extension methods and non-static methods + case SymbolKind.Method: + if (!symbol.IsStatic || ((MethodSymbol)symbol).IsExtensionMethod) + { + return false; + } + + break; + + // types are considered static members for purposes of "using static" feature + // regardless of whether they are declared with "static" modifier or not + case SymbolKind.NamedType: + break; + + // lookup via "using static" ignores non-static members + default: + if (!symbol.IsStatic) + { + return false; + } + + break; + } + + return true; + } + + protected override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) + { + // If we are looking only for labels we do not need to search through the imports. + if ((options & LookupOptions.LabelsOnly) == 0) + { + // Add types within namespaces imported through usings, but don't add nested namespaces. + options = (options & ~(LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly)) | LookupOptions.MustNotBeNamespace; + + // look in all using namespaces + foreach (var namespaceSymbol in this.GetUsings(basesBeingResolved: null)) + { + foreach (var member in namespaceSymbol.NamespaceOrType.GetMembersUnordered()) + { + if (IsValidLookupCandidateInUsings(member) && originalBinder.CanAddLookupSymbolInfo(member, options, result, null)) + { + result.AddSymbol(member, member.Name, member.GetArity()); + } + } + } + } + } + + protected override SourceLocalSymbol? LookupLocal(SyntaxToken nameToken) + { + return null; + } + + protected override LocalFunctionSymbol? LookupLocalFunction(SyntaxToken nameToken) + { + return null; + } + + internal override uint LocalScopeDepth => Binder.ExternalScope; + + + internal override ImportChain? ImportChain + { + get + { + if (_lazyImportChain == null) + { + ImportChain? importChain = this.Next!.ImportChain; + + if (_withImportChainEntry) + { + importChain = new ImportChain(GetImports(), importChain); + } + + Interlocked.CompareExchange(ref _lazyImportChain, importChain, null); + } + + Debug.Assert(_lazyImportChain != null || !_withImportChainEntry); + + return _lazyImportChain; + } + } + + protected abstract Imports GetImports(); + + internal static WithUsingNamespacesAndTypesBinder Create(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next, bool withPreviousSubmissionImports = false, bool withImportChainEntry = false) + { + if (withPreviousSubmissionImports) + { + return new FromSyntaxWithPreviousSubmissionImports(declaringSymbol, declarationSyntax, next, withImportChainEntry); + } + + return new FromSyntax(declaringSymbol, declarationSyntax, next, withImportChainEntry); + } + + internal static WithUsingNamespacesAndTypesBinder Create(ImmutableArray namespacesOrTypes, Binder next, bool withImportChainEntry = false) + { + return new FromNamespacesOrTypes(namespacesOrTypes, next, withImportChainEntry); + } + + private sealed class FromSyntax : WithUsingNamespacesAndTypesBinder + { + private readonly SourceNamespaceSymbol _declaringSymbol; + private readonly CSharpSyntaxNode _declarationSyntax; + private ImmutableArray _lazyUsings; + + internal FromSyntax(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next, bool withImportChainEntry) + : base(next, withImportChainEntry) + { + Debug.Assert(declarationSyntax.IsKind(SyntaxKind.CompilationUnit) || declarationSyntax.IsKind(SyntaxKind.NamespaceDeclaration)); + _declaringSymbol = declaringSymbol; + _declarationSyntax = declarationSyntax; + } + + internal override ImmutableArray GetUsings(ConsList? basesBeingResolved) + { + if (_lazyUsings.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyUsings, _declaringSymbol.GetUsingNamespacesOrTypes(_declarationSyntax, basesBeingResolved)); + } + + return _lazyUsings; + } + + protected override Imports GetImports() + { + return _declaringSymbol.GetImports(_declarationSyntax, basesBeingResolved: null); + } + } + + private sealed class FromSyntaxWithPreviousSubmissionImports : WithUsingNamespacesAndTypesBinder + { + private readonly SourceNamespaceSymbol _declaringSymbol; + private readonly CSharpSyntaxNode _declarationSyntax; + private Imports? _lazyFullImports; + + internal FromSyntaxWithPreviousSubmissionImports(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next, bool withImportChainEntry) + : base(next, withImportChainEntry) + { + Debug.Assert(declarationSyntax.IsKind(SyntaxKind.CompilationUnit) || declarationSyntax.IsKind(SyntaxKind.NamespaceDeclaration)); + _declaringSymbol = declaringSymbol; + _declarationSyntax = declarationSyntax; + } + + internal override ImmutableArray GetUsings(ConsList? basesBeingResolved) + { + return GetImports(basesBeingResolved).Usings; + } + + private Imports GetImports(ConsList? basesBeingResolved) + { + if (_lazyFullImports is null) + { + Interlocked.CompareExchange(ref _lazyFullImports, + _declaringSymbol.DeclaringCompilation.GetPreviousSubmissionImports().Concat(_declaringSymbol.GetImports(_declarationSyntax, basesBeingResolved)), + null); + } + + return _lazyFullImports; + } + + protected override Imports GetImports() + { + return GetImports(basesBeingResolved: null); + } + } + + private sealed class FromNamespacesOrTypes : WithUsingNamespacesAndTypesBinder + { + private readonly ImmutableArray _usings; + + internal FromNamespacesOrTypes(ImmutableArray namespacesOrTypes, Binder next, bool withImportChainEntry) + : base(next, withImportChainEntry) + { + Debug.Assert(!namespacesOrTypes.IsDefault); + _usings = namespacesOrTypes; + } + + internal override ImmutableArray GetUsings(ConsList? basesBeingResolved) + { + return _usings; + } + + protected override Imports GetImports() + { + return Imports.Create(ImmutableDictionary.Empty, _usings, ImmutableArray.Empty); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs index 79767a5bf915d..856b444788ebc 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.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.Diagnostics; using System.Diagnostics.CodeAnalysis; using Roslyn.Utilities; @@ -37,6 +38,39 @@ public override int GetHashCode() { return Hash.Combine(Input.GetHashCode(), this.Symbol?.GetHashCode() ?? 0); } + +#if DEBUG + private int _id = -1; + + public int Id + { + get + { + return _id; + } + internal set + { + Debug.Assert(value > 0, "Id must be positive but was set to " + value); + Debug.Assert(_id == -1, $"Id was set to {_id} and set again to {value}"); + _id = value; + } + } + + internal string GetOutputTempDebuggerDisplay() + { + var id = Id; + return id switch + { + -1 => "", + + // Note that we never expect to create an evaluation with id 0 + // To do so would imply that dag evaluation assigns to the original input + 0 => "", + + _ => $"t{id}" + }; + } +#endif } partial class BoundDagIndexEvaluation diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDagTemp.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDagTemp.cs index a95a1b2a93077..74cacbbda5910 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDagTemp.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDagTemp.cs @@ -2,11 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { +#if DEBUG + [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] +#endif partial class BoundDagTemp { /// @@ -29,5 +33,24 @@ public override int GetHashCode() { return Hash.Combine(this.Type.GetHashCode(), Hash.Combine(this.Source?.GetHashCode() ?? 0, this.Index)); } + +#if DEBUG + internal new string GetDebuggerDisplay() + { + var name = Source?.Id switch + { + -1 => "", + + // Note that we never expect to have a non-null source with id 0 + // because id 0 is reserved for the original input. + // However, we also don't want to assert in a debugger display method. + 0 => "", + + null => "t0", + var id => $"t{id}" + }; + return $"{name}{(Source is BoundDagDeconstructEvaluation ? $".Item{(Index + 1).ToString()}" : "")}"; + } +#endif } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs index be06e6274bfbd..4ca09a1ee80ec 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs @@ -2,11 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { +#if DEBUG + [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] +#endif partial class BoundDagTest { public override bool Equals([NotNullWhen(true)] object? obj) => this.Equals(obj as BoundDagTest); @@ -39,5 +43,59 @@ public override int GetHashCode() { return Hash.Combine(Kind.GetHashCode(), Input.GetHashCode()); } + +#if DEBUG + internal new string GetDebuggerDisplay() + { + switch (this) + { + case BoundDagTypeEvaluation a: + return $"{a.GetOutputTempDebuggerDisplay()} = ({a.Type}){a.Input.GetDebuggerDisplay()}"; + case BoundDagPropertyEvaluation e: + return $"{e.GetOutputTempDebuggerDisplay()} = {e.Input.GetDebuggerDisplay()}.{e.Property.Name}"; + case BoundDagFieldEvaluation e: + return $"{e.GetOutputTempDebuggerDisplay()} = {e.Input.GetDebuggerDisplay()}.{e.Field.Name}"; + case BoundDagDeconstructEvaluation d: + var result = "("; + var first = true; + foreach (var param in d.DeconstructMethod.Parameters) + { + if (!first) + { + result += ", "; + } + first = false; + result += $"Item{param.Ordinal + 1}"; + } + result += $") {d.GetOutputTempDebuggerDisplay()} = {d.Input.GetDebuggerDisplay()}"; + return result; + case BoundDagIndexEvaluation i: + return $"{i.GetOutputTempDebuggerDisplay()} = {i.Input.GetDebuggerDisplay()}[{i.Index}]"; + case BoundDagEvaluation e: + return $"{e.GetOutputTempDebuggerDisplay()} = {e.Kind}({e.Input.GetDebuggerDisplay()})"; + case BoundDagTypeTest b: + var typeName = b.Type.TypeKind == TypeKind.Error ? "" : b.Type.ToString(); + return $"{b.Input.GetDebuggerDisplay()} is {typeName}"; + case BoundDagValueTest v: + return $"{v.Input.GetDebuggerDisplay()} == {v.Value.GetValueToDisplay()}"; + case BoundDagNonNullTest nn: + return $"{nn.Input.GetDebuggerDisplay()} != null"; + case BoundDagExplicitNullTest n: + return $"{n.Input.GetDebuggerDisplay()} == null"; + case BoundDagRelationalTest r: + var operatorName = r.Relation.Operator() switch + { + BinaryOperatorKind.LessThan => "<", + BinaryOperatorKind.LessThanOrEqual => "<=", + BinaryOperatorKind.GreaterThan => ">", + BinaryOperatorKind.GreaterThanOrEqual => ">=", + _ => "??" + }; + return $"{r.Input.GetDebuggerDisplay()} {operatorName} {r.Value.GetValueToDisplay()}"; + default: + return $"{this.Kind}({this.Input.GetDebuggerDisplay()})"; + } + } +#endif } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs index d48dd8ffa49c8..05e8c4962186f 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs @@ -206,101 +206,16 @@ BoundDecisionDagNode makeReplacement(BoundDecisionDagNode dag, Func.GetInstance(); - for (int i = 0; i < allStates.Length; i++) - { - stateIdentifierMap.Add(allStates[i], i); - } - - int nextTempNumber = 0; - var tempIdentifierMap = PooledDictionary.GetInstance(); - int tempIdentifier(BoundDagEvaluation e) - { - return (e == null) ? 0 : tempIdentifierMap.TryGetValue(e, out int value) ? value : tempIdentifierMap[e] = ++nextTempNumber; - } - - string tempName(BoundDagTemp t) - { - return $"t{tempIdentifier(t.Source)}{(t.Index != 0 ? $".{t.Index.ToString()}" : "")}"; - } var resultBuilder = PooledStringBuilder.GetInstance(); var result = resultBuilder.Builder; foreach (var state in allStates) { - result.AppendLine($"State " + stateIdentifierMap[state]); - switch (state) - { - case BoundTestDecisionDagNode node: - result.AppendLine($" Test: {dumpDagTest(node.Test)}"); - if (node.WhenTrue != null) - { - result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}"); - } - - if (node.WhenFalse != null) - { - result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}"); - } - break; - case BoundEvaluationDecisionDagNode node: - result.AppendLine($" Test: {dumpDagTest(node.Evaluation)}"); - if (node.Next != null) - { - result.AppendLine($" Next: {stateIdentifierMap[node.Next]}"); - } - break; - case BoundWhenDecisionDagNode node: - result.AppendLine($" WhenClause: " + node.WhenExpression?.Syntax); - if (node.WhenTrue != null) - { - result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}"); - } - - if (node.WhenFalse != null) - { - result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}"); - } - break; - case BoundLeafDecisionDagNode node: - result.AppendLine($" Case: {node.Label.Name}" + node.Syntax); - break; - default: - throw ExceptionUtilities.UnexpectedValue(state); - } + result.AppendLine(state.GetDebuggerDisplay()); } - stateIdentifierMap.Free(); - tempIdentifierMap.Free(); return resultBuilder.ToStringAndFree(); - - string dumpDagTest(BoundDagTest d) - { - switch (d) - { - case BoundDagTypeEvaluation a: - return $"t{tempIdentifier(a)}={a.Kind}(t{tempIdentifier(a)} as {a.Type})"; - case BoundDagEvaluation e: - return $"t{tempIdentifier(e)}={e.Kind}(t{tempIdentifier(e)})"; - case BoundDagTypeTest b: - return $"?{d.Kind}({tempName(d.Input)} is {b.Type})"; - case BoundDagValueTest v: - return $"?{d.Kind}({tempName(d.Input)} == {v.Value})"; - case BoundDagRelationalTest r: - var operatorName = r.Relation.Operator() switch - { - BinaryOperatorKind.LessThan => "<", - BinaryOperatorKind.LessThanOrEqual => "<=", - BinaryOperatorKind.GreaterThan => ">", - BinaryOperatorKind.GreaterThanOrEqual => ">=", - _ => "??" - }; - return $"?{d.Kind}({tempName(d.Input)} {operatorName} {r.Value})"; - default: - return $"?{d.Kind}({tempName(d.Input)})"; - } - } } #endif } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDagNode.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDagNode.cs index b65db06918b05..1d3fd497c172b 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDagNode.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDagNode.cs @@ -2,11 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { +#if DEBUG + [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] +#endif partial class BoundDecisionDagNode { public override bool Equals(object? other) @@ -46,5 +52,74 @@ public override int GetHashCode() throw ExceptionUtilities.UnexpectedValue(this); } } + +#if DEBUG + private int _id = -1; + + public int Id + { + get + { + return _id; + } + internal set + { + Debug.Assert(value >= 0, "Id must be non-negative but was set to " + value); + Debug.Assert(_id == -1, $"Id was set to {_id} and set again to {value}"); + _id = value; + } + } + + internal new string GetDebuggerDisplay() + { + var pooledBuilder = PooledStringBuilder.GetInstance(); + var builder = pooledBuilder.Builder; + builder.Append($"[{this.Id}]: "); + switch (this) + { + case BoundTestDecisionDagNode node: + builder.Append($"{node.Test.GetDebuggerDisplay()} "); + builder.Append(node.WhenTrue != null + ? $"? [{node.WhenTrue.Id}] " + : "? "); + + builder.Append(node.WhenFalse != null + ? $": [{node.WhenFalse.Id}]" + : ": "); + break; + case BoundEvaluationDecisionDagNode node: + builder.Append($"{node.Evaluation.GetDebuggerDisplay()}; "); + builder.Append(node.Next != null + ? $"[{node.Next.Id}]" + : ""); + break; + case BoundWhenDecisionDagNode node: + builder.Append("when "); + builder.Append(node.WhenExpression is { } when + ? $"({when.Syntax}) " + : " "); + + builder.Append(node.WhenTrue != null + ? $"? [{node.WhenTrue.Id}] " + : "? "); + + builder.Append(node.WhenFalse != null + ? $": [{node.WhenFalse.Id}]" + : ": "); + + break; + case BoundLeafDecisionDagNode node: + builder.Append(node.Label is GeneratedLabelSymbol generated + ? $"leaf {generated.NameNoSequence} `{node.Syntax}`" + : $"leaf `{node.Label.Name}`"); + break; + default: + builder.Append(base.GetDebuggerDisplay()); + break; + } + + return pooledBuilder.ToStringAndFree(); + } +#endif } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs index 20f10bfaa9231..7f03fb6cb86a0 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -22,7 +21,7 @@ internal interface IBoundLambdaOrFunction { MethodSymbol Symbol { get; } SyntaxNode Syntax { get; } - BoundBlock Body { get; } + BoundBlock? Body { get; } bool WasCompilerGenerated { get; } } @@ -32,7 +31,7 @@ internal sealed partial class BoundLocalFunctionStatement : IBoundLambdaOrFuncti SyntaxNode IBoundLambdaOrFunction.Syntax { get { return Syntax; } } - BoundBlock IBoundLambdaOrFunction.Body { get => this.Body; } + BoundBlock? IBoundLambdaOrFunction.Body { get => this.Body; } } internal readonly struct InferredLambdaReturnType @@ -71,8 +70,8 @@ internal sealed partial class BoundLambda : IBoundLambdaOrFunction SyntaxNode IBoundLambdaOrFunction.Syntax { get { return Syntax; } } - public BoundLambda(SyntaxNode syntax, UnboundLambda unboundLambda, BoundBlock body, ImmutableBindingDiagnostic diagnostics, Binder binder, TypeSymbol delegateType, InferredLambdaReturnType inferredReturnType) - : this(syntax, unboundLambda.WithNoCache(), (LambdaSymbol)binder.ContainingMemberOrLambda, body, diagnostics, binder, delegateType) + public BoundLambda(SyntaxNode syntax, UnboundLambda unboundLambda, BoundBlock body, ImmutableBindingDiagnostic diagnostics, Binder binder, TypeSymbol? delegateType, InferredLambdaReturnType inferredReturnType) + : this(syntax, unboundLambda.WithNoCache(), (LambdaSymbol)binder.ContainingMemberOrLambda!, body, diagnostics, binder, delegateType) { InferredReturnType = inferredReturnType; @@ -94,7 +93,7 @@ public TypeWithAnnotations GetInferredReturnType(ref CompoundUseSiteInfo - public TypeWithAnnotations GetInferredReturnType(ConversionsBase conversions, NullableWalker.VariableState nullableState, ref CompoundUseSiteInfo useSiteInfo) + public TypeWithAnnotations GetInferredReturnType(ConversionsBase? conversions, NullableWalker.VariableState? nullableState, ref CompoundUseSiteInfo useSiteInfo) { if (!InferredReturnType.UseSiteDiagnostics.IsEmpty) { @@ -158,13 +157,13 @@ internal LambdaSymbol CreateLambdaSymbol( internal static readonly TypeSymbol NoReturnExpression = new UnsupportedMetadataTypeSymbol(); internal static InferredLambdaReturnType InferReturnType(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypes, - BoundLambda node, Binder binder, TypeSymbol delegateType, bool isAsync, ConversionsBase conversions) + BoundLambda node, Binder binder, TypeSymbol? delegateType, bool isAsync, ConversionsBase conversions) { return InferReturnTypeImpl(returnTypes, node, binder, delegateType, isAsync, conversions, node.UnboundLambda.WithDependencies); } internal static InferredLambdaReturnType InferReturnType(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypes, - UnboundLambda node, Binder binder, TypeSymbol delegateType, bool isAsync, ConversionsBase conversions) + UnboundLambda node, Binder binder, TypeSymbol? delegateType, bool isAsync, ConversionsBase conversions) { return InferReturnTypeImpl(returnTypes, node, binder, delegateType, isAsync, conversions, node.WithDependencies); } @@ -173,7 +172,7 @@ internal static InferredLambdaReturnType InferReturnType(ArrayBuilder<(BoundRetu /// Behavior of this function should be kept aligned with . /// private static InferredLambdaReturnType InferReturnTypeImpl(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypes, - BoundNode node, Binder binder, TypeSymbol delegateType, bool isAsync, ConversionsBase conversions, bool withDependencies) + BoundNode node, Binder binder, TypeSymbol? delegateType, bool isAsync, ConversionsBase conversions, bool withDependencies) { var types = ArrayBuilder<(BoundExpression, TypeWithAnnotations)>.GetInstance(); bool hasReturnWithoutArgument = false; @@ -192,7 +191,7 @@ private static InferredLambdaReturnType InferReturnTypeImpl(ArrayBuilder<(BoundR } else { - types.Add((returnStatement.ExpressionOpt, type)); + types.Add((returnStatement.ExpressionOpt!, type)); } } @@ -208,7 +207,7 @@ private static InferredLambdaReturnType InferReturnTypeImpl(ArrayBuilder<(BoundR private static TypeWithAnnotations CalculateReturnType( Binder binder, ConversionsBase conversions, - TypeSymbol delegateType, + TypeSymbol? delegateType, ArrayBuilder<(BoundExpression, TypeWithAnnotations resultType)> returns, bool isAsync, BoundNode node, @@ -252,12 +251,11 @@ private static TypeWithAnnotations CalculateReturnType( // For async lambdas, the return type is the return type of the // delegate Invoke method if Invoke has a Task-like return type. // Otherwise the return type is Task or Task. - NamedTypeSymbol taskType = null; + NamedTypeSymbol? taskType = null; var delegateReturnType = delegateType?.GetDelegateType()?.DelegateInvokeMethod?.ReturnType as NamedTypeSymbol; - if ((object)delegateReturnType != null && !delegateReturnType.IsVoidType()) + if (delegateReturnType?.IsVoidType() == false) { - object builderType; - if (delegateReturnType.IsCustomTaskType(out builderType)) + if (delegateReturnType.IsCustomTaskType(builderArgument: out _)) { taskType = delegateReturnType.ConstructedFrom; } @@ -267,7 +265,7 @@ private static TypeWithAnnotations CalculateReturnType( { // No return statements have expressions; use delegate InvokeMethod // or infer type Task if delegate type not available. - var resultType = (object)taskType != null && taskType.Arity == 0 ? + var resultType = taskType?.Arity == 0 ? taskType : binder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task); return TypeWithAnnotations.Create(resultType); @@ -282,7 +280,7 @@ private static TypeWithAnnotations CalculateReturnType( // Some non-void best type T was found; use delegate InvokeMethod // or infer type Task if delegate type not available. - var taskTypeT = (object)taskType != null && taskType.Arity == 1 ? + var taskTypeT = taskType?.Arity == 1 ? taskType : binder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T); return TypeWithAnnotations.Create(taskTypeT.Construct(ImmutableArray.Create(bestResultType))); @@ -299,11 +297,11 @@ private BlockReturns(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> b public static void GetReturnTypes(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> builder, BoundBlock block) { - var visitor = new BoundLambda.BlockReturns(builder); + var visitor = new BlockReturns(builder); visitor.Visit(block); } - public override BoundNode Visit(BoundNode node) + public override BoundNode? Visit(BoundNode node) { if (!(node is BoundExpression)) { @@ -318,13 +316,13 @@ protected override BoundExpression VisitExpressionWithoutStackGuard(BoundExpress throw ExceptionUtilities.Unreachable; } - public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement node) + public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node) { // Do not recurse into local functions; we don't want their returns. return null; } - public override BoundNode VisitReturnStatement(BoundReturnStatement node) + public override BoundNode? VisitReturnStatement(BoundReturnStatement node) { var expression = node.ExpressionOpt; var type = (expression is null) ? @@ -338,26 +336,27 @@ public override BoundNode VisitReturnStatement(BoundReturnStatement node) internal partial class UnboundLambda { - private readonly NullableWalker.VariableState _nullableState; + private readonly NullableWalker.VariableState? _nullableState; public UnboundLambda( CSharpSyntaxNode syntax, Binder binder, bool withDependencies, + ImmutableArray> attributes, ImmutableArray refKinds, ImmutableArray types, ImmutableArray names, ImmutableArray discardsOpt, bool isAsync, bool isStatic) - : this(syntax, new PlainUnboundLambdaState(binder, names, discardsOpt, types, refKinds, isAsync, isStatic, includeCache: true), withDependencies, !types.IsDefault && types.Any(t => t.Type?.Kind == SymbolKind.ErrorType)) + : this(syntax, new PlainUnboundLambdaState(binder, attributes, names, discardsOpt, types, refKinds, isAsync, isStatic, includeCache: true), withDependencies, !types.IsDefault && types.Any(t => t.Type?.Kind == SymbolKind.ErrorType)) { Debug.Assert(binder != null); Debug.Assert(syntax.IsAnonymousFunction()); this.Data.SetUnboundLambda(this); } - private UnboundLambda(SyntaxNode syntax, UnboundLambdaState state, bool withDependencies, NullableWalker.VariableState nullableState, bool hasErrors) : + private UnboundLambda(SyntaxNode syntax, UnboundLambdaState state, bool withDependencies, NullableWalker.VariableState? nullableState, bool hasErrors) : this(syntax, state, withDependencies, hasErrors) { this._nullableState = nullableState; @@ -386,6 +385,9 @@ internal UnboundLambda WithNoCache() public MessageID MessageID { get { return Data.MessageID; } } + public NamedTypeSymbol? InferDelegateType(ref CompoundUseSiteInfo useSiteInfo) + => Data.InferDelegateType(ref useSiteInfo); + public BoundLambda Bind(NamedTypeSymbol delegateType) => SuppressIfNeeded(Data.Bind(delegateType)); @@ -409,6 +411,7 @@ public TypeWithAnnotations InferReturnType(ConversionsBase conversions, NamedTyp public bool GenerateSummaryErrors(BindingDiagnosticBag diagnostics) { return Data.GenerateSummaryErrors(diagnostics); } public bool IsAsync { get { return Data.IsAsync; } } public bool IsStatic => Data.IsStatic; + public SyntaxList ParameterAttributes(int index) { return Data.ParameterAttributes(index); } public TypeWithAnnotations ParameterTypeWithAnnotations(int index) { return Data.ParameterTypeWithAnnotations(index); } public TypeSymbol ParameterType(int index) { return ParameterTypeWithAnnotations(index).Type; } public Location ParameterLocation(int index) { return Data.ParameterLocation(index); } @@ -418,24 +421,25 @@ public TypeWithAnnotations InferReturnType(ConversionsBase conversions, NamedTyp internal abstract class UnboundLambdaState { - private UnboundLambda _unboundLambda; // we would prefer this readonly, but we have an initialization cycle. + private UnboundLambda _unboundLambda = null!; // we would prefer this readonly, but we have an initialization cycle. internal readonly Binder Binder; [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Avoid " + nameof(ConcurrentDictionary) + " which has a large default size, but this cache is normally small.")] - private ImmutableDictionary _bindingCache; + private ImmutableDictionary? _bindingCache; [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Avoid " + nameof(ConcurrentDictionary) + " which has a large default size, but this cache is normally small.")] - private ImmutableDictionary _returnInferenceCache; + private ImmutableDictionary? _returnInferenceCache; - private BoundLambda _errorBinding; + private BoundLambda? _errorBinding; public UnboundLambdaState(Binder binder, bool includeCache) { Debug.Assert(binder != null); + Debug.Assert(binder.ContainingMemberOrLambda != null); if (includeCache) { @@ -472,6 +476,7 @@ internal UnboundLambdaState WithCaching(bool includeCache) public abstract MessageID MessageID { get; } public abstract string ParameterName(int index); public abstract bool ParameterIsDiscard(int index); + public abstract SyntaxList ParameterAttributes(int index); public abstract bool HasSignature { get; } public abstract bool HasExplicitlyTypedParameterList { get; } public abstract int ParameterCount { get; } @@ -487,7 +492,7 @@ internal UnboundLambdaState WithCaching(bool includeCache) /// Return the bound expression if the lambda has an expression body and can be reused easily. /// This is an optimization only. Implementations can return null to skip reuse. /// - protected abstract BoundExpression GetLambdaExpressionBody(BoundBlock body); + protected abstract BoundExpression? GetLambdaExpressionBody(BoundBlock body); /// /// Produce a bound block for the expression returned from GetLambdaExpressionBody. @@ -502,8 +507,8 @@ public virtual void GenerateAnonymousFunctionConversionError(BindingDiagnosticBa // Returns the inferred return type, or null if none can be inferred. public BoundLambda Bind(NamedTypeSymbol delegateType) { - BoundLambda result; - if (!_bindingCache.TryGetValue(delegateType, out result)) + BoundLambda? result; + if (!_bindingCache!.TryGetValue(delegateType, out result)) { result = ReallyBind(delegateType); result = ImmutableInterlocked.GetOrAdd(ref _bindingCache, delegateType, result); @@ -515,7 +520,7 @@ public BoundLambda Bind(NamedTypeSymbol delegateType) internal IEnumerable InferredReturnTypes() { bool any = false; - foreach (var lambda in _returnInferenceCache.Values) + foreach (var lambda in _returnInferenceCache!.Values) { var type = lambda.InferredReturnType.TypeWithAnnotations; if (type.HasType) @@ -535,14 +540,14 @@ internal IEnumerable InferredReturnTypes() } } - private static MethodSymbol DelegateInvokeMethod(NamedTypeSymbol delegateType) + private static MethodSymbol? DelegateInvokeMethod(NamedTypeSymbol? delegateType) { return delegateType.GetDelegateType()?.DelegateInvokeMethod; } - private TypeWithAnnotations DelegateReturnTypeWithAnnotations(MethodSymbol invokeMethod, out RefKind refKind) + private static TypeWithAnnotations DelegateReturnTypeWithAnnotations(MethodSymbol? invokeMethod, out RefKind refKind) { - if ((object)invokeMethod == null) + if (invokeMethod is null) { refKind = CodeAnalysis.RefKind.None; return default; @@ -551,9 +556,9 @@ private TypeWithAnnotations DelegateReturnTypeWithAnnotations(MethodSymbol invok return invokeMethod.ReturnTypeWithAnnotations; } - private bool DelegateNeedsReturn(MethodSymbol invokeMethod) + private bool DelegateNeedsReturn(MethodSymbol? invokeMethod) { - if ((object)invokeMethod == null || invokeMethod.ReturnsVoid) + if (invokeMethod is null || invokeMethod.ReturnsVoid) { return false; } @@ -566,8 +571,59 @@ private bool DelegateNeedsReturn(MethodSymbol invokeMethod) return true; } + internal NamedTypeSymbol? InferDelegateType(ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(Binder.ContainingMemberOrLambda is { }); + + var compilation = Binder.Compilation; + var parameterRefKindsBuilder = ArrayBuilder.GetInstance(ParameterCount); + var parameterTypesBuilder = ArrayBuilder.GetInstance(ParameterCount); + for (int i = 0; i < ParameterCount; i++) + { + parameterRefKindsBuilder.Add(HasExplicitlyTypedParameterList ? RefKind(i) : default); + parameterTypesBuilder.Add(HasExplicitlyTypedParameterList ? ParameterTypeWithAnnotations(i) : TypeWithAnnotations.Create(compilation.GetSpecialType(SpecialType.System_Object))); + } + var parameterRefKinds = parameterRefKindsBuilder.ToImmutableAndFree(); + var parameterTypes = parameterTypesBuilder.ToImmutableAndFree(); + + var lambdaSymbol = new LambdaSymbol( + Binder, + compilation, + Binder.ContainingMemberOrLambda, + _unboundLambda, + parameterTypes, + parameterRefKinds, + refKind: default, + returnType: default); + var lambdaBodyBinder = new ExecutableCodeBinder(_unboundLambda.Syntax, lambdaSymbol, ParameterBinder(lambdaSymbol, Binder)); + var block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, BindingDiagnosticBag.Discarded); + var returnTypes = ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>.GetInstance(); + BoundLambda.BlockReturns.GetReturnTypes(returnTypes, block); + var inferredReturnType = BoundLambda.InferReturnType( + returnTypes, + _unboundLambda, + lambdaBodyBinder, + delegateType: null, + isAsync: IsAsync, + Binder.Conversions); + + if (HasExplicitlyTypedParameterList && (inferredReturnType.TypeWithAnnotations.HasType || returnTypes.Count == 0)) + { + return Binder.GetMethodGroupOrLambdaDelegateType( + inferredReturnType.RefKind, + inferredReturnType.TypeWithAnnotations, + parameterRefKinds, + parameterTypes, + ref useSiteInfo); + } + + return null; + } + private BoundLambda ReallyBind(NamedTypeSymbol delegateType) { + Debug.Assert(Binder.ContainingMemberOrLambda is { }); + var invokeMethod = DelegateInvokeMethod(delegateType); var returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out RefKind refKind); @@ -585,7 +641,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType) // we reuse the bound expression and apply any conversion to the return value // since the inferred return type was not used when binding for return inference. if (refKind == CodeAnalysis.RefKind.None && - _returnInferenceCache.TryGetValue(cacheKey, out BoundLambda returnInferenceLambda) && + _returnInferenceCache!.TryGetValue(cacheKey, out BoundLambda? returnInferenceLambda) && GetLambdaExpressionBody(returnInferenceLambda.Body) is BoundExpression expression && (lambdaSymbol = returnInferenceLambda.Symbol).RefKind == refKind && (object)LambdaSymbol.InferenceFailureReturnType != lambdaSymbol.ReturnType && @@ -602,6 +658,8 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType) block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, diagnostics); } + lambdaSymbol.GetDeclarationDiagnostics(diagnostics); + if (lambdaSymbol.RefKind == CodeAnalysis.RefKind.RefReadOnly) { compilation.EnsureIsReadOnlyAttributeExists(diagnostics, lambdaSymbol.DiagnosticLocation, modifyCompilation: false); @@ -676,6 +734,7 @@ internal LambdaSymbol CreateLambdaSymbol( ImmutableArray parameterRefKinds, RefKind refKind) => new LambdaSymbol( + Binder, Binder.Compilation, containingSymbol, _unboundLambda, @@ -718,11 +777,11 @@ private void ValidateUnsafeParameters(BindingDiagnosticBag diagnostics, Immutabl } private BoundLambda ReallyInferReturnType( - NamedTypeSymbol delegateType, + NamedTypeSymbol? delegateType, ImmutableArray parameterTypes, ImmutableArray parameterRefKinds) { - (var lambdaSymbol, var block, var lambdaBodyBinder, var diagnostics) = BindWithParameterAndReturnType(parameterTypes, parameterRefKinds, returnType: default); + (var lambdaSymbol, var block, var lambdaBodyBinder, var diagnostics) = BindWithParameterAndReturnType(parameterTypes, parameterRefKinds, returnType: default, refKind: CodeAnalysis.RefKind.None); var returnTypes = ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>.GetInstance(); BoundLambda.BlockReturns.GetReturnTypes(returnTypes, block); var inferredReturnType = BoundLambda.InferReturnType(returnTypes, _unboundLambda, lambdaBodyBinder, delegateType, lambdaSymbol.IsAsync, lambdaBodyBinder.Conversions); @@ -751,25 +810,18 @@ private BoundLambda ReallyInferReturnType( return result; } - private (LambdaSymbol lambdaSymbol, BoundBlock block, ExecutableCodeBinder lambdaBodyBinder, BindingDiagnosticBag diagnostics) BindWithDelegateAndReturnType( - NamedTypeSymbol delegateType, - TypeWithAnnotations returnType) - { - var cacheKey = ReturnInferenceCacheKey.Create(delegateType, IsAsync); - return BindWithParameterAndReturnType(cacheKey.ParameterTypes, cacheKey.ParameterRefKinds, returnType); - } - private (LambdaSymbol lambdaSymbol, BoundBlock block, ExecutableCodeBinder lambdaBodyBinder, BindingDiagnosticBag diagnostics) BindWithParameterAndReturnType( ImmutableArray parameterTypes, ImmutableArray parameterRefKinds, - TypeWithAnnotations returnType) + TypeWithAnnotations returnType, + RefKind refKind) { var diagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, _unboundLambda.WithDependencies); - var lambdaSymbol = CreateLambdaSymbol(Binder.ContainingMemberOrLambda, + var lambdaSymbol = CreateLambdaSymbol(Binder.ContainingMemberOrLambda!, returnType, parameterTypes, parameterRefKinds, - CodeAnalysis.RefKind.None); + refKind); var lambdaBodyBinder = new ExecutableCodeBinder(_unboundLambda.Syntax, lambdaSymbol, ParameterBinder(lambdaSymbol, Binder)); var block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, diagnostics); return (lambdaSymbol, block, lambdaBodyBinder, diagnostics); @@ -779,8 +831,8 @@ public BoundLambda BindForReturnTypeInference(NamedTypeSymbol delegateType) { var cacheKey = ReturnInferenceCacheKey.Create(delegateType, IsAsync); - BoundLambda result; - if (!_returnInferenceCache.TryGetValue(cacheKey, out result)) + BoundLambda? result; + if (!_returnInferenceCache!.TryGetValue(cacheKey, out result)) { result = ReallyInferReturnType(delegateType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds); result = ImmutableInterlocked.GetOrAdd(ref _returnInferenceCache, cacheKey, result); @@ -796,20 +848,20 @@ private sealed class ReturnInferenceCacheKey { public readonly ImmutableArray ParameterTypes; public readonly ImmutableArray ParameterRefKinds; - public readonly NamedTypeSymbol TaskLikeReturnTypeOpt; + public readonly NamedTypeSymbol? TaskLikeReturnTypeOpt; public static readonly ReturnInferenceCacheKey Empty = new ReturnInferenceCacheKey(ImmutableArray.Empty, ImmutableArray.Empty, null); - private ReturnInferenceCacheKey(ImmutableArray parameterTypes, ImmutableArray parameterRefKinds, NamedTypeSymbol taskLikeReturnTypeOpt) + private ReturnInferenceCacheKey(ImmutableArray parameterTypes, ImmutableArray parameterRefKinds, NamedTypeSymbol? taskLikeReturnTypeOpt) { Debug.Assert(parameterTypes.Length == parameterRefKinds.Length); - Debug.Assert((object)taskLikeReturnTypeOpt == null || ((object)taskLikeReturnTypeOpt == taskLikeReturnTypeOpt.ConstructedFrom && taskLikeReturnTypeOpt.IsCustomTaskType(out var builderArgument))); + Debug.Assert(taskLikeReturnTypeOpt is null || ((object)taskLikeReturnTypeOpt == taskLikeReturnTypeOpt.ConstructedFrom && taskLikeReturnTypeOpt.IsCustomTaskType(out var builderArgument))); this.ParameterTypes = parameterTypes; this.ParameterRefKinds = parameterRefKinds; this.TaskLikeReturnTypeOpt = taskLikeReturnTypeOpt; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if ((object)this == obj) { @@ -818,7 +870,7 @@ public override bool Equals(object obj) var other = obj as ReturnInferenceCacheKey; - if ((object)other == null || + if (other is null || other.ParameterTypes.Length != this.ParameterTypes.Length || !TypeSymbol.Equals(other.TaskLikeReturnTypeOpt, this.TaskLikeReturnTypeOpt, TypeCompareKind.ConsiderEverything2)) { @@ -847,15 +899,15 @@ public override int GetHashCode() return value; } - public static ReturnInferenceCacheKey Create(NamedTypeSymbol delegateType, bool isAsync) + public static ReturnInferenceCacheKey Create(NamedTypeSymbol? delegateType, bool isAsync) { // delegateType or DelegateInvokeMethod can be null in cases of malformed delegates // in such case we would want something trivial with no parameters var parameterTypes = ImmutableArray.Empty; var parameterRefKinds = ImmutableArray.Empty; - NamedTypeSymbol taskLikeReturnTypeOpt = null; - MethodSymbol invoke = DelegateInvokeMethod(delegateType); - if ((object)invoke != null) + NamedTypeSymbol? taskLikeReturnTypeOpt = null; + MethodSymbol? invoke = DelegateInvokeMethod(delegateType); + if (invoke is not null) { int parameterCount = invoke.ParameterCount; if (parameterCount > 0) @@ -876,7 +928,7 @@ public static ReturnInferenceCacheKey Create(NamedTypeSymbol delegateType, bool if (isAsync) { var delegateReturnType = invoke.ReturnType as NamedTypeSymbol; - if ((object)delegateReturnType != null && !delegateReturnType.IsVoidType()) + if (delegateReturnType?.IsVoidType() == false) { if (delegateReturnType.IsCustomTaskType(out var builderType)) { @@ -886,7 +938,7 @@ public static ReturnInferenceCacheKey Create(NamedTypeSymbol delegateType, bool } } - if (parameterTypes.IsEmpty && parameterRefKinds.IsEmpty && (object)taskLikeReturnTypeOpt == null) + if (parameterTypes.IsEmpty && parameterRefKinds.IsEmpty && taskLikeReturnTypeOpt is null) { return Empty; } @@ -942,47 +994,57 @@ private BoundLambda ReallyBindForErrorRecovery() // and bind. return - GuessBestBoundLambda(_bindingCache) - ?? rebind(GuessBestBoundLambda(_returnInferenceCache)) + GuessBestBoundLambda(_bindingCache!) + ?? rebind(GuessBestBoundLambda(_returnInferenceCache!)) ?? rebind(ReallyInferReturnType(null, ImmutableArray.Empty, ImmutableArray.Empty)); // Rebind a lambda to push target conversions through the return/result expressions - BoundLambda rebind(BoundLambda lambda) + [return: NotNullIfNotNull("lambda")] BoundLambda? rebind(BoundLambda? lambda) { if (lambda is null) return null; - var delegateType = (NamedTypeSymbol)lambda.Type; - InferredLambdaReturnType inferredReturnType = lambda.InferredReturnType; - var returnType = inferredReturnType.TypeWithAnnotations; - var refKind = lambda.Symbol.RefKind; - if (!returnType.HasType) + var delegateType = (NamedTypeSymbol?)lambda.Type; + var cacheKey = ReturnInferenceCacheKey.Create(delegateType, IsAsync); + return ReallyBindForErrorRecovery(delegateType, lambda.InferredReturnType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds); + } + } + + private BoundLambda ReallyBindForErrorRecovery( + NamedTypeSymbol? delegateType, + InferredLambdaReturnType inferredReturnType, + ImmutableArray parameterTypes, + ImmutableArray parameterRefKinds) + { + var returnType = inferredReturnType.TypeWithAnnotations; + var refKind = inferredReturnType.RefKind; + if (!returnType.HasType) + { + var invokeMethod = DelegateInvokeMethod(delegateType); + returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out refKind); + if (!returnType.HasType || returnType.Type.ContainsTypeParameter()) { - var invokeMethod = DelegateInvokeMethod(delegateType); - returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out refKind); - if (!returnType.HasType || returnType.Type.ContainsTypeParameter()) - { - var t = (inferredReturnType.HadExpressionlessReturn || inferredReturnType.NumExpressions == 0) - ? this.Binder.Compilation.GetSpecialType(SpecialType.System_Void) - : this.Binder.CreateErrorType(); - returnType = TypeWithAnnotations.Create(t); - refKind = CodeAnalysis.RefKind.None; - } + var t = (inferredReturnType.HadExpressionlessReturn || inferredReturnType.NumExpressions == 0) + ? this.Binder.Compilation.GetSpecialType(SpecialType.System_Void) + : this.Binder.CreateErrorType(); + returnType = TypeWithAnnotations.Create(t); + refKind = CodeAnalysis.RefKind.None; } - - (var lambdaSymbol, var block, var lambdaBodyBinder, var diagnostics) = BindWithDelegateAndReturnType(delegateType, returnType); - return new BoundLambda( - _unboundLambda.Syntax, - _unboundLambda, - block, - diagnostics.ToReadOnlyAndFree(), - lambdaBodyBinder, - delegateType, - new InferredLambdaReturnType(inferredReturnType.NumExpressions, inferredReturnType.HadExpressionlessReturn, refKind, returnType, ImmutableArray.Empty, ImmutableArray.Empty)) - { WasCompilerGenerated = _unboundLambda.WasCompilerGenerated }; } + + (var lambdaSymbol, var block, var lambdaBodyBinder, var diagnostics) = BindWithParameterAndReturnType(parameterTypes, parameterRefKinds, returnType, refKind); + return new BoundLambda( + _unboundLambda.Syntax, + _unboundLambda, + block, + diagnostics.ToReadOnlyAndFree(), + lambdaBodyBinder, + delegateType, + new InferredLambdaReturnType(inferredReturnType.NumExpressions, inferredReturnType.HadExpressionlessReturn, refKind, returnType, ImmutableArray.Empty, ImmutableArray.Empty)) + { WasCompilerGenerated = _unboundLambda.WasCompilerGenerated }; } - private static BoundLambda GuessBestBoundLambda(ImmutableDictionary candidates) + private static BoundLambda? GuessBestBoundLambda(ImmutableDictionary candidates) + where T : notnull { switch (candidates.Count) { @@ -1054,10 +1116,10 @@ public bool GenerateSummaryErrors(BindingDiagnosticBag diagnostics) // order when converted to a string. var convBags = from boundLambda in _bindingCache select boundLambda.Value.Diagnostics; - var retBags = from boundLambda in _returnInferenceCache.Values select boundLambda.Diagnostics; + var retBags = from boundLambda in _returnInferenceCache!.Values select boundLambda.Diagnostics; var allBags = convBags.Concat(retBags); - FirstAmongEqualsSet intersection = null; + FirstAmongEqualsSet? intersection = null; foreach (ImmutableBindingDiagnostic bag in allBags) { if (intersection == null) @@ -1079,7 +1141,7 @@ public bool GenerateSummaryErrors(BindingDiagnosticBag diagnostics) } } - FirstAmongEqualsSet union = null; + FirstAmongEqualsSet? union = null; foreach (ImmutableBindingDiagnostic bag in allBags) { @@ -1145,8 +1207,8 @@ private static int CanonicallyCompareDiagnostics(Diagnostic x, Diagnostic y) var ny = y.Arguments?.Count ?? 0; for (int i = 0, n = Math.Min(nx, ny); i < n; i++) { - object argx = x.Arguments[i]; - object argy = y.Arguments[i]; + object? argx = x.Arguments![i]; + object? argy = y.Arguments![i]; int argCompare = string.CompareOrdinal(argx?.ToString(), argy?.ToString()); if (argCompare != 0) @@ -1159,6 +1221,7 @@ private static int CanonicallyCompareDiagnostics(Diagnostic x, Diagnostic y) internal sealed class PlainUnboundLambdaState : UnboundLambdaState { + private readonly ImmutableArray> _parameterAttributes; private readonly ImmutableArray _parameterNames; private readonly ImmutableArray _parameterIsDiscardOpt; private readonly ImmutableArray _parameterTypesWithAnnotations; @@ -1168,6 +1231,7 @@ internal sealed class PlainUnboundLambdaState : UnboundLambdaState internal PlainUnboundLambdaState( Binder binder, + ImmutableArray> parameterAttributes, ImmutableArray parameterNames, ImmutableArray parameterIsDiscardOpt, ImmutableArray parameterTypesWithAnnotations, @@ -1177,6 +1241,7 @@ internal PlainUnboundLambdaState( bool includeCache) : base(binder, includeCache) { + _parameterAttributes = parameterAttributes; _parameterNames = parameterNames; _parameterIsDiscardOpt = parameterIsDiscardOpt; _parameterTypesWithAnnotations = parameterTypesWithAnnotations; @@ -1219,12 +1284,17 @@ public override Location ParameterLocation(int index) case SyntaxKind.ParenthesizedLambdaExpression: return ((ParenthesizedLambdaExpressionSyntax)syntax).ParameterList.Parameters[index].Identifier.GetLocation(); case SyntaxKind.AnonymousMethodExpression: - return ((AnonymousMethodExpressionSyntax)syntax).ParameterList.Parameters[index].Identifier.GetLocation(); + return ((AnonymousMethodExpressionSyntax)syntax).ParameterList!.Parameters[index].Identifier.GetLocation(); } } private bool IsExpressionLambda { get { return Body.Kind() != SyntaxKind.Block; } } + public override SyntaxList ParameterAttributes(int index) + { + return _parameterAttributes.IsDefault ? default : _parameterAttributes[index]; + } + public override string ParameterName(int index) { Debug.Assert(!_parameterNames.IsDefault && 0 <= index && index < _parameterNames.Length); @@ -1251,10 +1321,10 @@ public override TypeWithAnnotations ParameterTypeWithAnnotations(int index) protected override UnboundLambdaState WithCachingCore(bool includeCache) { - return new PlainUnboundLambdaState(Binder, _parameterNames, _parameterIsDiscardOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _isAsync, _isStatic, includeCache); + return new PlainUnboundLambdaState(Binder, _parameterAttributes, _parameterNames, _parameterIsDiscardOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _isAsync, _isStatic, includeCache); } - protected override BoundExpression GetLambdaExpressionBody(BoundBlock body) + protected override BoundExpression? GetLambdaExpressionBody(BoundBlock body) { if (IsExpressionLambda) { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 539c4c0801cd7..6a05f90e59e15 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -1132,7 +1132,7 @@ Missing partial modifier on declaration of type '{0}'; another partial declaration of this type exists - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces Partial declarations of '{0}' have conflicting accessibility modifiers @@ -1725,7 +1725,7 @@ If such a class is used as a base class and if the deriving class defines a dest Alias '{0}' conflicts with {1} definition - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation The Conditional attribute is not valid on '{0}' because its return type is not void @@ -3789,7 +3789,7 @@ Give the compiler some way to differentiate the methods. For example, you can gi The 'async' modifier can only be used in methods that have a body. - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. foreach statement cannot operate on enumerators of type '{0}' in async or iterator methods because '{0}' is a ref struct. @@ -3941,6 +3941,9 @@ You should consider suppressing the warning only if you're sure that you don't w Attributes are not valid in this context. + + Attributes on lambda expressions require a parenthesized parameter list. + 'extern alias' is not valid in this context @@ -5833,6 +5836,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ There is no target type for the default literal. + + The delegate type could not be inferred. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. @@ -6274,8 +6280,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The receiver of a `with` expression must have a non-void type. - - The receiver type '{0}' is not a valid record type. + + The receiver type '{0}' is not a valid record type and is not a struct type. Init-only property or indexer '{0}' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. @@ -6419,7 +6425,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Only records may inherit from records. - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. No accessible copy constructor found in base type '{0}'. @@ -6563,6 +6569,19 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Mixed declarations and expressions in deconstruction + + record structs + 'record structs' is not localizable. + + + with on structs + + + with on anonymous types + + + positional fields in records + variance safety for static interface members @@ -6591,6 +6610,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The primary constructor conflicts with the synthesized copy constructor. + + lambda attributes + + + inferred delegate type + Comparison of function pointers might yield an unexpected result, since pointers to the same function may be distinct. @@ -6606,4 +6631,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + + The positional member '{0}' found corresponding to this parameter is hidden. + + + global using directive + + + A global using directive cannot be used in a namespace declaration. + + + A global using directive must precede all non-global using directives. + diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs new file mode 100644 index 0000000000000..2848b30694105 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.UsingsFromOptionsAndDiagnostics.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + partial class CSharpCompilation + { + private class UsingsFromOptionsAndDiagnostics + { + public static readonly UsingsFromOptionsAndDiagnostics Empty = new UsingsFromOptionsAndDiagnostics() { UsingNamespacesOrTypes = ImmutableArray.Empty, Diagnostics = null }; + + public ImmutableArray UsingNamespacesOrTypes { get; init; } + public DiagnosticBag? Diagnostics { get; init; } + + // completion state that tracks whether validation was done/not done/currently in process. + private SymbolCompletionState _state; + + public static UsingsFromOptionsAndDiagnostics FromOptions(CSharpCompilation compilation) + { + var usings = compilation.Options.Usings; + + if (usings.Length == 0) + { + return Empty; + } + + var diagnostics = new DiagnosticBag(); + var usingsBinder = new InContainerBinder(compilation.GlobalNamespace, new BuckStopsHereBinder(compilation)); + var boundUsings = ArrayBuilder.GetInstance(); + var uniqueUsings = PooledHashSet.GetInstance(); + + foreach (string @using in usings) + { + if (!@using.IsValidClrNamespaceName()) + { + continue; + } + + string[] identifiers = @using.Split('.'); + NameSyntax qualifiedName = SyntaxFactory.IdentifierName(identifiers[0]); + + for (int j = 1; j < identifiers.Length; j++) + { + qualifiedName = SyntaxFactory.QualifiedName(left: qualifiedName, right: SyntaxFactory.IdentifierName(identifiers[j])); + } + + var directiveDiagnostics = BindingDiagnosticBag.GetInstance(); + Debug.Assert(directiveDiagnostics.DiagnosticBag is object); + Debug.Assert(directiveDiagnostics.DependenciesBag is object); + + var imported = usingsBinder.BindNamespaceOrTypeSymbol(qualifiedName, directiveDiagnostics).NamespaceOrTypeSymbol; + if (uniqueUsings.Add(imported)) + { + boundUsings.Add(new NamespaceOrTypeAndUsingDirective(imported, null, dependencies: directiveDiagnostics.DependenciesBag.ToImmutableArray())); + } + + diagnostics.AddRange(directiveDiagnostics.DiagnosticBag); + directiveDiagnostics.Free(); + } + + if (diagnostics.IsEmptyWithoutResolution) + { + diagnostics = null; + } + + uniqueUsings.Free(); + + if (boundUsings.Count == 0 && diagnostics is null) + { + boundUsings.Free(); + return Empty; + } + + return new UsingsFromOptionsAndDiagnostics() { UsingNamespacesOrTypes = boundUsings.ToImmutableAndFree(), Diagnostics = diagnostics }; + } + + internal void Complete(CSharpCompilation compilation, CancellationToken cancellationToken) + { + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + var incompletePart = _state.NextIncompletePart; + switch (incompletePart) + { + case CompletionPart.StartValidatingImports: + { + if (_state.NotePartComplete(CompletionPart.StartValidatingImports)) + { + Validate(compilation); + _state.NotePartComplete(CompletionPart.FinishValidatingImports); + } + } + break; + + case CompletionPart.FinishValidatingImports: + // some other thread has started validating imports (otherwise we would be in the case above) so + // we just wait for it to both finish and report the diagnostics. + Debug.Assert(_state.HasComplete(CompletionPart.StartValidatingImports)); + _state.SpinWaitComplete(CompletionPart.FinishValidatingImports, cancellationToken); + break; + + case CompletionPart.None: + return; + + default: + // any other values are completion parts intended for other kinds of symbols + _state.NotePartComplete(CompletionPart.All & ~CompletionPart.ImportsAll); + break; + } + + _state.SpinWaitComplete(incompletePart, cancellationToken); + } + } + + private void Validate(CSharpCompilation compilation) + { + if (this == Empty) + { + return; + } + + DiagnosticBag semanticDiagnostics = compilation.DeclarationDiagnostics; + var diagnostics = BindingDiagnosticBag.GetInstance(); + Debug.Assert(diagnostics.DiagnosticBag is object); + Debug.Assert(diagnostics.DependenciesBag is object); + + var corLibrary = compilation.SourceAssembly.CorLibrary; + var conversions = new TypeConversions(corLibrary); + foreach (var @using in UsingNamespacesOrTypes) + { + diagnostics.Clear(); + diagnostics.AddDependencies(@using.Dependencies); + + NamespaceOrTypeSymbol target = @using.NamespaceOrType; + + // Check if `using static` directives meet constraints. + Debug.Assert(@using.UsingDirective is null); + if (target.IsType) + { + var typeSymbol = (TypeSymbol)target; + var location = NoLocation.Singleton; + typeSymbol.CheckAllConstraints(compilation, conversions, location, diagnostics); + } + + semanticDiagnostics.AddRange(diagnostics.DiagnosticBag); + + recordImportDependencies(target); + } + + if (Diagnostics != null && !Diagnostics.IsEmptyWithoutResolution) + { + semanticDiagnostics.AddRange(Diagnostics.AsEnumerable()); + } + + diagnostics.Free(); + + void recordImportDependencies(NamespaceOrTypeSymbol target) + { + if (target.IsNamespace) + { + diagnostics.AddAssembliesUsedByNamespaceReference((NamespaceSymbol)target); + } + + compilation.AddUsedAssemblies(diagnostics.DependenciesBag); + } + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 8116475713893..8959add27efdf 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -49,7 +49,8 @@ public sealed partial class CSharpCompilation : Compilation // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! private readonly CSharpCompilationOptions _options; - private readonly Lazy _globalImports; + private readonly Lazy _usingsFromOptions; + private readonly Lazy> _globalImports; private readonly Lazy _previousSubmissionImports; private readonly Lazy _globalNamespaceAlias; // alias symbol used to resolve "global::". private readonly Lazy _scriptClass; @@ -132,6 +133,12 @@ internal Conversions Conversions /// private HashSet? _lazyCompilationUnitCompletedTrees; + /// + /// The set of trees for which enough analysis was performed in order to record usage of using directives. + /// Once all trees are processed the value is set to null. + /// + private ImmutableHashSet? _usageOfUsingsRecordedInTrees = ImmutableHashSet.Empty; + /// /// Nullable analysis data for methods, parameter default values, and attributes. /// The key is a symbol for methods or parameters, and syntax for attributes. @@ -151,6 +158,8 @@ internal NullableData(int maxRecursionDepth = -1) } } + internal ImmutableHashSet? UsageOfUsingsRecordedInTrees => Volatile.Read(ref _usageOfUsingsRecordedInTrees); + public override string Language { get @@ -458,7 +467,8 @@ private CSharpCompilation( this.builtInOperators = new BuiltInOperators(this); _scriptClass = new Lazy(BindScriptClass); - _globalImports = new Lazy(BindGlobalImports); + _globalImports = new Lazy>(BindGlobalImports); + _usingsFromOptions = new Lazy(BindUsingsFromOptions); _previousSubmissionImports = new Lazy(ExpandPreviousSubmissionImports); _globalNamespaceAlias = new Lazy(CreateGlobalNamespaceAlias); _anonymousTypeManager = new AnonymousTypeManager(this); @@ -1435,9 +1445,48 @@ internal bool IsSubmissionSyntaxTree(SyntaxTree tree) /// /// Global imports (including those from previous submissions, if there are any). /// - internal Imports GlobalImports => _globalImports.Value; + internal ImmutableArray GlobalImports => _globalImports.Value; + + private ImmutableArray BindGlobalImports() + { + var usingsFromoptions = UsingsFromOptions; + var previousSubmission = PreviousSubmission; + var previousSubmissionImports = previousSubmission is object ? Imports.ExpandPreviousSubmissionImports(previousSubmission.GlobalImports, this) : ImmutableArray.Empty; + + if (usingsFromoptions.UsingNamespacesOrTypes.IsEmpty) + { + return previousSubmissionImports; + } + else if (previousSubmissionImports.IsEmpty) + { + return usingsFromoptions.UsingNamespacesOrTypes; + } + + var boundUsings = ArrayBuilder.GetInstance(); + var uniqueUsings = PooledHashSet.GetInstance(); + + boundUsings.AddRange(usingsFromoptions.UsingNamespacesOrTypes); + uniqueUsings.AddAll(usingsFromoptions.UsingNamespacesOrTypes.Select(static unt => unt.NamespaceOrType)); + + foreach (var previousUsing in previousSubmissionImports) + { + if (uniqueUsings.Add(previousUsing.NamespaceOrType)) + { + boundUsings.Add(previousUsing); + } + } + + uniqueUsings.Free(); + + return boundUsings.ToImmutableAndFree(); + } + + /// + /// Global imports not including those from previous submissions. + /// + private UsingsFromOptionsAndDiagnostics UsingsFromOptions => _usingsFromOptions.Value; - private Imports BindGlobalImports() => Imports.FromGlobalUsings(this); + private UsingsFromOptionsAndDiagnostics BindUsingsFromOptions() => UsingsFromOptionsAndDiagnostics.FromOptions(this); /// /// Imports declared by this submission (null if this isn't one). @@ -1454,8 +1503,7 @@ internal Imports GetSubmissionImports() return Imports.Empty; } - var binder = GetBinderFactory(tree).GetImportsBinder((CSharpSyntaxNode)tree.GetRoot()); - return binder.GetImports(basesBeingResolved: null); + return ((SourceNamespaceSymbol)SourceModule.GlobalNamespace).GetImports((CSharpSyntaxNode)tree.GetRoot(), basesBeingResolved: null); } /// @@ -2284,17 +2332,9 @@ internal Binder GetBinder(CSharpSyntaxNode syntax) return GetBinderFactory(syntax.SyntaxTree).GetBinder(syntax); } - /// - /// Returns imported symbols for the given declaration. - /// - internal Imports GetImports(SingleNamespaceDeclaration declaration) - { - return GetBinderFactory(declaration.SyntaxReference.SyntaxTree).GetImportsBinder((CSharpSyntaxNode)declaration.SyntaxReference.GetSyntax()).GetImports(basesBeingResolved: null); - } - private AliasSymbol CreateGlobalNamespaceAlias() { - return AliasSymbol.CreateGlobalNamespaceAlias(this.GlobalNamespace, new InContainerBinder(this.GlobalNamespace, new BuckStopsHereBinder(this))); + return AliasSymbol.CreateGlobalNamespaceAlias(this.GlobalNamespace); } private void CompleteTree(SyntaxTree tree) @@ -2316,12 +2356,12 @@ private void CompleteTree(SyntaxTree tree) } } - internal override void ReportUnusedImports(SyntaxTree? filterTree, DiagnosticBag diagnostics, CancellationToken cancellationToken) + internal override void ReportUnusedImports(DiagnosticBag diagnostics, CancellationToken cancellationToken) { - ReportUnusedImports(filterTree, new BindingDiagnosticBag(diagnostics), cancellationToken); + ReportUnusedImports(filterTree: null, new BindingDiagnosticBag(diagnostics), cancellationToken); } - internal void ReportUnusedImports(SyntaxTree? filterTree, BindingDiagnosticBag diagnostics, CancellationToken cancellationToken) + private void ReportUnusedImports(SyntaxTree? filterTree, BindingDiagnosticBag diagnostics, CancellationToken cancellationToken) { if (_lazyImportInfos != null && (filterTree is null || ReportUnusedImportsInTree(filterTree))) { @@ -2433,6 +2473,11 @@ internal override void CompleteTrees(SyntaxTree? filterTree) } } } + + if (filterTree is null) + { + _usageOfUsingsRecordedInTrees = null; + } } internal void RecordImport(UsingDirectiveSyntax syntax) @@ -2762,32 +2807,137 @@ private ImmutableArray GetDiagnosticsForMethodBodiesInTree(SyntaxTre var diagnostics = DiagnosticBag.GetInstance(); var bindingDiagnostics = new BindingDiagnosticBag(diagnostics); - MethodCompiler.CompileMethodBodies( - compilation: this, - moduleBeingBuiltOpt: null, - emittingPdb: false, - emitTestCoverageData: false, - hasDeclarationErrors: false, - emitMethodBodies: false, - diagnostics: bindingDiagnostics, - filterOpt: s => IsDefinedOrImplementedInSourceTree(s, tree, span), - cancellationToken: cancellationToken); - - DocumentationCommentCompiler.WriteDocumentationCommentXml(this, null, null, bindingDiagnostics, cancellationToken, tree, span); - // Report unused directives only if computing diagnostics for the entire tree. // Otherwise we cannot determine if a particular directive is used outside of the given sub-span within the tree. - if (!span.HasValue || span.Value == tree.GetRoot(cancellationToken).FullSpan) + bool reportUnusedUsings = (!span.HasValue || span.Value == tree.GetRoot(cancellationToken).FullSpan) && ReportUnusedImportsInTree(tree); + bool recordUsageOfUsingsInAllTrees = false; + + if (reportUnusedUsings && UsageOfUsingsRecordedInTrees is not null) { - ReportUnusedImports(tree, diagnostics, cancellationToken); + foreach (var singleDeclaration in ((SourceNamespaceSymbol)SourceModule.GlobalNamespace).MergedDeclaration.Declarations) + { + if (singleDeclaration.SyntaxReference.SyntaxTree == tree) + { + if (singleDeclaration.HasGlobalUsings) + { + // Global Using directives can be used in any tree. Make sure we collect usage information from all of them. + recordUsageOfUsingsInAllTrees = true; + } + + break; + } + } + } + + if (recordUsageOfUsingsInAllTrees && UsageOfUsingsRecordedInTrees?.IsEmpty == true) + { + Debug.Assert(reportUnusedUsings); + + // Simply compile the world + compileMethodBodiesAndDocComments(filterTree: null, filterSpan: null, bindingDiagnostics, cancellationToken); + _usageOfUsingsRecordedInTrees = null; + } + else + { + // Always compile the target tree + compileMethodBodiesAndDocComments(filterTree: tree, filterSpan: span, bindingDiagnostics, cancellationToken); + + if (reportUnusedUsings) + { + registeredUsageOfUsingsInTree(tree); + } + + // Compile other trees if we need to, but discard diagnostics from them. + if (recordUsageOfUsingsInAllTrees) + { + Debug.Assert(reportUnusedUsings); + + var discarded = new BindingDiagnosticBag(DiagnosticBag.GetInstance()); + Debug.Assert(discarded.DiagnosticBag is object); + + foreach (var otherTree in SyntaxTrees) + { + var trackingSet = UsageOfUsingsRecordedInTrees; + + if (trackingSet is null) + { + break; + } + + if (!trackingSet.Contains(otherTree)) + { + compileMethodBodiesAndDocComments(filterTree: otherTree, filterSpan: null, discarded, cancellationToken); + registeredUsageOfUsingsInTree(otherTree); + discarded.DiagnosticBag.Clear(); + } + } + + discarded.DiagnosticBag.Free(); + } + } + + if (reportUnusedUsings) + { + ReportUnusedImports(tree, bindingDiagnostics, cancellationToken); } return diagnostics.ToReadOnlyAndFree(); + + void compileMethodBodiesAndDocComments(SyntaxTree? filterTree, TextSpan? filterSpan, BindingDiagnosticBag bindingDiagnostics, CancellationToken cancellationToken) + { + MethodCompiler.CompileMethodBodies( + compilation: this, + moduleBeingBuiltOpt: null, + emittingPdb: false, + emitTestCoverageData: false, + hasDeclarationErrors: false, + emitMethodBodies: false, + diagnostics: bindingDiagnostics, + filterOpt: filterTree is object ? (Predicate?)(s => IsDefinedOrImplementedInSourceTree(s, filterTree, filterSpan)) : (Predicate?)null, + cancellationToken: cancellationToken); + + DocumentationCommentCompiler.WriteDocumentationCommentXml(this, null, null, bindingDiagnostics, cancellationToken, filterTree, filterSpan); + } + + void registeredUsageOfUsingsInTree(SyntaxTree tree) + { + var current = UsageOfUsingsRecordedInTrees; + + while (true) + { + if (current is null) + { + break; + } + + var updated = current.Add(tree); + + if ((object)updated == current) + { + break; + } + + if (updated.Count == SyntaxTrees.Length) + { + _usageOfUsingsRecordedInTrees = null; + break; + } + + var recent = Interlocked.CompareExchange(ref _usageOfUsingsRecordedInTrees, updated, current); + + if (recent == (object)current) + { + break; + } + + current = recent; + } + } } private ImmutableBindingDiagnostic GetSourceDeclarationDiagnostics(SyntaxTree? syntaxTree = null, TextSpan? filterSpanWithinTree = null, Func, SyntaxTree, TextSpan?, IEnumerable>? locationFilterOpt = null, CancellationToken cancellationToken = default) { - GlobalImports.Complete(cancellationToken); + UsingsFromOptions.Complete(this, cancellationToken); SourceLocation? location = null; if (syntaxTree != null) @@ -3202,7 +3352,6 @@ internal override EmitDifferenceResult EmitDifference( Stream metadataStream, Stream ilStream, Stream pdbStream, - ICollection updatedMethods, CompilationTestData? testData, CancellationToken cancellationToken) { @@ -3214,7 +3363,6 @@ internal override EmitDifferenceResult EmitDifference( metadataStream, ilStream, pdbStream, - updatedMethods, testData, cancellationToken); } diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index a3bf13c2fb869..84236d3cd16a3 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -4547,8 +4547,7 @@ internal static ImmutableArray GetReducedAndFilteredMethodGroupSym { var extensionMethods = ArrayBuilder.GetInstance(); var otherBinder = scope.Binder; - otherBinder.GetCandidateExtensionMethods(scope.SearchUsingsNotNamespace, - extensionMethods, + otherBinder.GetCandidateExtensionMethods(extensionMethods, name, arity, options, diff --git a/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs index 330378a2052a6..7d6df06e584b2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs @@ -270,15 +270,19 @@ internal override bool TryGetSpeculativeSemanticModelCore(SyntaxTreeSemanticMode internal override bool TryGetSpeculativeSemanticModelCore(SyntaxTreeSemanticModel parentModel, int position, PrimaryConstructorBaseTypeSyntax constructorInitializer, out SemanticModel speculativeModel) { if (MemberSymbol is SynthesizedRecordConstructor primaryCtor && - Root.FindToken(position).Parent?.AncestorsAndSelf().OfType().FirstOrDefault() == primaryCtor.GetSyntax().PrimaryConstructorBaseType) + primaryCtor.GetSyntax() is RecordDeclarationSyntax recordDecl) { - var binder = this.GetEnclosingBinder(position); - if (binder != null) + Debug.Assert(recordDecl.Kind() == SyntaxKind.RecordDeclaration); + if (Root.FindToken(position).Parent?.AncestorsAndSelf().OfType().FirstOrDefault() == recordDecl.PrimaryConstructorBaseTypeIfClass) { - binder = new WithNullableContextBinder(SyntaxTree, position, binder); - binder = new ExecutableCodeBinder(constructorInitializer, primaryCtor, binder); - speculativeModel = CreateSpeculative(parentModel, primaryCtor, constructorInitializer, binder, position); - return true; + var binder = this.GetEnclosingBinder(position); + if (binder != null) + { + binder = new WithNullableContextBinder(SyntaxTree, position, binder); + binder = new ExecutableCodeBinder(constructorInitializer, primaryCtor, binder); + speculativeModel = CreateSpeculative(parentModel, primaryCtor, constructorInitializer, binder, position); + return true; + } } } diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index 61f0a48e49946..7592924da90f0 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -181,7 +181,7 @@ internal override IOperation GetOperationWorker(CSharpSyntaxNode node, Cancellat case AccessorDeclarationSyntax accessor: model = (accessor.Body != null || accessor.ExpressionBody != null) ? GetOrAddModel(node) : null; break; - case RecordDeclarationSyntax { ParameterList: { }, PrimaryConstructorBaseType: { } } recordDeclaration when TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor: + case RecordDeclarationSyntax { ParameterList: { }, PrimaryConstructorBaseTypeIfClass: { } } recordDeclaration when TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor: model = GetOrAddModel(recordDeclaration); break; default: @@ -814,7 +814,7 @@ private MemberSemanticModel GetMemberModel(int position) } else { - var argumentList = recordDecl.PrimaryConstructorBaseType?.ArgumentList; + var argumentList = recordDecl.PrimaryConstructorBaseTypeIfClass?.ArgumentList; outsideMemberDecl = argumentList is null || !LookupPosition.IsBetweenTokens(position, argumentList.OpenParenToken, argumentList.CloseParenToken); } } @@ -877,7 +877,7 @@ internal override MemberSemanticModel GetMemberModel(SyntaxNode node) { var recordDecl = (RecordDeclarationSyntax)memberDecl; return recordDecl.ParameterList is object && - recordDecl.PrimaryConstructorBaseType is PrimaryConstructorBaseTypeSyntax baseWithArguments && + recordDecl.PrimaryConstructorBaseTypeIfClass is PrimaryConstructorBaseTypeSyntax baseWithArguments && (node == baseWithArguments || baseWithArguments.ArgumentList.FullSpan.Contains(span)) ? GetOrAddModel(memberDecl) : null; } @@ -1932,25 +1932,27 @@ public override IAliasSymbol GetDeclaredSymbol( return null; } - Binder binder = _binderFactory.GetImportsBinder(declarationSyntax.Parent); - var imports = binder.GetImports(basesBeingResolved: null); - var alias = imports.UsingAliases[declarationSyntax.Alias.Name.Identifier.ValueText]; + Binder binder = _binderFactory.GetInNamespaceBinder(declarationSyntax.Parent); - if ((object)alias.Alias == null) + for (; binder != null; binder = binder.Next) { - // Case: no aliases - return null; - } - else if (alias.Alias.Locations[0].SourceSpan == declarationSyntax.Alias.Name.Span) - { - // Case: first alias (there may be others) - return alias.Alias.GetPublicSymbol(); - } - else - { - // Case: multiple aliases, not the first (see DevDiv #9368) - return new AliasSymbol(binder, declarationSyntax.Name, declarationSyntax.Alias).GetPublicSymbol(); + var usingAliases = binder.UsingAliases; + + if (!usingAliases.IsDefault) + { + foreach (var alias in usingAliases) + { + if (alias.Alias.Locations[0].SourceSpan == declarationSyntax.Alias.Name.Span) + { + return alias.Alias.GetPublicSymbol(); + } + } + + break; + } } + + return null; } /// @@ -1963,19 +1965,27 @@ public override IAliasSymbol GetDeclaredSymbol( { CheckSyntaxNode(declarationSyntax); - var binder = _binderFactory.GetImportsBinder(declarationSyntax.Parent); - var imports = binder.GetImports(basesBeingResolved: null); + var binder = _binderFactory.GetInNamespaceBinder(declarationSyntax.Parent); - // TODO: If this becomes a bottleneck, put the extern aliases in a dictionary, as for using aliases. - foreach (var alias in imports.ExternAliases) + for (; binder != null; binder = binder.Next) { - if (alias.Alias.Locations[0].SourceSpan == declarationSyntax.Identifier.Span) + var externAliases = binder.ExternAliases; + + if (!externAliases.IsDefault) { - return alias.Alias.GetPublicSymbol(); + foreach (var alias in externAliases) + { + if (alias.Alias.Locations[0].SourceSpan == declarationSyntax.Identifier.Span) + { + return alias.Alias.GetPublicSymbol(); + } + } + + break; } } - return new AliasSymbol(binder, declarationSyntax).GetPublicSymbol(); + return null; } /// @@ -2395,43 +2405,73 @@ internal override Func GetSyntaxNodesToAnalyzeFilter(SyntaxNod break; case RecordDeclarationSyntax recordDeclaration when TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor ctor: - switch (declaredSymbol.Kind) + if (recordDeclaration.IsKind(SyntaxKind.RecordDeclaration)) { - case SymbolKind.Method: - Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor); - return (node) => - { - // Accept only nodes that either match, or above/below of a 'parameter list'/'base arguments list'. - if (node.Parent == recordDeclaration) - { - return node == recordDeclaration.ParameterList || node == recordDeclaration.BaseList; - } - else if (node.Parent is BaseListSyntax baseList) - { - return node == recordDeclaration.PrimaryConstructorBaseType; - } - else if (node.Parent is PrimaryConstructorBaseTypeSyntax baseType && baseType == recordDeclaration.PrimaryConstructorBaseType) + switch (declaredSymbol.Kind) + { + case SymbolKind.Method: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor); + return (node) => { - return node == baseType.ArgumentList; - } - - return true; - }; - - case SymbolKind.NamedType: - Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor.ContainingSymbol); - // Accept nodes that do not match a 'parameter list'/'base arguments list'. - return (node) => node != recordDeclaration.ParameterList && - !(node.Kind() == SyntaxKind.ArgumentList && node == recordDeclaration.PrimaryConstructorBaseType?.ArgumentList); - - default: - ExceptionUtilities.UnexpectedValue(declaredSymbol.Kind); - break; + // Accept only nodes that either match, or above/below of a 'parameter list'/'base arguments list'. + if (node.Parent == recordDeclaration) + { + return node == recordDeclaration.ParameterList || node == recordDeclaration.BaseList; + } + else if (node.Parent is BaseListSyntax baseList) + { + return node == recordDeclaration.PrimaryConstructorBaseTypeIfClass; + } + else if (node.Parent is PrimaryConstructorBaseTypeSyntax baseType && baseType == recordDeclaration.PrimaryConstructorBaseTypeIfClass) + { + return node == baseType.ArgumentList; + } + + return true; + }; + + case SymbolKind.NamedType: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor.ContainingSymbol); + // Accept nodes that do not match a 'parameter list'/'base arguments list'. + return (node) => node != recordDeclaration.ParameterList && + !(node.Kind() == SyntaxKind.ArgumentList && node == recordDeclaration.PrimaryConstructorBaseTypeIfClass?.ArgumentList); + + default: + ExceptionUtilities.UnexpectedValue(declaredSymbol.Kind); + break; + } + } + else + { + switch (declaredSymbol.Kind) + { + case SymbolKind.Method: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor); + return (node) => + { + // Accept only nodes that either match, or above/below of a 'parameter list'. + if (node.Parent == recordDeclaration) + { + return node == recordDeclaration.ParameterList; + } + + return true; + }; + + case SymbolKind.NamedType: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor.ContainingSymbol); + // Accept nodes that do not match a 'parameter list'. + return (node) => node != recordDeclaration.ParameterList; + + default: + ExceptionUtilities.UnexpectedValue(declaredSymbol.Kind); + break; + } } break; case PrimaryConstructorBaseTypeSyntax { Parent: BaseListSyntax { Parent: RecordDeclarationSyntax recordDeclaration } } baseType - when recordDeclaration.PrimaryConstructorBaseType == declaredNode && TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor ctor: + when recordDeclaration.PrimaryConstructorBaseTypeIfClass == declaredNode && TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor ctor: if ((object)declaredSymbol.GetSymbol() == (object)ctor) { // Only 'base arguments list' or nodes below it diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index caa0530112e29..50568274f92c7 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1688,7 +1688,11 @@ private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationSta BoundBlock body; - if (method is SourceMemberMethodSymbol sourceMethod) + if (method is SynthesizedRecordConstructor recordStructPrimaryCtor && method.ContainingType.IsRecordStruct) + { + body = BoundBlock.SynthesizedNoLocals(recordStructPrimaryCtor.GetSyntax()); + } + else if (method is SourceMemberMethodSymbol sourceMethod) { CSharpSyntaxNode syntaxNode = sourceMethod.SyntaxNode; @@ -1910,10 +1914,12 @@ internal static BoundExpression BindImplicitConstructorInitializer( MethodSymbol constructor, BindingDiagnosticBag diagnostics, CSharpCompilation compilation) { // Note that the base type can be null if we're compiling System.Object in source. - NamedTypeSymbol baseType = constructor.ContainingType.BaseTypeNoUseSiteDiagnostics; + NamedTypeSymbol containingType = constructor.ContainingType; + NamedTypeSymbol baseType = containingType.BaseTypeNoUseSiteDiagnostics; SourceMemberMethodSymbol sourceConstructor = constructor as SourceMemberMethodSymbol; - Debug.Assert(sourceConstructor?.SyntaxNode is RecordDeclarationSyntax || ((ConstructorDeclarationSyntax)sourceConstructor?.SyntaxNode)?.Initializer == null); + Debug.Assert(sourceConstructor?.SyntaxNode is RecordDeclarationSyntax + || ((ConstructorDeclarationSyntax)sourceConstructor?.SyntaxNode)?.Initializer == null); // The common case is that the type inherits directly from object. // Also, we might be trying to generate a constructor for an entirely compiler-generated class such @@ -1935,7 +1941,11 @@ internal static BoundExpression BindImplicitConstructorInitializer( } } - if (constructor is SynthesizedRecordCopyCtor copyCtor) + if (containingType.IsStructType() || containingType.IsEnumType()) + { + return null; + } + else if (constructor is SynthesizedRecordCopyCtor copyCtor) { return GenerateBaseCopyConstructorInitializer(copyCtor, diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs index 62aef779449d5..67a98d7fceb9f 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs @@ -23,7 +23,8 @@ internal enum DeclarationKind : byte Submission, ImplicitClass, SimpleProgram, - Record + Record, + RecordStruct } internal static partial class EnumConversions @@ -39,6 +40,7 @@ internal static DeclarationKind ToDeclarationKind(this SyntaxKind kind) case SyntaxKind.EnumDeclaration: return DeclarationKind.Enum; case SyntaxKind.DelegateDeclaration: return DeclarationKind.Delegate; case SyntaxKind.RecordDeclaration: return DeclarationKind.Record; + case SyntaxKind.RecordStructDeclaration: return DeclarationKind.RecordStruct; default: throw ExceptionUtilities.UnexpectedValue(kind); } diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs index 753ece2541b95..7f4f224ad24b3 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.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; namespace Microsoft.CodeAnalysis.CSharp @@ -38,8 +36,6 @@ internal enum DeclarationModifiers : uint Async = 1 << 20, Ref = 1 << 21, // used only for structs - Data = 1 << 22, - All = (1 << 23) - 1, // all modifiers Unset = 1 << 23, // used when a modifiers value hasn't yet been computed diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs index 903b69b9125b9..a71e95949fec6 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs @@ -186,13 +186,7 @@ private RootSingleNamespaceDeclaration CreateScriptRootDeclaration(CompilationUn membernames, declFlags)); - return new RootSingleNamespaceDeclaration( - hasUsings: compilationUnit.Usings.Any(), - hasExternAliases: compilationUnit.Externs.Any(), - treeNode: _syntaxTree.GetReference(compilationUnit), - children: rootChildren.ToImmutableAndFree(), - referenceDirectives: GetReferenceDirectives(compilationUnit), - hasAssemblyAttributes: compilationUnit.AttributeLists.Any()); + return CreateRootSingleNamespaceDeclaration(compilationUnit, rootChildren.ToImmutableAndFree(), isForScript: true); } private static ImmutableArray GetReferenceDirectives(CompilationUnitSyntax compilationUnit) @@ -261,13 +255,43 @@ public override SingleNamespaceOrTypeDeclaration VisitCompilationUnit(Compilatio var children = VisitNamespaceChildren(compilationUnit, compilationUnit.Members, ((Syntax.InternalSyntax.CompilationUnitSyntax)(compilationUnit.Green)).Members); + return CreateRootSingleNamespaceDeclaration(compilationUnit, children, isForScript: false); + } + + private RootSingleNamespaceDeclaration CreateRootSingleNamespaceDeclaration(CompilationUnitSyntax compilationUnit, ImmutableArray children, bool isForScript) + { + bool hasUsings = false; + bool hasGlobalUsings = false; + bool reportedGlobalUsingOutOfOrder = false; + var diagnostics = DiagnosticBag.GetInstance(); + + foreach (var directive in compilationUnit.Usings) + { + if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword)) + { + hasGlobalUsings = true; + + if (hasUsings && !reportedGlobalUsingOutOfOrder) + { + reportedGlobalUsingOutOfOrder = true; + diagnostics.Add(ErrorCode.ERR_GlobalUsingOutOfOrder, directive.GlobalKeyword.GetLocation()); + } + } + else + { + hasUsings = true; + } + } + return new RootSingleNamespaceDeclaration( - hasUsings: compilationUnit.Usings.Any(), + hasGlobalUsings: hasGlobalUsings, + hasUsings: hasUsings, hasExternAliases: compilationUnit.Externs.Any(), treeNode: _syntaxTree.GetReference(compilationUnit), children: children, - referenceDirectives: ImmutableArray.Empty, - hasAssemblyAttributes: compilationUnit.AttributeLists.Any()); + referenceDirectives: isForScript ? GetReferenceDirectives(compilationUnit) : ImmutableArray.Empty, + hasAssemblyAttributes: compilationUnit.AttributeLists.Any(), + diagnostics: diagnostics.ToReadOnlyAndFree()); } public override SingleNamespaceOrTypeDeclaration VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) @@ -319,6 +343,15 @@ public override SingleNamespaceOrTypeDeclaration VisitNamespaceDeclaration(Names diagnostics.Add(ErrorCode.ERR_BadModifiersOnNamespace, node.Modifiers[0].GetLocation()); } + foreach (var directive in node.Usings) + { + if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword)) + { + diagnostics.Add(ErrorCode.ERR_GlobalUsingInNamespace, directive.GlobalKeyword.GetLocation()); + break; + } + } + // NOTE: *Something* has to happen for alias-qualified names. It turns out that we // just grab the part after the colons (via GetUnqualifiedName, below). This logic // must be kept in sync with NamespaceSymbol.GetNestedNamespace. @@ -380,7 +413,16 @@ public override SingleNamespaceOrTypeDeclaration VisitInterfaceDeclaration(Inter } public override SingleNamespaceOrTypeDeclaration VisitRecordDeclaration(RecordDeclarationSyntax node) - => VisitTypeDeclaration(node, DeclarationKind.Record); + { + var declarationKind = node.Kind() switch + { + SyntaxKind.RecordDeclaration => DeclarationKind.Record, + SyntaxKind.RecordStructDeclaration => DeclarationKind.RecordStruct, + _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()) + }; + + return VisitTypeDeclaration(node, declarationKind); + } private SingleNamespaceOrTypeDeclaration VisitTypeDeclaration(TypeDeclarationSyntax node, DeclarationKind kind) { @@ -627,6 +669,7 @@ private static bool CheckMemberForAttributes(Syntax.InternalSyntax.CSharpSyntaxN case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return (((Syntax.InternalSyntax.BaseTypeDeclarationSyntax)member).AttributeLists).Any(); case SyntaxKind.DelegateDeclaration: diff --git a/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs index 379aaab1157e3..9728ae14c5b1b 100644 --- a/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs @@ -66,6 +66,7 @@ public ImmutableArray> GetAttributeDeclarations( case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: attributesSyntaxList = ((TypeDeclarationSyntax)typeDecl).AttributeLists; break; diff --git a/src/Compilers/CSharp/Portable/Declarations/RootSingleNamespaceDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/RootSingleNamespaceDeclaration.cs index 59be1593879ad..52c07194c2625 100644 --- a/src/Compilers/CSharp/Portable/Declarations/RootSingleNamespaceDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/RootSingleNamespaceDeclaration.cs @@ -13,26 +13,30 @@ internal sealed class RootSingleNamespaceDeclaration : SingleNamespaceDeclaratio { private readonly ImmutableArray _referenceDirectives; private readonly bool _hasAssemblyAttributes; + private readonly bool _hasGlobalUsings; private readonly bool _hasUsings; private readonly bool _hasExternAliases; public RootSingleNamespaceDeclaration( + bool hasGlobalUsings, bool hasUsings, bool hasExternAliases, SyntaxReference treeNode, ImmutableArray children, ImmutableArray referenceDirectives, - bool hasAssemblyAttributes) + bool hasAssemblyAttributes, + ImmutableArray diagnostics) : base(string.Empty, treeNode, nameLocation: new SourceLocation(treeNode), children: children, - diagnostics: ImmutableArray.Empty) + diagnostics: diagnostics) { Debug.Assert(!referenceDirectives.IsDefault); _referenceDirectives = referenceDirectives; _hasAssemblyAttributes = hasAssemblyAttributes; + _hasGlobalUsings = hasGlobalUsings; _hasUsings = hasUsings; _hasExternAliases = hasExternAliases; } @@ -53,6 +57,14 @@ public bool HasAssemblyAttributes } } + public override bool HasGlobalUsings + { + get + { + return _hasGlobalUsings; + } + } + public override bool HasUsings { get diff --git a/src/Compilers/CSharp/Portable/Declarations/SingleNamespaceDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/SingleNamespaceDeclaration.cs index 222f9611ae831..5383a8f758259 100644 --- a/src/Compilers/CSharp/Portable/Declarations/SingleNamespaceDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/SingleNamespaceDeclaration.cs @@ -36,6 +36,14 @@ protected override ImmutableArray GetNamespace return _children; } + public virtual bool HasGlobalUsings + { + get + { + return false; + } + } + public virtual bool HasUsings { get diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs index f687ff4e4f4cb..80b467c126c93 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs @@ -10,6 +10,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Emit @@ -24,7 +25,6 @@ internal static EmitDifferenceResult EmitDifference( Stream metadataStream, Stream ilStream, Stream pdbStream, - ICollection updatedMethods, CompilationTestData? testData, CancellationToken cancellationToken) { @@ -35,6 +35,9 @@ internal static EmitDifferenceResult EmitDifference( var serializationProperties = compilation.ConstructModuleSerializationProperties(emitOptions, runtimeMDVersion, baseline.ModuleVersionId); var manifestResources = SpecializedCollections.EmptyEnumerable(); + var updatedMethods = ArrayBuilder.GetInstance(); + var updatedTypes = ArrayBuilder.GetInstance(); + PEDeltaAssemblyBuilder moduleBeingBuilt; try { @@ -52,7 +55,7 @@ internal static EmitDifferenceResult EmitDifference( { // TODO: https://github.com/dotnet/roslyn/issues/9004 diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, NoLocation.Singleton, compilation.AssemblyName, e.Message); - return new EmitDifferenceResult(success: false, diagnostics: diagnostics.ToReadOnlyAndFree(), baseline: null); + return new EmitDifferenceResult(success: false, diagnostics: diagnostics.ToReadOnlyAndFree(), baseline: null, updatedMethods: updatedMethods.ToImmutableAndFree(), updatedTypes: updatedTypes.ToImmutableAndFree()); } if (testData != null) @@ -87,6 +90,7 @@ internal static EmitDifferenceResult EmitDifference( ilStream, pdbStream, updatedMethods, + updatedTypes, diagnostics, testData?.SymWriterFactory, emitOptions.PdbFilePath, @@ -96,7 +100,9 @@ internal static EmitDifferenceResult EmitDifference( return new EmitDifferenceResult( success: newBaseline != null, diagnostics: diagnostics.ToReadOnlyAndFree(), - baseline: newBaseline); + baseline: newBaseline, + updatedMethods: updatedMethods.ToImmutableAndFree(), + updatedTypes: updatedTypes.ToImmutableAndFree()); } /// diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/ArrayTypeSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/ArrayTypeSymbolAdapter.cs index 010206b08d1d4..673b7175782be 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/ArrayTypeSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/ArrayTypeSymbolAdapter.cs @@ -24,7 +24,7 @@ Cci.ITypeReference Cci.IArrayTypeReference.GetElementType(EmitContext context) PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; TypeWithAnnotations elementType = AdaptedArrayTypeSymbol.ElementTypeWithAnnotations; - var type = moduleBeingBuilt.Translate(elementType.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + var type = moduleBeingBuilt.Translate(elementType.Type, syntaxNodeOpt: (CSharpSyntaxNode?)context.SyntaxNode, diagnostics: context.Diagnostics); if (elementType.CustomModifiers.Length == 0) { diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs index bd862b24fbfed..75726e3486208 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs @@ -43,14 +43,14 @@ Cci.IMethodReference Cci.ICustomAttribute.Constructor(EmitContext context, bool if (reportDiagnostics) { - context.Diagnostics.Add(ErrorCode.ERR_NotAnAttributeClass, context.SyntaxNodeOpt?.Location ?? NoLocation.Singleton, this.AttributeClass); + context.Diagnostics.Add(ErrorCode.ERR_NotAnAttributeClass, context.SyntaxNode?.Location ?? NoLocation.Singleton, this.AttributeClass); } return null; } PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return (Cci.IMethodReference)moduleBeingBuilt.Translate(this.AttributeConstructor, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return (Cci.IMethodReference)moduleBeingBuilt.Translate(this.AttributeConstructor, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics); } ImmutableArray Cci.ICustomAttribute.GetNamedArguments(EmitContext context) @@ -88,7 +88,7 @@ ushort Cci.ICustomAttribute.NamedArgumentCount Cci.ITypeReference Cci.ICustomAttribute.GetType(EmitContext context) { PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return moduleBeingBuilt.Translate(this.AttributeClass, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + return moduleBeingBuilt.Translate(this.AttributeClass, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } bool Cci.ICustomAttribute.AllowMultiple @@ -144,7 +144,7 @@ private static MetadataTypeOf CreateType(TypedConstant argument, EmitContext con { Debug.Assert(argument.ValueInternal != null); var moduleBeingBuilt = (PEModuleBuilder)context.Module; - var syntaxNodeOpt = (CSharpSyntaxNode)context.SyntaxNodeOpt; + var syntaxNodeOpt = (CSharpSyntaxNode)context.SyntaxNode; var diagnostics = context.Diagnostics; return new MetadataTypeOf(moduleBeingBuilt.Translate((TypeSymbol)argument.ValueInternal, syntaxNodeOpt, diagnostics), moduleBeingBuilt.Translate((TypeSymbol)argument.TypeInternal, syntaxNodeOpt, diagnostics)); @@ -153,7 +153,7 @@ private static MetadataTypeOf CreateType(TypedConstant argument, EmitContext con private static MetadataConstant CreateMetadataConstant(ITypeSymbolInternal type, object value, EmitContext context) { PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return moduleBeingBuilt.CreateConstant((TypeSymbol)type, value, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + return moduleBeingBuilt.CreateConstant((TypeSymbol)type, value, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } private Cci.IMetadataNamedArgument CreateMetadataNamedArgument(string name, TypedConstant argument, EmitContext context) @@ -172,7 +172,7 @@ private Cci.IMetadataNamedArgument CreateMetadataNamedArgument(string name, Type } PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return new MetadataNamedArgument(symbol, moduleBeingBuilt.Translate(type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics), value); + return new MetadataNamedArgument(symbol, moduleBeingBuilt.Translate(type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics), value); } private Symbol LookupName(string name) diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/CustomModifierAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/CustomModifierAdapter.cs index c1187ac6c43e7..59381c0bd7fc5 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/CustomModifierAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/CustomModifierAdapter.cs @@ -19,7 +19,7 @@ bool Cci.ICustomModifier.IsOptional Cci.ITypeReference Cci.ICustomModifier.GetModifier(EmitContext context) { - return ((PEModuleBuilder)context.Module).Translate(this.ModifierSymbol, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return ((PEModuleBuilder)context.Module).Translate(this.ModifierSymbol, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics); } } } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/EventSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/EventSymbolAdapter.cs index 43440f41b607a..acea1728ccdac 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/EventSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/EventSymbolAdapter.cs @@ -91,7 +91,7 @@ bool Cci.IEventDefinition.IsSpecialName Cci.ITypeReference Cci.IEventDefinition.GetType(EmitContext context) { - return ((PEModuleBuilder)context.Module).Translate(AdaptedEventSymbol.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + return ((PEModuleBuilder)context.Module).Translate(AdaptedEventSymbol.Type, syntaxNodeOpt: (CSharpSyntaxNode?)context.SyntaxNode, diagnostics: context.Diagnostics); } #endregion diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs index ccc09ee1c7386..78c10edb9912a 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs @@ -34,7 +34,7 @@ Cci.ITypeReference Cci.IFieldReference.GetType(EmitContext context) var isFixed = AdaptedFieldSymbol.IsFixedSizeBuffer; var implType = isFixed ? AdaptedFieldSymbol.FixedImplementationType(moduleBeingBuilt) : fieldTypeWithAnnotations.Type; var type = moduleBeingBuilt.Translate(implType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); if (isFixed || customModifiers.Length == 0) @@ -87,7 +87,7 @@ Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext contex Debug.Assert(this.IsDefinitionOrDistinct()); return moduleBeingBuilt.Translate(AdaptedFieldSymbol.ContainingType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: AdaptedFieldSymbol.IsDefinition); } @@ -152,7 +152,7 @@ internal MetadataConstant GetMetadataConstantValue(EmitContext context) // (and we specifically don't want to prevent metadata-only emit because of a bad // constant). If the constant value is bad, we'll end up exposing null to CCI. return ((PEModuleBuilder)context.Module).CreateConstant(AdaptedFieldSymbol.Type, AdaptedFieldSymbol.ConstantValue, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/GenericMethodInstanceReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/GenericMethodInstanceReference.cs index 4f39f94d4bccc..306b67293e57d 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/GenericMethodInstanceReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/GenericMethodInstanceReference.cs @@ -34,7 +34,7 @@ public override void Dispatch(Cci.MetadataVisitor visitor) foreach (var arg in UnderlyingMethod.TypeArgumentsWithAnnotations) { Debug.Assert(arg.CustomModifiers.IsEmpty); - yield return moduleBeingBuilt.Translate(arg.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + yield return moduleBeingBuilt.Translate(arg.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } } @@ -43,7 +43,7 @@ Cci.IMethodReference Cci.IGenericMethodInstanceReference.GetGenericMethod(EmitCo // NoPia method might come through here. return ((PEModuleBuilder)context.Module).Translate( UnderlyingMethod.OriginalDefinition, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: true); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/GenericNestedTypeInstanceReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/GenericNestedTypeInstanceReference.cs index 7f801248aca6a..de9cf582fe2d7 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/GenericNestedTypeInstanceReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/GenericNestedTypeInstanceReference.cs @@ -22,7 +22,7 @@ public GenericNestedTypeInstanceReference(NamedTypeSymbol underlyingNamedType) Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext context) { - return ((PEModuleBuilder)context.Module).Translate(UnderlyingNamedType.ContainingType, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + return ((PEModuleBuilder)context.Module).Translate(UnderlyingNamedType.ContainingType, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } public override Cci.IGenericTypeInstanceReference AsGenericTypeInstanceReference diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/GenericTypeInstanceReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/GenericTypeInstanceReference.cs index 59b64e2d7dcac..6637d7d228b2b 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/GenericTypeInstanceReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/GenericTypeInstanceReference.cs @@ -41,7 +41,7 @@ public sealed override void Dispatch(Cci.MetadataVisitor visitor) var builder = ArrayBuilder.GetInstance(); foreach (TypeWithAnnotations type in UnderlyingNamedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics) { - builder.Add(moduleBeingBuilt.Translate(type.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics)); + builder.Add(moduleBeingBuilt.Translate(type.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics)); } return builder.ToImmutableAndFree(); @@ -51,7 +51,7 @@ Cci.INamedTypeReference Cci.IGenericTypeInstanceReference.GetGenericType(EmitCon { System.Diagnostics.Debug.Assert(UnderlyingNamedType.OriginalDefinition.IsDefinition); PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return moduleBeingBuilt.Translate(UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + return moduleBeingBuilt.Translate(UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: true); } } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/MethodReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/MethodReference.cs index 55bfcdf27bd8c..6cdc85d3fbb18 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/MethodReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/MethodReference.cs @@ -116,7 +116,7 @@ bool Cci.ISignature.ReturnValueIsByRef Cci.ITypeReference Cci.ISignature.GetType(EmitContext context) { - return ((PEModuleBuilder)context.Module).Translate(UnderlyingMethod.ReturnType, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + return ((PEModuleBuilder)context.Module).Translate(UnderlyingMethod.ReturnType, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } public virtual Cci.IGenericMethodInstanceReference AsGenericMethodInstanceReference diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs index 6adb96ca023ae..cdd9f1466be0b 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs @@ -82,7 +82,7 @@ Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext contex var moduleBeingBuilt = (PEModuleBuilder)context.Module; return moduleBeingBuilt.Translate(containingType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: AdaptedMethodSymbol.IsDefinition); } @@ -245,7 +245,7 @@ bool Cci.ISignature.ReturnValueIsByRef Cci.ITypeReference Cci.ISignature.GetType(EmitContext context) { return ((PEModuleBuilder)context.Module).Translate(AdaptedMethodSymbol.ReturnType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } @@ -259,7 +259,7 @@ Cci.ITypeReference Cci.ISignature.GetType(EmitContext context) { Debug.Assert(arg.CustomModifiers.IsEmpty); yield return moduleBeingBuilt.Translate(arg.Type, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } } @@ -275,7 +275,7 @@ Cci.IMethodReference Cci.IGenericMethodInstanceReference.GetGenericMethod(EmitCo // NoPia method might come through here. return ((PEModuleBuilder)context.Module).Translate( (MethodSymbol)AdaptedMethodSymbol.OriginalDefinition, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: true); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs index 03969d1d039e9..0a5c663b9569e 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs @@ -293,7 +293,7 @@ Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) } return ((object)baseType != null) ? moduleBeingBuilt.Translate(baseType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics) : null; } @@ -332,7 +332,7 @@ Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) foreach (var implemented in method.ExplicitInterfaceImplementations) { - yield return new Microsoft.Cci.MethodImplementation(adapter, moduleBeingBuilt.TranslateOverriddenMethodReference(implemented, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics)); + yield return new Microsoft.Cci.MethodImplementation(adapter, moduleBeingBuilt.TranslateOverriddenMethodReference(implemented, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics)); } } @@ -351,7 +351,7 @@ Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) // It also affects covariant returns - C# ignores the return type in // determining if one method overrides another, while the runtime considers // the return type part of the signature. - yield return new Microsoft.Cci.MethodImplementation(method.GetCciAdapter(), moduleBeingBuilt.TranslateOverriddenMethodReference(method.OverriddenMethod, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics)); + yield return new Microsoft.Cci.MethodImplementation(method.GetCciAdapter(), moduleBeingBuilt.TranslateOverriddenMethodReference(method.OverriddenMethod, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics)); } else if (method.MethodKind == MethodKind.Destructor && AdaptedNamedTypeSymbol.SpecialType != SpecialType.System_Object) { @@ -366,7 +366,7 @@ Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) MethodSymbol objectMethod = objectMember as MethodSymbol; if ((object)objectMethod != null && objectMethod.MethodKind == MethodKind.Destructor) { - yield return new Microsoft.Cci.MethodImplementation(method.GetCciAdapter(), moduleBeingBuilt.TranslateOverriddenMethodReference(objectMethod, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics)); + yield return new Microsoft.Cci.MethodImplementation(method.GetCciAdapter(), moduleBeingBuilt.TranslateOverriddenMethodReference(objectMethod, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics)); } } } @@ -390,7 +390,7 @@ Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) foreach (var implemented in method.ExplicitInterfaceImplementations) { - yield return new Microsoft.Cci.MethodImplementation(m, moduleBeingBuilt.TranslateOverriddenMethodReference(implemented, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics)); + yield return new Microsoft.Cci.MethodImplementation(m, moduleBeingBuilt.TranslateOverriddenMethodReference(implemented, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics)); } Debug.Assert(!method.RequiresExplicitOverride(out _)); @@ -468,7 +468,7 @@ private ushort GenericParameterCountImpl { var typeRef = moduleBeingBuilt.Translate( @interface, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, fromImplements: true); @@ -807,7 +807,7 @@ Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext contex Debug.Assert(this.IsDefinitionOrDistinct()); return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.ContainingType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: AdaptedNamedTypeSymbol.IsDefinition); } @@ -844,7 +844,7 @@ Cci.TypeMemberVisibility Cci.ITypeDefinitionMember.Visibility for (int i = 0; i < arguments.Length; i++) { - var arg = moduleBeingBuilt.Translate(arguments[i].Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + var arg = moduleBeingBuilt.Translate(arguments[i].Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); var modifiers = arguments[i].CustomModifiers; if (!modifiers.IsDefaultOrEmpty) { @@ -866,7 +866,7 @@ Cci.INamedTypeReference Cci.IGenericTypeInstanceReference.GetGenericType(EmitCon private Cci.INamedTypeReference GenericTypeImpl(EmitContext context) { PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.OriginalDefinition, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.OriginalDefinition, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: true); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/ParameterSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/ParameterSymbolAdapter.cs index f4837733937be..336a5e830119a 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/ParameterSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/ParameterSymbolAdapter.cs @@ -50,7 +50,7 @@ bool Cci.IParameterTypeInformation.IsByReference Cci.ITypeReference Cci.IParameterTypeInformation.GetType(EmitContext context) { return ((PEModuleBuilder)context.Module).Translate(AdaptedParameterSymbol.Type, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } @@ -93,7 +93,7 @@ internal MetadataConstant GetMetadataConstantValue(EmitContext context) } return ((PEModuleBuilder)context.Module).CreateConstant(type, constant.Value, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/ParameterTypeInformation.cs b/src/Compilers/CSharp/Portable/Emitter/Model/ParameterTypeInformation.cs index 36067125a2adc..c1ac9b16ab3ff 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/ParameterTypeInformation.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/ParameterTypeInformation.cs @@ -50,7 +50,7 @@ bool Cci.IParameterTypeInformation.IsByReference Cci.ITypeReference Cci.IParameterTypeInformation.GetType(EmitContext context) { - return ((PEModuleBuilder)context.Module).Translate(_underlyingParameter.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + return ((PEModuleBuilder)context.Module).Translate(_underlyingParameter.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } ushort Cci.IParameterListEntry.Index diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PointerTypeSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PointerTypeSymbolAdapter.cs index f39e035a99741..62d661c8d418e 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PointerTypeSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PointerTypeSymbolAdapter.cs @@ -22,7 +22,7 @@ internal partial class { Cci.ITypeReference Cci.IPointerTypeReference.GetTargetType(EmitContext context) { - var type = ((PEModuleBuilder)context.Module).Translate(AdaptedPointerTypeSymbol.PointedAtType, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + var type = ((PEModuleBuilder)context.Module).Translate(AdaptedPointerTypeSymbol.PointedAtType, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); if (AdaptedPointerTypeSymbol.PointedAtTypeWithAnnotations.CustomModifiers.Length == 0) { diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs index ab561e51eeb8a..e954cd866b4f4 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs @@ -204,7 +204,7 @@ ITypeReference ISignature.GetType(EmitContext context) { CheckDefinitionInvariantAllowEmbedded(); return ((PEModuleBuilder)context.Module).Translate(AdaptedPropertySymbol.Type, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs index 3abdcd940ee05..3952875053d5a 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs @@ -62,7 +62,7 @@ Cci.ITypeReference Cci.IFieldReference.GetType(EmitContext context) { TypeWithAnnotations oldType = _underlyingField.TypeWithAnnotations; var customModifiers = oldType.CustomModifiers; - var type = ((PEModuleBuilder)context.Module).Translate(oldType.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + var type = ((PEModuleBuilder)context.Module).Translate(oldType.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); if (customModifiers.Length == 0) { diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericMethodInstanceReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericMethodInstanceReference.cs index a3763e6425a06..500d9f03f5fdc 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericMethodInstanceReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericMethodInstanceReference.cs @@ -35,7 +35,7 @@ public SpecializedGenericMethodInstanceReference(MethodSymbol underlyingMethod) foreach (var arg in UnderlyingMethod.TypeArgumentsWithAnnotations) { Debug.Assert(arg.CustomModifiers.IsEmpty); - yield return moduleBeingBuilt.Translate(arg.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics); + yield return moduleBeingBuilt.Translate(arg.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); } } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericNestedTypeInstanceReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericNestedTypeInstanceReference.cs index f41f9f1165059..9ce90478d3aa4 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericNestedTypeInstanceReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedGenericNestedTypeInstanceReference.cs @@ -40,7 +40,7 @@ public sealed override void Dispatch(Cci.MetadataVisitor visitor) var builder = ArrayBuilder.GetInstance(); foreach (TypeWithAnnotations type in UnderlyingNamedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics) { - builder.Add(moduleBeingBuilt.Translate(type.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, diagnostics: context.Diagnostics)); + builder.Add(moduleBeingBuilt.Translate(type.Type, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics)); } return builder.ToImmutableAndFree(); @@ -50,7 +50,7 @@ Cci.INamedTypeReference Cci.IGenericTypeInstanceReference.GetGenericType(EmitCon { System.Diagnostics.Debug.Assert(UnderlyingNamedType.OriginalDefinition.IsDefinition); PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return moduleBeingBuilt.Translate(this.UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + return moduleBeingBuilt.Translate(this.UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics, needDeclaration: true); } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedNestedTypeReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedNestedTypeReference.cs index 9c3fc7a8e23f9..a8cb25ab27b8d 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedNestedTypeReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedNestedTypeReference.cs @@ -27,7 +27,7 @@ Cci.INestedTypeReference Cci.ISpecializedNestedTypeReference.GetUnspecializedVer { Debug.Assert(UnderlyingNamedType.OriginalDefinition.IsDefinition); var result = ((PEModuleBuilder)context.Module).Translate(this.UnderlyingNamedType.OriginalDefinition, - (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics, needDeclaration: true).AsNestedTypeReference; + (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics, needDeclaration: true).AsNestedTypeReference; Debug.Assert(result != null); return result; @@ -40,7 +40,7 @@ public override void Dispatch(Cci.MetadataVisitor visitor) Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext context) { - return ((PEModuleBuilder)context.Module).Translate(UnderlyingNamedType.ContainingType, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return ((PEModuleBuilder)context.Module).Translate(UnderlyingNamedType.ContainingType, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics); } public override Cci.IGenericTypeInstanceReference AsGenericTypeInstanceReference diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/TypeMemberReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/TypeMemberReference.cs index 086b76d47cb8e..4efb7dd6d012c 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/TypeMemberReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/TypeMemberReference.cs @@ -17,7 +17,7 @@ internal abstract class TypeMemberReference : Cci.ITypeMemberReference public virtual Cci.ITypeReference GetContainingType(EmitContext context) { PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module; - return moduleBeingBuilt.Translate(UnderlyingSymbol.ContainingType, (CSharpSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return moduleBeingBuilt.Translate(UnderlyingSymbol.ContainingType, (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics); } string Cci.INamedEntity.Name diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/TypeParameterSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/TypeParameterSymbolAdapter.cs index 3177d9835c01c..1cd67e481413d 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/TypeParameterSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/TypeParameterSymbolAdapter.cs @@ -238,7 +238,7 @@ Cci.ITypeReference Cci.IGenericTypeParameterReference.DefiningType { var typeRef = moduleBeingBuilt.GetSpecialType( SpecialType.System_ValueType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); var modifier = CSharpCustomModifier.CreateRequired( @@ -263,7 +263,7 @@ Cci.ITypeReference Cci.IGenericTypeParameterReference.DefiningType break; } var typeRef = moduleBeingBuilt.Translate(type.Type, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); yield return type.GetTypeRefWithAttributes( @@ -276,7 +276,7 @@ Cci.ITypeReference Cci.IGenericTypeParameterReference.DefiningType { // Add System.ValueType constraint to comply with Dev11 output var typeRef = moduleBeingBuilt.GetSpecialType(SpecialType.System_ValueType, - syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNodeOpt, + syntaxNodeOpt: (CSharpSyntaxNode)context.SyntaxNode, diagnostics: context.Diagnostics); yield return new Cci.TypeReferenceWithAttributes(typeRef); diff --git a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs index 49ab4910a87d4..f797574451c93 100644 --- a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs +++ b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs @@ -130,7 +130,7 @@ protected override IEnumerable GetPropertiesToEmit() { var typeRef = moduleBeingBuilt.Translate( @interface, - (CSharpSyntaxNode)context.SyntaxNodeOpt, + (CSharpSyntaxNode)context.SyntaxNode, context.Diagnostics); var type = TypeWithAnnotations.Create(@interface); diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index a0856446e23a4..63fdfd9bee009 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.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 - namespace Microsoft.CodeAnalysis.CSharp { internal enum ErrorCode @@ -1860,7 +1858,7 @@ internal enum ErrorCode ERR_ExplicitPropertyMismatchInitOnly = 8855, ERR_BadInitAccessor = 8856, ERR_InvalidWithReceiverType = 8857, - ERR_NoSingleCloneMethod = 8858, + ERR_CannotClone = 8858, ERR_CloneDisallowedInRecord = 8859, WRN_RecordNamedDisallowed = 8860, ERR_UnexpectedArgumentList = 8861, @@ -1934,7 +1932,12 @@ internal enum ErrorCode #region diagnostics introduced for C# 10.0 - ERR_InheritingFromRecordWithSealedToString = 8912 + ERR_InheritingFromRecordWithSealedToString = 8912, + ERR_HiddenPositionalMember = 8913, + ERR_GlobalUsingInNamespace = 8914, + ERR_GlobalUsingOutOfOrder = 8915, + ERR_AttributesRequireParenthesizedLambdaExpression = 8916, + ERR_CannotInferDelegateType = 8917, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index a999ea580e0de..6272644a9d2d3 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -216,7 +216,14 @@ internal enum MessageID IDS_FeatureVarianceSafetyForStaticInterfaceMembers = MessageBase + 12791, IDS_FeatureConstantInterpolatedStrings = MessageBase + 12792, IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction = MessageBase + 12793, - IDS_FeatureSealedToStringInRecord = MessageBase + 12794 + IDS_FeatureSealedToStringInRecord = MessageBase + 12794, + IDS_FeatureRecordStructs = MessageBase + 12795, + IDS_FeatureWithOnStructs = MessageBase + 12796, + IDS_FeaturePositionalFieldsInRecords = MessageBase + 12797, + IDS_FeatureGlobalUsing = MessageBase + 12798, + IDS_FeatureInferredDelegateType = MessageBase + 12799, + IDS_FeatureLambdaAttributes = MessageBase + 12800, + IDS_FeatureWithOnAnonymousTypes = MessageBase + 12801, } // Message IDs may refer to strings that need to be localized. @@ -324,9 +331,17 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) switch (feature) { // C# preview features. - case MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction: + case MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction: // semantic check case MessageID.IDS_FeatureSealedToStringInRecord: // semantic check + case MessageID.IDS_FeatureRecordStructs: + case MessageID.IDS_FeatureWithOnStructs: // semantic check + case MessageID.IDS_FeatureWithOnAnonymousTypes: // semantic check + case MessageID.IDS_FeaturePositionalFieldsInRecords: // semantic check + case MessageID.IDS_FeatureGlobalUsing: + case MessageID.IDS_FeatureInferredDelegateType: // semantic check + case MessageID.IDS_FeatureLambdaAttributes: // semantic check return LanguageVersion.Preview; + // C# 9.0 features. case MessageID.IDS_FeatureLambdaDiscardParameters: // semantic check case MessageID.IDS_FeatureFunctionPointers: diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 7fa38958442d1..aec39b98d0afa 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -2404,7 +2404,7 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param return null; } - private static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations) + internal static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations) { if ((parameterAnnotations & FlowAnalysisAnnotations.AllowNull) != 0) { @@ -6624,14 +6624,24 @@ private bool HasTopLevelNullabilityConversion(TypeWithAnnotations source, TypeWi } /// - /// Apply the conversion to the type of the operand and return the resulting type. (If the - /// operand does not have an explicit type, the operand expression is used for the type.) - /// If `checkConversion` is set, the incoming conversion is assumed to be from binding and will be - /// re-calculated, this time considering nullability. (Note that the conversion calculation considers - /// nested nullability only. The caller is responsible for checking the top-level nullability of - /// the type returned by this method.) `trackMembers` should be set if the nullability of any - /// members of the operand should be copied to the converted result when possible. + /// Apply the conversion to the type of the operand and return the resulting type. + /// If the operand does not have an explicit type, the operand expression is used. /// + /// + /// If , the incoming conversion is assumed to be from binding + /// and will be re-calculated, this time considering nullability. + /// Note that the conversion calculation considers nested nullability only. + /// The caller is responsible for checking the top-level nullability of + /// the type returned by this method. + /// + /// + /// If , the nullability of any members of the operand + /// will be copied to the converted result when possible. + /// + /// + /// If , indicates that the "non-safety" diagnostic + /// should be given for an invalid conversion. + /// private TypeWithState VisitConversion( BoundConversion? conversionOpt, BoundExpression conversionOperand, @@ -6695,6 +6705,7 @@ private TypeWithState VisitConversion( { NamedTypeSymbol { TypeKind: TypeKind.Delegate, DelegateInvokeMethod: { Parameters: { } parameters } signature } => (signature, parameters), FunctionPointerTypeSymbol { Signature: { Parameters: { } parameters } signature } => (signature, parameters), + { SpecialType: SpecialType.System_Delegate } => (null, ImmutableArray.Empty), ErrorTypeSymbol => (null, ImmutableArray.Empty), _ => throw ExceptionUtilities.UnexpectedValue(targetType) }; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs index 8dfa376a19920..9ac94bcc14106 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -208,7 +208,6 @@ protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement // visit switch header Visit(node.Expression); var expressionState = ResultType; - var initialState = PossiblyConditionalState.Create(this); DeclareLocals(node.InnerLocals); foreach (var section in node.SwitchSections) @@ -217,7 +216,7 @@ protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement DeclareLocals(section.Locals); } - var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref initialState); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState); foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) @@ -284,13 +283,11 @@ public PossiblyConditionalState Clone() } } - private PooledDictionary - LearnFromDecisionDag( + private PooledDictionary LearnFromDecisionDag( SyntaxNode node, BoundDecisionDag decisionDag, BoundExpression expression, - TypeWithState expressionType, - ref PossiblyConditionalState initialState) + TypeWithState expressionType) { // We reuse the slot at the beginning of a switch (or is-pattern expression), pretending that we are // not copying the input to evaluate the patterns. In this way we infer non-nullability of the original @@ -319,7 +316,7 @@ public PossiblyConditionalState Clone() tempMap.Add(rootTemp, (originalInputSlot, expressionType.Type)); var nodeStateMap = PooledDictionary.GetInstance(); - nodeStateMap.Add(decisionDag.RootNode, (state: initialState.Clone(), believedReachable: true)); + nodeStateMap.Add(decisionDag.RootNode, (state: PossiblyConditionalState.Create(this), believedReachable: true)); var labelStateMap = PooledDictionary.GetInstance(); @@ -735,8 +732,7 @@ private void VisitSwitchExpressionCore(BoundSwitchExpression node, bool inferTyp Visit(node.Expression); var expressionState = ResultType; - var state = PossiblyConditionalState.Create(this); - var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref state); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState); var endState = UnreachableState(); if (!node.ReportedNotExhaustive && node.DefaultLabel != null && @@ -846,8 +842,7 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node VisitPatternForRewriting(node.Pattern); Visit(node.Expression); var expressionState = ResultType; - var state = PossiblyConditionalState.Create(this); - var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref state); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState); var trueState = labelStateMap.TryGetValue(node.IsNegated ? node.WhenFalseLabel : node.WhenTrueLabel, out var s1) ? s1.state : UnreachableState(); var falseState = labelStateMap.TryGetValue(node.IsNegated ? node.WhenTrueLabel : node.WhenFalseLabel, out var s2) ? s2.state : UnreachableState(); labelStateMap.Free(); diff --git a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 index e40f3eaedb827..17406ac149b7d 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 +++ b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 @@ -10,7 +10,7 @@ extern_alias_directive ; using_directive - : 'using' ('static' | name_equals)? name ';' + : 'global'? 'using' ('static' | name_equals)? name ';' ; name_equals @@ -98,7 +98,6 @@ modifier : 'abstract' | 'async' | 'const' - | 'data' | 'extern' | 'fixed' | 'internal' @@ -309,7 +308,7 @@ interface_declaration ; record_declaration - : attribute_list* modifier* syntax_token identifier_token type_parameter_list? parameter_list? base_list? type_parameter_constraint_clause* '{'? member_declaration* '}'? ';'? + : attribute_list* modifier* syntax_token ('class' | 'struct')? identifier_token type_parameter_list? parameter_list? base_list? type_parameter_constraint_clause* '{'? member_declaration* '}'? ';'? ; struct_declaration @@ -744,11 +743,11 @@ lambda_expression ; parenthesized_lambda_expression - : modifier* parameter_list '=>' (block | expression) + : attribute_list* modifier* parameter_list '=>' (block | expression) ; simple_lambda_expression - : modifier* parameter '=>' (block | expression) + : attribute_list* modifier* parameter '=>' (block | expression) ; anonymous_object_creation_expression diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs index 7a4108972ee84..6bced1a20ae46 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs @@ -6247,6 +6247,8 @@ protected LambdaExpressionSyntax(ObjectReader reader) { } + public abstract Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList AttributeLists { get; } + /// SyntaxToken representing equals greater than. public abstract SyntaxToken ArrowToken { get; } } @@ -6254,16 +6256,22 @@ protected LambdaExpressionSyntax(ObjectReader reader) /// Class which represents the syntax node for a simple lambda expression. internal sealed partial class SimpleLambdaExpressionSyntax : LambdaExpressionSyntax { + internal readonly GreenNode? attributeLists; internal readonly GreenNode? modifiers; internal readonly ParameterSyntax parameter; internal readonly SyntaxToken arrowToken; internal readonly BlockSyntax? block; internal readonly ExpressionSyntax? expressionBody; - internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) : base(kind, diagnostics, annotations) { - this.SlotCount = 5; + this.SlotCount = 6; + if (attributeLists != null) + { + this.AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } if (modifiers != null) { this.AdjustFlagsAndWidth(modifiers); @@ -6285,11 +6293,16 @@ internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, Par } } - internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, SyntaxFactoryContext context) + internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, SyntaxFactoryContext context) : base(kind) { this.SetFactoryContext(context); - this.SlotCount = 5; + this.SlotCount = 6; + if (attributeLists != null) + { + this.AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } if (modifiers != null) { this.AdjustFlagsAndWidth(modifiers); @@ -6311,10 +6324,15 @@ internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, Par } } - internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) : base(kind) { - this.SlotCount = 5; + this.SlotCount = 6; + if (attributeLists != null) + { + this.AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } if (modifiers != null) { this.AdjustFlagsAndWidth(modifiers); @@ -6336,6 +6354,7 @@ internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, Par } } + public override Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList AttributeLists => new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(this.attributeLists); public override Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList Modifiers => new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(this.modifiers); /// ParameterSyntax node representing the parameter of the lambda expression. public ParameterSyntax Parameter => this.parameter; @@ -6355,11 +6374,12 @@ internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, Par internal override GreenNode? GetSlot(int index) => index switch { - 0 => this.modifiers, - 1 => this.parameter, - 2 => this.arrowToken, - 3 => this.block, - 4 => this.expressionBody, + 0 => this.attributeLists, + 1 => this.modifiers, + 2 => this.parameter, + 3 => this.arrowToken, + 4 => this.block, + 5 => this.expressionBody, _ => null, }; @@ -6368,11 +6388,11 @@ internal SimpleLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, Par public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitSimpleLambdaExpression(this); public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitSimpleLambdaExpression(this); - public SimpleLambdaExpressionSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax block, ExpressionSyntax expressionBody) + public SimpleLambdaExpressionSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax block, ExpressionSyntax expressionBody) { - if (modifiers != this.Modifiers || parameter != this.Parameter || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || parameter != this.Parameter || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) { - var newNode = SyntaxFactory.SimpleLambdaExpression(modifiers, parameter, arrowToken, block, expressionBody); + var newNode = SyntaxFactory.SimpleLambdaExpression(attributeLists, modifiers, parameter, arrowToken, block, expressionBody); var diags = GetDiagnostics(); if (diags?.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -6386,15 +6406,21 @@ public SimpleLambdaExpressionSyntax Update(Microsoft.CodeAnalysis.Syntax.Interna } internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) - => new SimpleLambdaExpressionSyntax(this.Kind, this.modifiers, this.parameter, this.arrowToken, this.block, this.expressionBody, diagnostics, GetAnnotations()); + => new SimpleLambdaExpressionSyntax(this.Kind, this.attributeLists, this.modifiers, this.parameter, this.arrowToken, this.block, this.expressionBody, diagnostics, GetAnnotations()); internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) - => new SimpleLambdaExpressionSyntax(this.Kind, this.modifiers, this.parameter, this.arrowToken, this.block, this.expressionBody, GetDiagnostics(), annotations); + => new SimpleLambdaExpressionSyntax(this.Kind, this.attributeLists, this.modifiers, this.parameter, this.arrowToken, this.block, this.expressionBody, GetDiagnostics(), annotations); internal SimpleLambdaExpressionSyntax(ObjectReader reader) : base(reader) { - this.SlotCount = 5; + this.SlotCount = 6; + var attributeLists = (GreenNode?)reader.ReadValue(); + if (attributeLists != null) + { + AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } var modifiers = (GreenNode?)reader.ReadValue(); if (modifiers != null) { @@ -6424,6 +6450,7 @@ internal SimpleLambdaExpressionSyntax(ObjectReader reader) internal override void WriteTo(ObjectWriter writer) { base.WriteTo(writer); + writer.WriteValue(this.attributeLists); writer.WriteValue(this.modifiers); writer.WriteValue(this.parameter); writer.WriteValue(this.arrowToken); @@ -6540,16 +6567,22 @@ static RefExpressionSyntax() /// Class which represents the syntax node for parenthesized lambda expression. internal sealed partial class ParenthesizedLambdaExpressionSyntax : LambdaExpressionSyntax { + internal readonly GreenNode? attributeLists; internal readonly GreenNode? modifiers; internal readonly ParameterListSyntax parameterList; internal readonly SyntaxToken arrowToken; internal readonly BlockSyntax? block; internal readonly ExpressionSyntax? expressionBody; - internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) : base(kind, diagnostics, annotations) { - this.SlotCount = 5; + this.SlotCount = 6; + if (attributeLists != null) + { + this.AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } if (modifiers != null) { this.AdjustFlagsAndWidth(modifiers); @@ -6571,11 +6604,16 @@ internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifie } } - internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, SyntaxFactoryContext context) + internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody, SyntaxFactoryContext context) : base(kind) { this.SetFactoryContext(context); - this.SlotCount = 5; + this.SlotCount = 6; + if (attributeLists != null) + { + this.AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } if (modifiers != null) { this.AdjustFlagsAndWidth(modifiers); @@ -6597,10 +6635,15 @@ internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifie } } - internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) : base(kind) { - this.SlotCount = 5; + this.SlotCount = 6; + if (attributeLists != null) + { + this.AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } if (modifiers != null) { this.AdjustFlagsAndWidth(modifiers); @@ -6622,6 +6665,7 @@ internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifie } } + public override Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList AttributeLists => new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(this.attributeLists); public override Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList Modifiers => new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(this.modifiers); /// ParameterListSyntax node representing the list of parameters for the lambda expression. public ParameterListSyntax ParameterList => this.parameterList; @@ -6641,11 +6685,12 @@ internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifie internal override GreenNode? GetSlot(int index) => index switch { - 0 => this.modifiers, - 1 => this.parameterList, - 2 => this.arrowToken, - 3 => this.block, - 4 => this.expressionBody, + 0 => this.attributeLists, + 1 => this.modifiers, + 2 => this.parameterList, + 3 => this.arrowToken, + 4 => this.block, + 5 => this.expressionBody, _ => null, }; @@ -6654,11 +6699,11 @@ internal ParenthesizedLambdaExpressionSyntax(SyntaxKind kind, GreenNode? modifie public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitParenthesizedLambdaExpression(this); public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitParenthesizedLambdaExpression(this); - public ParenthesizedLambdaExpressionSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax block, ExpressionSyntax expressionBody) + public ParenthesizedLambdaExpressionSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax block, ExpressionSyntax expressionBody) { - if (modifiers != this.Modifiers || parameterList != this.ParameterList || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || parameterList != this.ParameterList || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) { - var newNode = SyntaxFactory.ParenthesizedLambdaExpression(modifiers, parameterList, arrowToken, block, expressionBody); + var newNode = SyntaxFactory.ParenthesizedLambdaExpression(attributeLists, modifiers, parameterList, arrowToken, block, expressionBody); var diags = GetDiagnostics(); if (diags?.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -6672,15 +6717,21 @@ public ParenthesizedLambdaExpressionSyntax Update(Microsoft.CodeAnalysis.Syntax. } internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) - => new ParenthesizedLambdaExpressionSyntax(this.Kind, this.modifiers, this.parameterList, this.arrowToken, this.block, this.expressionBody, diagnostics, GetAnnotations()); + => new ParenthesizedLambdaExpressionSyntax(this.Kind, this.attributeLists, this.modifiers, this.parameterList, this.arrowToken, this.block, this.expressionBody, diagnostics, GetAnnotations()); internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) - => new ParenthesizedLambdaExpressionSyntax(this.Kind, this.modifiers, this.parameterList, this.arrowToken, this.block, this.expressionBody, GetDiagnostics(), annotations); + => new ParenthesizedLambdaExpressionSyntax(this.Kind, this.attributeLists, this.modifiers, this.parameterList, this.arrowToken, this.block, this.expressionBody, GetDiagnostics(), annotations); internal ParenthesizedLambdaExpressionSyntax(ObjectReader reader) : base(reader) { - this.SlotCount = 5; + this.SlotCount = 6; + var attributeLists = (GreenNode?)reader.ReadValue(); + if (attributeLists != null) + { + AdjustFlagsAndWidth(attributeLists); + this.attributeLists = attributeLists; + } var modifiers = (GreenNode?)reader.ReadValue(); if (modifiers != null) { @@ -6710,6 +6761,7 @@ internal ParenthesizedLambdaExpressionSyntax(ObjectReader reader) internal override void WriteTo(ObjectWriter writer) { base.WriteTo(writer); + writer.WriteValue(this.attributeLists); writer.WriteValue(this.modifiers); writer.WriteValue(this.parameterList); writer.WriteValue(this.arrowToken); @@ -19095,16 +19147,22 @@ static ExternAliasDirectiveSyntax() internal sealed partial class UsingDirectiveSyntax : CSharpSyntaxNode { + internal readonly SyntaxToken? globalKeyword; internal readonly SyntaxToken usingKeyword; internal readonly SyntaxToken? staticKeyword; internal readonly NameEqualsSyntax? alias; internal readonly NameSyntax name; internal readonly SyntaxToken semicolonToken; - internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken? globalKeyword, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) : base(kind, diagnostics, annotations) { - this.SlotCount = 5; + this.SlotCount = 6; + if (globalKeyword != null) + { + this.AdjustFlagsAndWidth(globalKeyword); + this.globalKeyword = globalKeyword; + } this.AdjustFlagsAndWidth(usingKeyword); this.usingKeyword = usingKeyword; if (staticKeyword != null) @@ -19123,11 +19181,16 @@ internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxT this.semicolonToken = semicolonToken; } - internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken, SyntaxFactoryContext context) + internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken? globalKeyword, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken, SyntaxFactoryContext context) : base(kind) { this.SetFactoryContext(context); - this.SlotCount = 5; + this.SlotCount = 6; + if (globalKeyword != null) + { + this.AdjustFlagsAndWidth(globalKeyword); + this.globalKeyword = globalKeyword; + } this.AdjustFlagsAndWidth(usingKeyword); this.usingKeyword = usingKeyword; if (staticKeyword != null) @@ -19146,10 +19209,15 @@ internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxT this.semicolonToken = semicolonToken; } - internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken? globalKeyword, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) : base(kind) { - this.SlotCount = 5; + this.SlotCount = 6; + if (globalKeyword != null) + { + this.AdjustFlagsAndWidth(globalKeyword); + this.globalKeyword = globalKeyword; + } this.AdjustFlagsAndWidth(usingKeyword); this.usingKeyword = usingKeyword; if (staticKeyword != null) @@ -19168,6 +19236,7 @@ internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxT this.semicolonToken = semicolonToken; } + public SyntaxToken? GlobalKeyword => this.globalKeyword; public SyntaxToken UsingKeyword => this.usingKeyword; public SyntaxToken? StaticKeyword => this.staticKeyword; public NameEqualsSyntax? Alias => this.alias; @@ -19177,11 +19246,12 @@ internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxT internal override GreenNode? GetSlot(int index) => index switch { - 0 => this.usingKeyword, - 1 => this.staticKeyword, - 2 => this.alias, - 3 => this.name, - 4 => this.semicolonToken, + 0 => this.globalKeyword, + 1 => this.usingKeyword, + 2 => this.staticKeyword, + 3 => this.alias, + 4 => this.name, + 5 => this.semicolonToken, _ => null, }; @@ -19190,11 +19260,11 @@ internal UsingDirectiveSyntax(SyntaxKind kind, SyntaxToken usingKeyword, SyntaxT public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitUsingDirective(this); public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitUsingDirective(this); - public UsingDirectiveSyntax Update(SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax alias, NameSyntax name, SyntaxToken semicolonToken) + public UsingDirectiveSyntax Update(SyntaxToken globalKeyword, SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax alias, NameSyntax name, SyntaxToken semicolonToken) { - if (usingKeyword != this.UsingKeyword || staticKeyword != this.StaticKeyword || alias != this.Alias || name != this.Name || semicolonToken != this.SemicolonToken) + if (globalKeyword != this.GlobalKeyword || usingKeyword != this.UsingKeyword || staticKeyword != this.StaticKeyword || alias != this.Alias || name != this.Name || semicolonToken != this.SemicolonToken) { - var newNode = SyntaxFactory.UsingDirective(usingKeyword, staticKeyword, alias, name, semicolonToken); + var newNode = SyntaxFactory.UsingDirective(globalKeyword, usingKeyword, staticKeyword, alias, name, semicolonToken); var diags = GetDiagnostics(); if (diags?.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -19208,15 +19278,21 @@ public UsingDirectiveSyntax Update(SyntaxToken usingKeyword, SyntaxToken staticK } internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) - => new UsingDirectiveSyntax(this.Kind, this.usingKeyword, this.staticKeyword, this.alias, this.name, this.semicolonToken, diagnostics, GetAnnotations()); + => new UsingDirectiveSyntax(this.Kind, this.globalKeyword, this.usingKeyword, this.staticKeyword, this.alias, this.name, this.semicolonToken, diagnostics, GetAnnotations()); internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) - => new UsingDirectiveSyntax(this.Kind, this.usingKeyword, this.staticKeyword, this.alias, this.name, this.semicolonToken, GetDiagnostics(), annotations); + => new UsingDirectiveSyntax(this.Kind, this.globalKeyword, this.usingKeyword, this.staticKeyword, this.alias, this.name, this.semicolonToken, GetDiagnostics(), annotations); internal UsingDirectiveSyntax(ObjectReader reader) : base(reader) { - this.SlotCount = 5; + this.SlotCount = 6; + var globalKeyword = (SyntaxToken?)reader.ReadValue(); + if (globalKeyword != null) + { + AdjustFlagsAndWidth(globalKeyword); + this.globalKeyword = globalKeyword; + } var usingKeyword = (SyntaxToken)reader.ReadValue(); AdjustFlagsAndWidth(usingKeyword); this.usingKeyword = usingKeyword; @@ -19243,6 +19319,7 @@ internal UsingDirectiveSyntax(ObjectReader reader) internal override void WriteTo(ObjectWriter writer) { base.WriteTo(writer); + writer.WriteValue(this.globalKeyword); writer.WriteValue(this.usingKeyword); writer.WriteValue(this.staticKeyword); writer.WriteValue(this.alias); @@ -21547,6 +21624,7 @@ internal sealed partial class RecordDeclarationSyntax : TypeDeclarationSyntax internal readonly GreenNode? attributeLists; internal readonly GreenNode? modifiers; internal readonly SyntaxToken keyword; + internal readonly SyntaxToken? classOrStructKeyword; internal readonly SyntaxToken identifier; internal readonly TypeParameterListSyntax? typeParameterList; internal readonly ParameterListSyntax? parameterList; @@ -21557,10 +21635,10 @@ internal sealed partial class RecordDeclarationSyntax : TypeDeclarationSyntax internal readonly SyntaxToken? closeBraceToken; internal readonly SyntaxToken? semicolonToken; - internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, GreenNode? constraintClauses, SyntaxToken? openBraceToken, GreenNode? members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, SyntaxToken keyword, SyntaxToken? classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, GreenNode? constraintClauses, SyntaxToken? openBraceToken, GreenNode? members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) : base(kind, diagnostics, annotations) { - this.SlotCount = 12; + this.SlotCount = 13; if (attributeLists != null) { this.AdjustFlagsAndWidth(attributeLists); @@ -21573,6 +21651,11 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre } this.AdjustFlagsAndWidth(keyword); this.keyword = keyword; + if (classOrStructKeyword != null) + { + this.AdjustFlagsAndWidth(classOrStructKeyword); + this.classOrStructKeyword = classOrStructKeyword; + } this.AdjustFlagsAndWidth(identifier); this.identifier = identifier; if (typeParameterList != null) @@ -21617,11 +21700,11 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre } } - internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, GreenNode? constraintClauses, SyntaxToken? openBraceToken, GreenNode? members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken, SyntaxFactoryContext context) + internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, SyntaxToken keyword, SyntaxToken? classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, GreenNode? constraintClauses, SyntaxToken? openBraceToken, GreenNode? members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken, SyntaxFactoryContext context) : base(kind) { this.SetFactoryContext(context); - this.SlotCount = 12; + this.SlotCount = 13; if (attributeLists != null) { this.AdjustFlagsAndWidth(attributeLists); @@ -21634,6 +21717,11 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre } this.AdjustFlagsAndWidth(keyword); this.keyword = keyword; + if (classOrStructKeyword != null) + { + this.AdjustFlagsAndWidth(classOrStructKeyword); + this.classOrStructKeyword = classOrStructKeyword; + } this.AdjustFlagsAndWidth(identifier); this.identifier = identifier; if (typeParameterList != null) @@ -21678,10 +21766,10 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre } } - internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, GreenNode? constraintClauses, SyntaxToken? openBraceToken, GreenNode? members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken) + internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, SyntaxToken keyword, SyntaxToken? classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, GreenNode? constraintClauses, SyntaxToken? openBraceToken, GreenNode? members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken) : base(kind) { - this.SlotCount = 12; + this.SlotCount = 13; if (attributeLists != null) { this.AdjustFlagsAndWidth(attributeLists); @@ -21694,6 +21782,11 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre } this.AdjustFlagsAndWidth(keyword); this.keyword = keyword; + if (classOrStructKeyword != null) + { + this.AdjustFlagsAndWidth(classOrStructKeyword); + this.classOrStructKeyword = classOrStructKeyword; + } this.AdjustFlagsAndWidth(identifier); this.identifier = identifier; if (typeParameterList != null) @@ -21741,6 +21834,7 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre public override Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList AttributeLists => new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(this.attributeLists); public override Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList Modifiers => new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(this.modifiers); public override SyntaxToken Keyword => this.keyword; + public SyntaxToken? ClassOrStructKeyword => this.classOrStructKeyword; public override SyntaxToken Identifier => this.identifier; public override TypeParameterListSyntax? TypeParameterList => this.typeParameterList; public ParameterListSyntax? ParameterList => this.parameterList; @@ -21757,15 +21851,16 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre 0 => this.attributeLists, 1 => this.modifiers, 2 => this.keyword, - 3 => this.identifier, - 4 => this.typeParameterList, - 5 => this.parameterList, - 6 => this.baseList, - 7 => this.constraintClauses, - 8 => this.openBraceToken, - 9 => this.members, - 10 => this.closeBraceToken, - 11 => this.semicolonToken, + 3 => this.classOrStructKeyword, + 4 => this.identifier, + 5 => this.typeParameterList, + 6 => this.parameterList, + 7 => this.baseList, + 8 => this.constraintClauses, + 9 => this.openBraceToken, + 10 => this.members, + 11 => this.closeBraceToken, + 12 => this.semicolonToken, _ => null, }; @@ -21774,11 +21869,11 @@ internal RecordDeclarationSyntax(SyntaxKind kind, GreenNode? attributeLists, Gre public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitRecordDeclaration(this); public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitRecordDeclaration(this); - public RecordDeclarationSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, BaseListSyntax baseList, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList constraintClauses, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) + public RecordDeclarationSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken keyword, SyntaxToken classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, BaseListSyntax baseList, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList constraintClauses, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) { - if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || keyword != this.Keyword || identifier != this.Identifier || typeParameterList != this.TypeParameterList || parameterList != this.ParameterList || baseList != this.BaseList || constraintClauses != this.ConstraintClauses || openBraceToken != this.OpenBraceToken || members != this.Members || closeBraceToken != this.CloseBraceToken || semicolonToken != this.SemicolonToken) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || keyword != this.Keyword || classOrStructKeyword != this.ClassOrStructKeyword || identifier != this.Identifier || typeParameterList != this.TypeParameterList || parameterList != this.ParameterList || baseList != this.BaseList || constraintClauses != this.ConstraintClauses || openBraceToken != this.OpenBraceToken || members != this.Members || closeBraceToken != this.CloseBraceToken || semicolonToken != this.SemicolonToken) { - var newNode = SyntaxFactory.RecordDeclaration(attributeLists, modifiers, keyword, identifier, typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); + var newNode = SyntaxFactory.RecordDeclaration(this.Kind, attributeLists, modifiers, keyword, classOrStructKeyword, identifier, typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); var diags = GetDiagnostics(); if (diags?.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -21792,15 +21887,15 @@ public RecordDeclarationSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSynt } internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) - => new RecordDeclarationSyntax(this.Kind, this.attributeLists, this.modifiers, this.keyword, this.identifier, this.typeParameterList, this.parameterList, this.baseList, this.constraintClauses, this.openBraceToken, this.members, this.closeBraceToken, this.semicolonToken, diagnostics, GetAnnotations()); + => new RecordDeclarationSyntax(this.Kind, this.attributeLists, this.modifiers, this.keyword, this.classOrStructKeyword, this.identifier, this.typeParameterList, this.parameterList, this.baseList, this.constraintClauses, this.openBraceToken, this.members, this.closeBraceToken, this.semicolonToken, diagnostics, GetAnnotations()); internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) - => new RecordDeclarationSyntax(this.Kind, this.attributeLists, this.modifiers, this.keyword, this.identifier, this.typeParameterList, this.parameterList, this.baseList, this.constraintClauses, this.openBraceToken, this.members, this.closeBraceToken, this.semicolonToken, GetDiagnostics(), annotations); + => new RecordDeclarationSyntax(this.Kind, this.attributeLists, this.modifiers, this.keyword, this.classOrStructKeyword, this.identifier, this.typeParameterList, this.parameterList, this.baseList, this.constraintClauses, this.openBraceToken, this.members, this.closeBraceToken, this.semicolonToken, GetDiagnostics(), annotations); internal RecordDeclarationSyntax(ObjectReader reader) : base(reader) { - this.SlotCount = 12; + this.SlotCount = 13; var attributeLists = (GreenNode?)reader.ReadValue(); if (attributeLists != null) { @@ -21816,6 +21911,12 @@ internal RecordDeclarationSyntax(ObjectReader reader) var keyword = (SyntaxToken)reader.ReadValue(); AdjustFlagsAndWidth(keyword); this.keyword = keyword; + var classOrStructKeyword = (SyntaxToken?)reader.ReadValue(); + if (classOrStructKeyword != null) + { + AdjustFlagsAndWidth(classOrStructKeyword); + this.classOrStructKeyword = classOrStructKeyword; + } var identifier = (SyntaxToken)reader.ReadValue(); AdjustFlagsAndWidth(identifier); this.identifier = identifier; @@ -21875,6 +21976,7 @@ internal override void WriteTo(ObjectWriter writer) writer.WriteValue(this.attributeLists); writer.WriteValue(this.modifiers); writer.WriteValue(this.keyword); + writer.WriteValue(this.classOrStructKeyword); writer.WriteValue(this.identifier); writer.WriteValue(this.typeParameterList); writer.WriteValue(this.parameterList); @@ -33583,13 +33685,13 @@ public override CSharpSyntaxNode VisitAnonymousMethodExpression(AnonymousMethodE => node.Update(VisitList(node.Modifiers), (SyntaxToken)Visit(node.DelegateKeyword), (ParameterListSyntax)Visit(node.ParameterList), (BlockSyntax)Visit(node.Block), (ExpressionSyntax)Visit(node.ExpressionBody)); public override CSharpSyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) - => node.Update(VisitList(node.Modifiers), (ParameterSyntax)Visit(node.Parameter), (SyntaxToken)Visit(node.ArrowToken), (BlockSyntax)Visit(node.Block), (ExpressionSyntax)Visit(node.ExpressionBody)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (ParameterSyntax)Visit(node.Parameter), (SyntaxToken)Visit(node.ArrowToken), (BlockSyntax)Visit(node.Block), (ExpressionSyntax)Visit(node.ExpressionBody)); public override CSharpSyntaxNode VisitRefExpression(RefExpressionSyntax node) => node.Update((SyntaxToken)Visit(node.RefKeyword), (ExpressionSyntax)Visit(node.Expression)); public override CSharpSyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) - => node.Update(VisitList(node.Modifiers), (ParameterListSyntax)Visit(node.ParameterList), (SyntaxToken)Visit(node.ArrowToken), (BlockSyntax)Visit(node.Block), (ExpressionSyntax)Visit(node.ExpressionBody)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (ParameterListSyntax)Visit(node.ParameterList), (SyntaxToken)Visit(node.ArrowToken), (BlockSyntax)Visit(node.Block), (ExpressionSyntax)Visit(node.ExpressionBody)); public override CSharpSyntaxNode VisitInitializerExpression(InitializerExpressionSyntax node) => node.Update((SyntaxToken)Visit(node.OpenBraceToken), VisitList(node.Expressions), (SyntaxToken)Visit(node.CloseBraceToken)); @@ -33859,7 +33961,7 @@ public override CSharpSyntaxNode VisitExternAliasDirective(ExternAliasDirectiveS => node.Update((SyntaxToken)Visit(node.ExternKeyword), (SyntaxToken)Visit(node.AliasKeyword), (SyntaxToken)Visit(node.Identifier), (SyntaxToken)Visit(node.SemicolonToken)); public override CSharpSyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) - => node.Update((SyntaxToken)Visit(node.UsingKeyword), (SyntaxToken)Visit(node.StaticKeyword), (NameEqualsSyntax)Visit(node.Alias), (NameSyntax)Visit(node.Name), (SyntaxToken)Visit(node.SemicolonToken)); + => node.Update((SyntaxToken)Visit(node.GlobalKeyword), (SyntaxToken)Visit(node.UsingKeyword), (SyntaxToken)Visit(node.StaticKeyword), (NameEqualsSyntax)Visit(node.Alias), (NameSyntax)Visit(node.Name), (SyntaxToken)Visit(node.SemicolonToken)); public override CSharpSyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (SyntaxToken)Visit(node.NamespaceKeyword), (NameSyntax)Visit(node.Name), (SyntaxToken)Visit(node.OpenBraceToken), VisitList(node.Externs), VisitList(node.Usings), VisitList(node.Members), (SyntaxToken)Visit(node.CloseBraceToken), (SyntaxToken)Visit(node.SemicolonToken)); @@ -33898,7 +34000,7 @@ public override CSharpSyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationS => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (SyntaxToken)Visit(node.Keyword), (SyntaxToken)Visit(node.Identifier), (TypeParameterListSyntax)Visit(node.TypeParameterList), (BaseListSyntax)Visit(node.BaseList), VisitList(node.ConstraintClauses), (SyntaxToken)Visit(node.OpenBraceToken), VisitList(node.Members), (SyntaxToken)Visit(node.CloseBraceToken), (SyntaxToken)Visit(node.SemicolonToken)); public override CSharpSyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) - => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (SyntaxToken)Visit(node.Keyword), (SyntaxToken)Visit(node.Identifier), (TypeParameterListSyntax)Visit(node.TypeParameterList), (ParameterListSyntax)Visit(node.ParameterList), (BaseListSyntax)Visit(node.BaseList), VisitList(node.ConstraintClauses), (SyntaxToken)Visit(node.OpenBraceToken), VisitList(node.Members), (SyntaxToken)Visit(node.CloseBraceToken), (SyntaxToken)Visit(node.SemicolonToken)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (SyntaxToken)Visit(node.Keyword), (SyntaxToken)Visit(node.ClassOrStructKeyword), (SyntaxToken)Visit(node.Identifier), (TypeParameterListSyntax)Visit(node.TypeParameterList), (ParameterListSyntax)Visit(node.ParameterList), (BaseListSyntax)Visit(node.BaseList), VisitList(node.ConstraintClauses), (SyntaxToken)Visit(node.OpenBraceToken), VisitList(node.Members), (SyntaxToken)Visit(node.CloseBraceToken), (SyntaxToken)Visit(node.SemicolonToken)); public override CSharpSyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node) => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (SyntaxToken)Visit(node.EnumKeyword), (SyntaxToken)Visit(node.Identifier), (BaseListSyntax)Visit(node.BaseList), (SyntaxToken)Visit(node.OpenBraceToken), VisitList(node.Members), (SyntaxToken)Visit(node.CloseBraceToken), (SyntaxToken)Visit(node.SemicolonToken)); @@ -35365,7 +35467,7 @@ public AnonymousMethodExpressionSyntax AnonymousMethodExpression(Microsoft.CodeA return new AnonymousMethodExpressionSyntax(SyntaxKind.AnonymousMethodExpression, modifiers.Node, delegateKeyword, parameterList, block, expressionBody, this.context); } - public SimpleLambdaExpressionSyntax SimpleLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public SimpleLambdaExpressionSyntax SimpleLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { #if DEBUG if (parameter == null) throw new ArgumentNullException(nameof(parameter)); @@ -35373,7 +35475,7 @@ public SimpleLambdaExpressionSyntax SimpleLambdaExpression(Microsoft.CodeAnalysi if (arrowToken.Kind != SyntaxKind.EqualsGreaterThanToken) throw new ArgumentException(nameof(arrowToken)); #endif - return new SimpleLambdaExpressionSyntax(SyntaxKind.SimpleLambdaExpression, modifiers.Node, parameter, arrowToken, block, expressionBody, this.context); + return new SimpleLambdaExpressionSyntax(SyntaxKind.SimpleLambdaExpression, attributeLists.Node, modifiers.Node, parameter, arrowToken, block, expressionBody, this.context); } public RefExpressionSyntax RefExpression(SyntaxToken refKeyword, ExpressionSyntax expression) @@ -35397,7 +35499,7 @@ public RefExpressionSyntax RefExpression(SyntaxToken refKeyword, ExpressionSynta return result; } - public ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { #if DEBUG if (parameterList == null) throw new ArgumentNullException(nameof(parameterList)); @@ -35405,7 +35507,7 @@ public ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(Microso if (arrowToken.Kind != SyntaxKind.EqualsGreaterThanToken) throw new ArgumentException(nameof(arrowToken)); #endif - return new ParenthesizedLambdaExpressionSyntax(SyntaxKind.ParenthesizedLambdaExpression, modifiers.Node, parameterList, arrowToken, block, expressionBody, this.context); + return new ParenthesizedLambdaExpressionSyntax(SyntaxKind.ParenthesizedLambdaExpression, attributeLists.Node, modifiers.Node, parameterList, arrowToken, block, expressionBody, this.context); } public InitializerExpressionSyntax InitializerExpression(SyntaxKind kind, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList expressions, SyntaxToken closeBraceToken) @@ -37183,9 +37285,18 @@ public ExternAliasDirectiveSyntax ExternAliasDirective(SyntaxToken externKeyword return new ExternAliasDirectiveSyntax(SyntaxKind.ExternAliasDirective, externKeyword, aliasKeyword, identifier, semicolonToken, this.context); } - public UsingDirectiveSyntax UsingDirective(SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + public UsingDirectiveSyntax UsingDirective(SyntaxToken? globalKeyword, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) { #if DEBUG + if (globalKeyword != null) + { + switch (globalKeyword.Kind) + { + case SyntaxKind.GlobalKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(globalKeyword)); + } + } if (usingKeyword == null) throw new ArgumentNullException(nameof(usingKeyword)); if (usingKeyword.Kind != SyntaxKind.UsingKeyword) throw new ArgumentException(nameof(usingKeyword)); if (name == null) throw new ArgumentNullException(nameof(name)); @@ -37193,7 +37304,7 @@ public UsingDirectiveSyntax UsingDirective(SyntaxToken usingKeyword, SyntaxToken if (semicolonToken.Kind != SyntaxKind.SemicolonToken) throw new ArgumentException(nameof(semicolonToken)); #endif - return new UsingDirectiveSyntax(SyntaxKind.UsingDirective, usingKeyword, staticKeyword, alias, name, semicolonToken, this.context); + return new UsingDirectiveSyntax(SyntaxKind.UsingDirective, globalKeyword, usingKeyword, staticKeyword, alias, name, semicolonToken, this.context); } public NamespaceDeclarationSyntax NamespaceDeclaration(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken namespaceKeyword, NameSyntax name, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList externs, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList usings, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken? semicolonToken) @@ -37461,10 +37572,26 @@ public InterfaceDeclarationSyntax InterfaceDeclaration(Microsoft.CodeAnalysis.Sy return new InterfaceDeclarationSyntax(SyntaxKind.InterfaceDeclaration, attributeLists.Node, modifiers.Node, keyword, identifier, typeParameterList, baseList, constraintClauses.Node, openBraceToken, members.Node, closeBraceToken, semicolonToken, this.context); } - public RecordDeclarationSyntax RecordDeclaration(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList constraintClauses, SyntaxToken? openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken) + public RecordDeclarationSyntax RecordDeclaration(SyntaxKind kind, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken keyword, SyntaxToken? classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList constraintClauses, SyntaxToken? openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken) { + switch (kind) + { + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: break; + default: throw new ArgumentException(nameof(kind)); + } #if DEBUG if (keyword == null) throw new ArgumentNullException(nameof(keyword)); + if (classOrStructKeyword != null) + { + switch (classOrStructKeyword.Kind) + { + case SyntaxKind.ClassKeyword: + case SyntaxKind.StructKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(classOrStructKeyword)); + } + } if (identifier == null) throw new ArgumentNullException(nameof(identifier)); if (identifier.Kind != SyntaxKind.IdentifierToken) throw new ArgumentException(nameof(identifier)); if (openBraceToken != null) @@ -37496,7 +37623,7 @@ public RecordDeclarationSyntax RecordDeclaration(Microsoft.CodeAnalysis.Syntax.I } #endif - return new RecordDeclarationSyntax(SyntaxKind.RecordDeclaration, attributeLists.Node, modifiers.Node, keyword, identifier, typeParameterList, parameterList, baseList, constraintClauses.Node, openBraceToken, members.Node, closeBraceToken, semicolonToken, this.context); + return new RecordDeclarationSyntax(kind, attributeLists.Node, modifiers.Node, keyword, classOrStructKeyword, identifier, typeParameterList, parameterList, baseList, constraintClauses.Node, openBraceToken, members.Node, closeBraceToken, semicolonToken, this.context); } public EnumDeclarationSyntax EnumDeclaration(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken enumKeyword, SyntaxToken identifier, BaseListSyntax? baseList, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList members, SyntaxToken closeBraceToken, SyntaxToken? semicolonToken) @@ -40234,7 +40361,7 @@ public static AnonymousMethodExpressionSyntax AnonymousMethodExpression(Microsof return new AnonymousMethodExpressionSyntax(SyntaxKind.AnonymousMethodExpression, modifiers.Node, delegateKeyword, parameterList, block, expressionBody); } - public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { #if DEBUG if (parameter == null) throw new ArgumentNullException(nameof(parameter)); @@ -40242,7 +40369,7 @@ public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(Microsoft.Code if (arrowToken.Kind != SyntaxKind.EqualsGreaterThanToken) throw new ArgumentException(nameof(arrowToken)); #endif - return new SimpleLambdaExpressionSyntax(SyntaxKind.SimpleLambdaExpression, modifiers.Node, parameter, arrowToken, block, expressionBody); + return new SimpleLambdaExpressionSyntax(SyntaxKind.SimpleLambdaExpression, attributeLists.Node, modifiers.Node, parameter, arrowToken, block, expressionBody); } public static RefExpressionSyntax RefExpression(SyntaxToken refKeyword, ExpressionSyntax expression) @@ -40266,7 +40393,7 @@ public static RefExpressionSyntax RefExpression(SyntaxToken refKeyword, Expressi return result; } - public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { #if DEBUG if (parameterList == null) throw new ArgumentNullException(nameof(parameterList)); @@ -40274,7 +40401,7 @@ public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression( if (arrowToken.Kind != SyntaxKind.EqualsGreaterThanToken) throw new ArgumentException(nameof(arrowToken)); #endif - return new ParenthesizedLambdaExpressionSyntax(SyntaxKind.ParenthesizedLambdaExpression, modifiers.Node, parameterList, arrowToken, block, expressionBody); + return new ParenthesizedLambdaExpressionSyntax(SyntaxKind.ParenthesizedLambdaExpression, attributeLists.Node, modifiers.Node, parameterList, arrowToken, block, expressionBody); } public static InitializerExpressionSyntax InitializerExpression(SyntaxKind kind, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList expressions, SyntaxToken closeBraceToken) @@ -42052,9 +42179,18 @@ public static ExternAliasDirectiveSyntax ExternAliasDirective(SyntaxToken extern return new ExternAliasDirectiveSyntax(SyntaxKind.ExternAliasDirective, externKeyword, aliasKeyword, identifier, semicolonToken); } - public static UsingDirectiveSyntax UsingDirective(SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + public static UsingDirectiveSyntax UsingDirective(SyntaxToken? globalKeyword, SyntaxToken usingKeyword, SyntaxToken? staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) { #if DEBUG + if (globalKeyword != null) + { + switch (globalKeyword.Kind) + { + case SyntaxKind.GlobalKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(globalKeyword)); + } + } if (usingKeyword == null) throw new ArgumentNullException(nameof(usingKeyword)); if (usingKeyword.Kind != SyntaxKind.UsingKeyword) throw new ArgumentException(nameof(usingKeyword)); if (name == null) throw new ArgumentNullException(nameof(name)); @@ -42062,7 +42198,7 @@ public static UsingDirectiveSyntax UsingDirective(SyntaxToken usingKeyword, Synt if (semicolonToken.Kind != SyntaxKind.SemicolonToken) throw new ArgumentException(nameof(semicolonToken)); #endif - return new UsingDirectiveSyntax(SyntaxKind.UsingDirective, usingKeyword, staticKeyword, alias, name, semicolonToken); + return new UsingDirectiveSyntax(SyntaxKind.UsingDirective, globalKeyword, usingKeyword, staticKeyword, alias, name, semicolonToken); } public static NamespaceDeclarationSyntax NamespaceDeclaration(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken namespaceKeyword, NameSyntax name, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList externs, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList usings, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken? semicolonToken) @@ -42330,10 +42466,26 @@ public static InterfaceDeclarationSyntax InterfaceDeclaration(Microsoft.CodeAnal return new InterfaceDeclarationSyntax(SyntaxKind.InterfaceDeclaration, attributeLists.Node, modifiers.Node, keyword, identifier, typeParameterList, baseList, constraintClauses.Node, openBraceToken, members.Node, closeBraceToken, semicolonToken); } - public static RecordDeclarationSyntax RecordDeclaration(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList constraintClauses, SyntaxToken? openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken) + public static RecordDeclarationSyntax RecordDeclaration(SyntaxKind kind, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken keyword, SyntaxToken? classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList constraintClauses, SyntaxToken? openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList members, SyntaxToken? closeBraceToken, SyntaxToken? semicolonToken) { + switch (kind) + { + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: break; + default: throw new ArgumentException(nameof(kind)); + } #if DEBUG if (keyword == null) throw new ArgumentNullException(nameof(keyword)); + if (classOrStructKeyword != null) + { + switch (classOrStructKeyword.Kind) + { + case SyntaxKind.ClassKeyword: + case SyntaxKind.StructKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(classOrStructKeyword)); + } + } if (identifier == null) throw new ArgumentNullException(nameof(identifier)); if (identifier.Kind != SyntaxKind.IdentifierToken) throw new ArgumentException(nameof(identifier)); if (openBraceToken != null) @@ -42365,7 +42517,7 @@ public static RecordDeclarationSyntax RecordDeclaration(Microsoft.CodeAnalysis.S } #endif - return new RecordDeclarationSyntax(SyntaxKind.RecordDeclaration, attributeLists.Node, modifiers.Node, keyword, identifier, typeParameterList, parameterList, baseList, constraintClauses.Node, openBraceToken, members.Node, closeBraceToken, semicolonToken); + return new RecordDeclarationSyntax(kind, attributeLists.Node, modifiers.Node, keyword, classOrStructKeyword, identifier, typeParameterList, parameterList, baseList, constraintClauses.Node, openBraceToken, members.Node, closeBraceToken, semicolonToken); } public static EnumDeclarationSyntax EnumDeclaration(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, SyntaxToken enumKeyword, SyntaxToken identifier, BaseListSyntax? baseList, SyntaxToken openBraceToken, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList members, SyntaxToken closeBraceToken, SyntaxToken? semicolonToken) diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs index 86a0e9c3dc084..66690ae17168a 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs @@ -1564,13 +1564,13 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor => node.Update(VisitList(node.Modifiers), VisitToken(node.DelegateKeyword), (ParameterListSyntax?)Visit(node.ParameterList), (BlockSyntax?)Visit(node.Block) ?? throw new ArgumentNullException("block"), (ExpressionSyntax?)Visit(node.ExpressionBody)); public override SyntaxNode? VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) - => node.Update(VisitList(node.Modifiers), (ParameterSyntax?)Visit(node.Parameter) ?? throw new ArgumentNullException("parameter"), VisitToken(node.ArrowToken), (BlockSyntax?)Visit(node.Block), (ExpressionSyntax?)Visit(node.ExpressionBody)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (ParameterSyntax?)Visit(node.Parameter) ?? throw new ArgumentNullException("parameter"), VisitToken(node.ArrowToken), (BlockSyntax?)Visit(node.Block), (ExpressionSyntax?)Visit(node.ExpressionBody)); public override SyntaxNode? VisitRefExpression(RefExpressionSyntax node) => node.Update(VisitToken(node.RefKeyword), (ExpressionSyntax?)Visit(node.Expression) ?? throw new ArgumentNullException("expression")); public override SyntaxNode? VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) - => node.Update(VisitList(node.Modifiers), (ParameterListSyntax?)Visit(node.ParameterList) ?? throw new ArgumentNullException("parameterList"), VisitToken(node.ArrowToken), (BlockSyntax?)Visit(node.Block), (ExpressionSyntax?)Visit(node.ExpressionBody)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (ParameterListSyntax?)Visit(node.ParameterList) ?? throw new ArgumentNullException("parameterList"), VisitToken(node.ArrowToken), (BlockSyntax?)Visit(node.Block), (ExpressionSyntax?)Visit(node.ExpressionBody)); public override SyntaxNode? VisitInitializerExpression(InitializerExpressionSyntax node) => node.Update(VisitToken(node.OpenBraceToken), VisitList(node.Expressions), VisitToken(node.CloseBraceToken)); @@ -1840,7 +1840,7 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor => node.Update(VisitToken(node.ExternKeyword), VisitToken(node.AliasKeyword), VisitToken(node.Identifier), VisitToken(node.SemicolonToken)); public override SyntaxNode? VisitUsingDirective(UsingDirectiveSyntax node) - => node.Update(VisitToken(node.UsingKeyword), VisitToken(node.StaticKeyword), (NameEqualsSyntax?)Visit(node.Alias), (NameSyntax?)Visit(node.Name) ?? throw new ArgumentNullException("name"), VisitToken(node.SemicolonToken)); + => node.Update(VisitToken(node.GlobalKeyword), VisitToken(node.UsingKeyword), VisitToken(node.StaticKeyword), (NameEqualsSyntax?)Visit(node.Alias), (NameSyntax?)Visit(node.Name) ?? throw new ArgumentNullException("name"), VisitToken(node.SemicolonToken)); public override SyntaxNode? VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), VisitToken(node.NamespaceKeyword), (NameSyntax?)Visit(node.Name) ?? throw new ArgumentNullException("name"), VisitToken(node.OpenBraceToken), VisitList(node.Externs), VisitList(node.Usings), VisitList(node.Members), VisitToken(node.CloseBraceToken), VisitToken(node.SemicolonToken)); @@ -1879,7 +1879,7 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), VisitToken(node.Keyword), VisitToken(node.Identifier), (TypeParameterListSyntax?)Visit(node.TypeParameterList), (BaseListSyntax?)Visit(node.BaseList), VisitList(node.ConstraintClauses), VisitToken(node.OpenBraceToken), VisitList(node.Members), VisitToken(node.CloseBraceToken), VisitToken(node.SemicolonToken)); public override SyntaxNode? VisitRecordDeclaration(RecordDeclarationSyntax node) - => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), VisitToken(node.Keyword), VisitToken(node.Identifier), (TypeParameterListSyntax?)Visit(node.TypeParameterList), (ParameterListSyntax?)Visit(node.ParameterList), (BaseListSyntax?)Visit(node.BaseList), VisitList(node.ConstraintClauses), VisitToken(node.OpenBraceToken), VisitList(node.Members), VisitToken(node.CloseBraceToken), VisitToken(node.SemicolonToken)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), VisitToken(node.Keyword), VisitToken(node.ClassOrStructKeyword), VisitToken(node.Identifier), (TypeParameterListSyntax?)Visit(node.TypeParameterList), (ParameterListSyntax?)Visit(node.ParameterList), (BaseListSyntax?)Visit(node.BaseList), VisitList(node.ConstraintClauses), VisitToken(node.OpenBraceToken), VisitList(node.Members), VisitToken(node.CloseBraceToken), VisitToken(node.SemicolonToken)); public override SyntaxNode? VisitEnumDeclaration(EnumDeclarationSyntax node) => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), VisitToken(node.EnumKeyword), VisitToken(node.Identifier), (BaseListSyntax?)Visit(node.BaseList), VisitToken(node.OpenBraceToken), VisitList(node.Members), VisitToken(node.CloseBraceToken), VisitToken(node.SemicolonToken)); @@ -3055,20 +3055,20 @@ public static AnonymousMethodExpressionSyntax AnonymousMethodExpression(SyntaxTo } /// Creates a new SimpleLambdaExpressionSyntax instance. - public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxTokenList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxList attributeLists, SyntaxTokenList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { if (parameter == null) throw new ArgumentNullException(nameof(parameter)); if (arrowToken.Kind() != SyntaxKind.EqualsGreaterThanToken) throw new ArgumentException(nameof(arrowToken)); - return (SimpleLambdaExpressionSyntax)Syntax.InternalSyntax.SyntaxFactory.SimpleLambdaExpression(modifiers.Node.ToGreenList(), (Syntax.InternalSyntax.ParameterSyntax)parameter.Green, (Syntax.InternalSyntax.SyntaxToken)arrowToken.Node!, block == null ? null : (Syntax.InternalSyntax.BlockSyntax)block.Green, expressionBody == null ? null : (Syntax.InternalSyntax.ExpressionSyntax)expressionBody.Green).CreateRed(); + return (SimpleLambdaExpressionSyntax)Syntax.InternalSyntax.SyntaxFactory.SimpleLambdaExpression(attributeLists.Node.ToGreenList(), modifiers.Node.ToGreenList(), (Syntax.InternalSyntax.ParameterSyntax)parameter.Green, (Syntax.InternalSyntax.SyntaxToken)arrowToken.Node!, block == null ? null : (Syntax.InternalSyntax.BlockSyntax)block.Green, expressionBody == null ? null : (Syntax.InternalSyntax.ExpressionSyntax)expressionBody.Green).CreateRed(); } /// Creates a new SimpleLambdaExpressionSyntax instance. - public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxTokenList modifiers, ParameterSyntax parameter, BlockSyntax? block, ExpressionSyntax? expressionBody) - => SyntaxFactory.SimpleLambdaExpression(modifiers, parameter, SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), block, expressionBody); + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxList attributeLists, SyntaxTokenList modifiers, ParameterSyntax parameter, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SyntaxFactory.SimpleLambdaExpression(attributeLists, modifiers, parameter, SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), block, expressionBody); /// Creates a new SimpleLambdaExpressionSyntax instance. public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(ParameterSyntax parameter) - => SyntaxFactory.SimpleLambdaExpression(default(SyntaxTokenList), parameter, SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default, default); + => SyntaxFactory.SimpleLambdaExpression(default, default(SyntaxTokenList), parameter, SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default, default); /// Creates a new RefExpressionSyntax instance. public static RefExpressionSyntax RefExpression(SyntaxToken refKeyword, ExpressionSyntax expression) @@ -3083,20 +3083,20 @@ public static RefExpressionSyntax RefExpression(ExpressionSyntax expression) => SyntaxFactory.RefExpression(SyntaxFactory.Token(SyntaxKind.RefKeyword), expression); /// Creates a new ParenthesizedLambdaExpressionSyntax instance. - public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(SyntaxTokenList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(SyntaxList attributeLists, SyntaxTokenList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { if (parameterList == null) throw new ArgumentNullException(nameof(parameterList)); if (arrowToken.Kind() != SyntaxKind.EqualsGreaterThanToken) throw new ArgumentException(nameof(arrowToken)); - return (ParenthesizedLambdaExpressionSyntax)Syntax.InternalSyntax.SyntaxFactory.ParenthesizedLambdaExpression(modifiers.Node.ToGreenList(), (Syntax.InternalSyntax.ParameterListSyntax)parameterList.Green, (Syntax.InternalSyntax.SyntaxToken)arrowToken.Node!, block == null ? null : (Syntax.InternalSyntax.BlockSyntax)block.Green, expressionBody == null ? null : (Syntax.InternalSyntax.ExpressionSyntax)expressionBody.Green).CreateRed(); + return (ParenthesizedLambdaExpressionSyntax)Syntax.InternalSyntax.SyntaxFactory.ParenthesizedLambdaExpression(attributeLists.Node.ToGreenList(), modifiers.Node.ToGreenList(), (Syntax.InternalSyntax.ParameterListSyntax)parameterList.Green, (Syntax.InternalSyntax.SyntaxToken)arrowToken.Node!, block == null ? null : (Syntax.InternalSyntax.BlockSyntax)block.Green, expressionBody == null ? null : (Syntax.InternalSyntax.ExpressionSyntax)expressionBody.Green).CreateRed(); } /// Creates a new ParenthesizedLambdaExpressionSyntax instance. - public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(SyntaxTokenList modifiers, ParameterListSyntax parameterList, BlockSyntax? block, ExpressionSyntax? expressionBody) - => SyntaxFactory.ParenthesizedLambdaExpression(modifiers, parameterList, SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), block, expressionBody); + public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(SyntaxList attributeLists, SyntaxTokenList modifiers, ParameterListSyntax parameterList, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SyntaxFactory.ParenthesizedLambdaExpression(attributeLists, modifiers, parameterList, SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), block, expressionBody); /// Creates a new ParenthesizedLambdaExpressionSyntax instance. public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression() - => SyntaxFactory.ParenthesizedLambdaExpression(default(SyntaxTokenList), SyntaxFactory.ParameterList(), SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default, default); + => SyntaxFactory.ParenthesizedLambdaExpression(default, default(SyntaxTokenList), SyntaxFactory.ParameterList(), SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default, default); /// Creates a new InitializerExpressionSyntax instance. public static InitializerExpressionSyntax InitializerExpression(SyntaxKind kind, SyntaxToken openBraceToken, SeparatedSyntaxList expressions, SyntaxToken closeBraceToken) @@ -4376,6 +4376,7 @@ public static CasePatternSwitchLabelSyntax CasePatternSwitchLabel(SyntaxToken ke { if (keyword.Kind() != SyntaxKind.CaseKeyword) throw new ArgumentException(nameof(keyword)); if (pattern == null) throw new ArgumentNullException(nameof(pattern)); + if (colonToken.Kind() != SyntaxKind.ColonToken) throw new ArgumentException(nameof(colonToken)); return (CasePatternSwitchLabelSyntax)Syntax.InternalSyntax.SyntaxFactory.CasePatternSwitchLabel((Syntax.InternalSyntax.SyntaxToken)keyword.Node!, (Syntax.InternalSyntax.PatternSyntax)pattern.Green, whenClause == null ? null : (Syntax.InternalSyntax.WhenClauseSyntax)whenClause.Green, (Syntax.InternalSyntax.SyntaxToken)colonToken.Node!).CreateRed(); } @@ -4392,6 +4393,7 @@ public static CaseSwitchLabelSyntax CaseSwitchLabel(SyntaxToken keyword, Express { if (keyword.Kind() != SyntaxKind.CaseKeyword) throw new ArgumentException(nameof(keyword)); if (value == null) throw new ArgumentNullException(nameof(value)); + if (colonToken.Kind() != SyntaxKind.ColonToken) throw new ArgumentException(nameof(colonToken)); return (CaseSwitchLabelSyntax)Syntax.InternalSyntax.SyntaxFactory.CaseSwitchLabel((Syntax.InternalSyntax.SyntaxToken)keyword.Node!, (Syntax.InternalSyntax.ExpressionSyntax)value.Green, (Syntax.InternalSyntax.SyntaxToken)colonToken.Node!).CreateRed(); } @@ -4403,6 +4405,7 @@ public static CaseSwitchLabelSyntax CaseSwitchLabel(ExpressionSyntax value, Synt public static DefaultSwitchLabelSyntax DefaultSwitchLabel(SyntaxToken keyword, SyntaxToken colonToken) { if (keyword.Kind() != SyntaxKind.DefaultKeyword) throw new ArgumentException(nameof(keyword)); + if (colonToken.Kind() != SyntaxKind.ColonToken) throw new ArgumentException(nameof(colonToken)); return (DefaultSwitchLabelSyntax)Syntax.InternalSyntax.SyntaxFactory.DefaultSwitchLabel((Syntax.InternalSyntax.SyntaxToken)keyword.Node!, (Syntax.InternalSyntax.SyntaxToken)colonToken.Node!).CreateRed(); } @@ -4562,21 +4565,27 @@ public static ExternAliasDirectiveSyntax ExternAliasDirective(string identifier) => SyntaxFactory.ExternAliasDirective(SyntaxFactory.Token(SyntaxKind.ExternKeyword), SyntaxFactory.Token(SyntaxKind.AliasKeyword), SyntaxFactory.Identifier(identifier), SyntaxFactory.Token(SyntaxKind.SemicolonToken)); /// Creates a new UsingDirectiveSyntax instance. - public static UsingDirectiveSyntax UsingDirective(SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + public static UsingDirectiveSyntax UsingDirective(SyntaxToken globalKeyword, SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) { + switch (globalKeyword.Kind()) + { + case SyntaxKind.GlobalKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(globalKeyword)); + } if (usingKeyword.Kind() != SyntaxKind.UsingKeyword) throw new ArgumentException(nameof(usingKeyword)); if (name == null) throw new ArgumentNullException(nameof(name)); if (semicolonToken.Kind() != SyntaxKind.SemicolonToken) throw new ArgumentException(nameof(semicolonToken)); - return (UsingDirectiveSyntax)Syntax.InternalSyntax.SyntaxFactory.UsingDirective((Syntax.InternalSyntax.SyntaxToken)usingKeyword.Node!, (Syntax.InternalSyntax.SyntaxToken?)staticKeyword.Node, alias == null ? null : (Syntax.InternalSyntax.NameEqualsSyntax)alias.Green, (Syntax.InternalSyntax.NameSyntax)name.Green, (Syntax.InternalSyntax.SyntaxToken)semicolonToken.Node!).CreateRed(); + return (UsingDirectiveSyntax)Syntax.InternalSyntax.SyntaxFactory.UsingDirective((Syntax.InternalSyntax.SyntaxToken?)globalKeyword.Node, (Syntax.InternalSyntax.SyntaxToken)usingKeyword.Node!, (Syntax.InternalSyntax.SyntaxToken?)staticKeyword.Node, alias == null ? null : (Syntax.InternalSyntax.NameEqualsSyntax)alias.Green, (Syntax.InternalSyntax.NameSyntax)name.Green, (Syntax.InternalSyntax.SyntaxToken)semicolonToken.Node!).CreateRed(); } /// Creates a new UsingDirectiveSyntax instance. public static UsingDirectiveSyntax UsingDirective(SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name) - => SyntaxFactory.UsingDirective(SyntaxFactory.Token(SyntaxKind.UsingKeyword), staticKeyword, alias, name, SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + => SyntaxFactory.UsingDirective(default, SyntaxFactory.Token(SyntaxKind.UsingKeyword), staticKeyword, alias, name, SyntaxFactory.Token(SyntaxKind.SemicolonToken)); /// Creates a new UsingDirectiveSyntax instance. public static UsingDirectiveSyntax UsingDirective(NameSyntax name) - => SyntaxFactory.UsingDirective(SyntaxFactory.Token(SyntaxKind.UsingKeyword), default, default, name, SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + => SyntaxFactory.UsingDirective(default, SyntaxFactory.Token(SyntaxKind.UsingKeyword), default, default, name, SyntaxFactory.Token(SyntaxKind.SemicolonToken)); /// Creates a new NamespaceDeclarationSyntax instance. public static NamespaceDeclarationSyntax NamespaceDeclaration(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken namespaceKeyword, NameSyntax name, SyntaxToken openBraceToken, SyntaxList externs, SyntaxList usings, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) @@ -4798,8 +4807,21 @@ public static InterfaceDeclarationSyntax InterfaceDeclaration(string identifier) => SyntaxFactory.InterfaceDeclaration(default, default(SyntaxTokenList), SyntaxFactory.Token(SyntaxKind.InterfaceKeyword), SyntaxFactory.Identifier(identifier), default, default, default, SyntaxFactory.Token(SyntaxKind.OpenBraceToken), default, SyntaxFactory.Token(SyntaxKind.CloseBraceToken), default); /// Creates a new RecordDeclarationSyntax instance. - public static RecordDeclarationSyntax RecordDeclaration(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, SyntaxToken openBraceToken, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) + public static RecordDeclarationSyntax RecordDeclaration(SyntaxKind kind, SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, SyntaxToken openBraceToken, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) { + switch (kind) + { + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: break; + default: throw new ArgumentException(nameof(kind)); + } + switch (classOrStructKeyword.Kind()) + { + case SyntaxKind.ClassKeyword: + case SyntaxKind.StructKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(classOrStructKeyword)); + } if (identifier.Kind() != SyntaxKind.IdentifierToken) throw new ArgumentException(nameof(identifier)); switch (openBraceToken.Kind()) { @@ -4819,20 +4841,28 @@ public static RecordDeclarationSyntax RecordDeclaration(SyntaxList(), modifiers.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken)keyword.Node!, (Syntax.InternalSyntax.SyntaxToken)identifier.Node!, typeParameterList == null ? null : (Syntax.InternalSyntax.TypeParameterListSyntax)typeParameterList.Green, parameterList == null ? null : (Syntax.InternalSyntax.ParameterListSyntax)parameterList.Green, baseList == null ? null : (Syntax.InternalSyntax.BaseListSyntax)baseList.Green, constraintClauses.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken?)openBraceToken.Node, members.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken?)closeBraceToken.Node, (Syntax.InternalSyntax.SyntaxToken?)semicolonToken.Node).CreateRed(); + return (RecordDeclarationSyntax)Syntax.InternalSyntax.SyntaxFactory.RecordDeclaration(kind, attributeLists.Node.ToGreenList(), modifiers.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken)keyword.Node!, (Syntax.InternalSyntax.SyntaxToken?)classOrStructKeyword.Node, (Syntax.InternalSyntax.SyntaxToken)identifier.Node!, typeParameterList == null ? null : (Syntax.InternalSyntax.TypeParameterListSyntax)typeParameterList.Green, parameterList == null ? null : (Syntax.InternalSyntax.ParameterListSyntax)parameterList.Green, baseList == null ? null : (Syntax.InternalSyntax.BaseListSyntax)baseList.Green, constraintClauses.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken?)openBraceToken.Node, members.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken?)closeBraceToken.Node, (Syntax.InternalSyntax.SyntaxToken?)semicolonToken.Node).CreateRed(); } /// Creates a new RecordDeclarationSyntax instance. - public static RecordDeclarationSyntax RecordDeclaration(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, SyntaxList members) - => SyntaxFactory.RecordDeclaration(attributeLists, modifiers, keyword, identifier, typeParameterList, parameterList, baseList, constraintClauses, default, members, default, default); + public static RecordDeclarationSyntax RecordDeclaration(SyntaxKind kind, SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, SyntaxList members) + => SyntaxFactory.RecordDeclaration(kind, attributeLists, modifiers, keyword, default, identifier, typeParameterList, parameterList, baseList, constraintClauses, default, members, default, default); /// Creates a new RecordDeclarationSyntax instance. - public static RecordDeclarationSyntax RecordDeclaration(SyntaxToken keyword, SyntaxToken identifier) - => SyntaxFactory.RecordDeclaration(default, default(SyntaxTokenList), keyword, identifier, default, default, default, default, default, default, default, default); + public static RecordDeclarationSyntax RecordDeclaration(SyntaxKind kind, SyntaxToken keyword, SyntaxToken identifier) + => SyntaxFactory.RecordDeclaration(kind, default, default(SyntaxTokenList), keyword, default, identifier, default, default, default, default, default, default, default, default); /// Creates a new RecordDeclarationSyntax instance. - public static RecordDeclarationSyntax RecordDeclaration(SyntaxToken keyword, string identifier) - => SyntaxFactory.RecordDeclaration(default, default(SyntaxTokenList), keyword, SyntaxFactory.Identifier(identifier), default, default, default, default, default, default, default, default); + public static RecordDeclarationSyntax RecordDeclaration(SyntaxKind kind, SyntaxToken keyword, string identifier) + => SyntaxFactory.RecordDeclaration(kind, default, default(SyntaxTokenList), keyword, default, SyntaxFactory.Identifier(identifier), default, default, default, default, default, default, default, default); + + private static SyntaxKind GetRecordDeclarationClassOrStructKeywordKind(SyntaxKind kind) + => kind switch + { + SyntaxKind.RecordDeclaration => SyntaxKind.ClassKeyword, + SyntaxKind.RecordStructDeclaration => SyntaxKind.StructKeyword, + _ => throw new ArgumentOutOfRangeException(), + }; /// Creates a new EnumDeclarationSyntax instance. public static EnumDeclarationSyntax EnumDeclaration(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken enumKeyword, SyntaxToken identifier, BaseListSyntax? baseList, SyntaxToken openBraceToken, SeparatedSyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs index 04aa76e5e9761..e5528217336a2 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs @@ -3020,6 +3020,13 @@ internal LambdaExpressionSyntax(InternalSyntax.CSharpSyntaxNode green, SyntaxNod { } + public abstract SyntaxList AttributeLists { get; } + public LambdaExpressionSyntax WithAttributeLists(SyntaxList attributeLists) => WithAttributeListsCore(attributeLists); + internal abstract LambdaExpressionSyntax WithAttributeListsCore(SyntaxList attributeLists); + + public LambdaExpressionSyntax AddAttributeLists(params AttributeListSyntax[] items) => AddAttributeListsCore(items); + internal abstract LambdaExpressionSyntax AddAttributeListsCore(params AttributeListSyntax[] items); + /// SyntaxToken representing equals greater than. public abstract SyntaxToken ArrowToken { get; } public LambdaExpressionSyntax WithArrowToken(SyntaxToken arrowToken) => WithArrowTokenCore(arrowToken); @@ -3045,6 +3052,7 @@ internal LambdaExpressionSyntax(InternalSyntax.CSharpSyntaxNode green, SyntaxNod /// public sealed partial class SimpleLambdaExpressionSyntax : LambdaExpressionSyntax { + private SyntaxNode? attributeLists; private ParameterSyntax? parameter; private BlockSyntax? block; private ExpressionSyntax? expressionBody; @@ -3054,59 +3062,63 @@ internal SimpleLambdaExpressionSyntax(InternalSyntax.CSharpSyntaxNode green, Syn { } + public override SyntaxList AttributeLists => new SyntaxList(GetRed(ref this.attributeLists, 0)); + public override SyntaxTokenList Modifiers { get { - var slot = this.Green.GetSlot(0); - return slot != null ? new SyntaxTokenList(this, slot, Position, 0) : default; + var slot = this.Green.GetSlot(1); + return slot != null ? new SyntaxTokenList(this, slot, GetChildPosition(1), GetChildIndex(1)) : default; } } /// ParameterSyntax node representing the parameter of the lambda expression. - public ParameterSyntax Parameter => GetRed(ref this.parameter, 1)!; + public ParameterSyntax Parameter => GetRed(ref this.parameter, 2)!; /// SyntaxToken representing equals greater than. - public override SyntaxToken ArrowToken => new SyntaxToken(this, ((Syntax.InternalSyntax.SimpleLambdaExpressionSyntax)this.Green).arrowToken, GetChildPosition(2), GetChildIndex(2)); + public override SyntaxToken ArrowToken => new SyntaxToken(this, ((Syntax.InternalSyntax.SimpleLambdaExpressionSyntax)this.Green).arrowToken, GetChildPosition(3), GetChildIndex(3)); /// /// BlockSyntax node representing the body of the lambda. /// Only one of Block or ExpressionBody will be non-null. /// - public override BlockSyntax? Block => GetRed(ref this.block, 3); + public override BlockSyntax? Block => GetRed(ref this.block, 4); /// /// ExpressionSyntax node representing the body of the lambda. /// Only one of Block or ExpressionBody will be non-null. /// - public override ExpressionSyntax? ExpressionBody => GetRed(ref this.expressionBody, 4); + public override ExpressionSyntax? ExpressionBody => GetRed(ref this.expressionBody, 5); internal override SyntaxNode? GetNodeSlot(int index) => index switch { - 1 => GetRed(ref this.parameter, 1)!, - 3 => GetRed(ref this.block, 3), - 4 => GetRed(ref this.expressionBody, 4), + 0 => GetRedAtZero(ref this.attributeLists)!, + 2 => GetRed(ref this.parameter, 2)!, + 4 => GetRed(ref this.block, 4), + 5 => GetRed(ref this.expressionBody, 5), _ => null, }; internal override SyntaxNode? GetCachedSlot(int index) => index switch { - 1 => this.parameter, - 3 => this.block, - 4 => this.expressionBody, + 0 => this.attributeLists, + 2 => this.parameter, + 4 => this.block, + 5 => this.expressionBody, _ => null, }; public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitSimpleLambdaExpression(this); public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitSimpleLambdaExpression(this); - public SimpleLambdaExpressionSyntax Update(SyntaxTokenList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public SimpleLambdaExpressionSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { - if (modifiers != this.Modifiers || parameter != this.Parameter || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || parameter != this.Parameter || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) { - var newNode = SyntaxFactory.SimpleLambdaExpression(modifiers, parameter, arrowToken, block, expressionBody); + var newNode = SyntaxFactory.SimpleLambdaExpression(attributeLists, modifiers, parameter, arrowToken, block, expressionBody); var annotations = GetAnnotations(); return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; } @@ -3114,16 +3126,20 @@ public SimpleLambdaExpressionSyntax Update(SyntaxTokenList modifiers, ParameterS return this; } + internal override LambdaExpressionSyntax WithAttributeListsCore(SyntaxList attributeLists) => WithAttributeLists(attributeLists); + public new SimpleLambdaExpressionSyntax WithAttributeLists(SyntaxList attributeLists) => Update(attributeLists, this.Modifiers, this.Parameter, this.ArrowToken, this.Block, this.ExpressionBody); internal override AnonymousFunctionExpressionSyntax WithModifiersCore(SyntaxTokenList modifiers) => WithModifiers(modifiers); - public new SimpleLambdaExpressionSyntax WithModifiers(SyntaxTokenList modifiers) => Update(modifiers, this.Parameter, this.ArrowToken, this.Block, this.ExpressionBody); - public SimpleLambdaExpressionSyntax WithParameter(ParameterSyntax parameter) => Update(this.Modifiers, parameter, this.ArrowToken, this.Block, this.ExpressionBody); + public new SimpleLambdaExpressionSyntax WithModifiers(SyntaxTokenList modifiers) => Update(this.AttributeLists, modifiers, this.Parameter, this.ArrowToken, this.Block, this.ExpressionBody); + public SimpleLambdaExpressionSyntax WithParameter(ParameterSyntax parameter) => Update(this.AttributeLists, this.Modifiers, parameter, this.ArrowToken, this.Block, this.ExpressionBody); internal override LambdaExpressionSyntax WithArrowTokenCore(SyntaxToken arrowToken) => WithArrowToken(arrowToken); - public new SimpleLambdaExpressionSyntax WithArrowToken(SyntaxToken arrowToken) => Update(this.Modifiers, this.Parameter, arrowToken, this.Block, this.ExpressionBody); + public new SimpleLambdaExpressionSyntax WithArrowToken(SyntaxToken arrowToken) => Update(this.AttributeLists, this.Modifiers, this.Parameter, arrowToken, this.Block, this.ExpressionBody); internal override AnonymousFunctionExpressionSyntax WithBlockCore(BlockSyntax? block) => WithBlock(block); - public new SimpleLambdaExpressionSyntax WithBlock(BlockSyntax? block) => Update(this.Modifiers, this.Parameter, this.ArrowToken, block, this.ExpressionBody); + public new SimpleLambdaExpressionSyntax WithBlock(BlockSyntax? block) => Update(this.AttributeLists, this.Modifiers, this.Parameter, this.ArrowToken, block, this.ExpressionBody); internal override AnonymousFunctionExpressionSyntax WithExpressionBodyCore(ExpressionSyntax? expressionBody) => WithExpressionBody(expressionBody); - public new SimpleLambdaExpressionSyntax WithExpressionBody(ExpressionSyntax? expressionBody) => Update(this.Modifiers, this.Parameter, this.ArrowToken, this.Block, expressionBody); + public new SimpleLambdaExpressionSyntax WithExpressionBody(ExpressionSyntax? expressionBody) => Update(this.AttributeLists, this.Modifiers, this.Parameter, this.ArrowToken, this.Block, expressionBody); + internal override LambdaExpressionSyntax AddAttributeListsCore(params AttributeListSyntax[] items) => AddAttributeLists(items); + public new SimpleLambdaExpressionSyntax AddAttributeLists(params AttributeListSyntax[] items) => WithAttributeLists(this.AttributeLists.AddRange(items)); internal override AnonymousFunctionExpressionSyntax AddModifiersCore(params SyntaxToken[] items) => AddModifiers(items); public new SimpleLambdaExpressionSyntax AddModifiers(params SyntaxToken[] items) => WithModifiers(this.Modifiers.AddRange(items)); public SimpleLambdaExpressionSyntax AddParameterAttributeLists(params AttributeListSyntax[] items) => WithParameter(this.Parameter.WithAttributeLists(this.Parameter.AttributeLists.AddRange(items))); @@ -3193,6 +3209,7 @@ public RefExpressionSyntax Update(SyntaxToken refKeyword, ExpressionSyntax expre /// public sealed partial class ParenthesizedLambdaExpressionSyntax : LambdaExpressionSyntax { + private SyntaxNode? attributeLists; private ParameterListSyntax? parameterList; private BlockSyntax? block; private ExpressionSyntax? expressionBody; @@ -3202,59 +3219,63 @@ internal ParenthesizedLambdaExpressionSyntax(InternalSyntax.CSharpSyntaxNode gre { } + public override SyntaxList AttributeLists => new SyntaxList(GetRed(ref this.attributeLists, 0)); + public override SyntaxTokenList Modifiers { get { - var slot = this.Green.GetSlot(0); - return slot != null ? new SyntaxTokenList(this, slot, Position, 0) : default; + var slot = this.Green.GetSlot(1); + return slot != null ? new SyntaxTokenList(this, slot, GetChildPosition(1), GetChildIndex(1)) : default; } } /// ParameterListSyntax node representing the list of parameters for the lambda expression. - public ParameterListSyntax ParameterList => GetRed(ref this.parameterList, 1)!; + public ParameterListSyntax ParameterList => GetRed(ref this.parameterList, 2)!; /// SyntaxToken representing equals greater than. - public override SyntaxToken ArrowToken => new SyntaxToken(this, ((Syntax.InternalSyntax.ParenthesizedLambdaExpressionSyntax)this.Green).arrowToken, GetChildPosition(2), GetChildIndex(2)); + public override SyntaxToken ArrowToken => new SyntaxToken(this, ((Syntax.InternalSyntax.ParenthesizedLambdaExpressionSyntax)this.Green).arrowToken, GetChildPosition(3), GetChildIndex(3)); /// /// BlockSyntax node representing the body of the lambda. /// Only one of Block or ExpressionBody will be non-null. /// - public override BlockSyntax? Block => GetRed(ref this.block, 3); + public override BlockSyntax? Block => GetRed(ref this.block, 4); /// /// ExpressionSyntax node representing the body of the lambda. /// Only one of Block or ExpressionBody will be non-null. /// - public override ExpressionSyntax? ExpressionBody => GetRed(ref this.expressionBody, 4); + public override ExpressionSyntax? ExpressionBody => GetRed(ref this.expressionBody, 5); internal override SyntaxNode? GetNodeSlot(int index) => index switch { - 1 => GetRed(ref this.parameterList, 1)!, - 3 => GetRed(ref this.block, 3), - 4 => GetRed(ref this.expressionBody, 4), + 0 => GetRedAtZero(ref this.attributeLists)!, + 2 => GetRed(ref this.parameterList, 2)!, + 4 => GetRed(ref this.block, 4), + 5 => GetRed(ref this.expressionBody, 5), _ => null, }; internal override SyntaxNode? GetCachedSlot(int index) => index switch { - 1 => this.parameterList, - 3 => this.block, - 4 => this.expressionBody, + 0 => this.attributeLists, + 2 => this.parameterList, + 4 => this.block, + 5 => this.expressionBody, _ => null, }; public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitParenthesizedLambdaExpression(this); public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitParenthesizedLambdaExpression(this); - public ParenthesizedLambdaExpressionSyntax Update(SyntaxTokenList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + public ParenthesizedLambdaExpressionSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) { - if (modifiers != this.Modifiers || parameterList != this.ParameterList || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || parameterList != this.ParameterList || arrowToken != this.ArrowToken || block != this.Block || expressionBody != this.ExpressionBody) { - var newNode = SyntaxFactory.ParenthesizedLambdaExpression(modifiers, parameterList, arrowToken, block, expressionBody); + var newNode = SyntaxFactory.ParenthesizedLambdaExpression(attributeLists, modifiers, parameterList, arrowToken, block, expressionBody); var annotations = GetAnnotations(); return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; } @@ -3262,16 +3283,20 @@ public ParenthesizedLambdaExpressionSyntax Update(SyntaxTokenList modifiers, Par return this; } + internal override LambdaExpressionSyntax WithAttributeListsCore(SyntaxList attributeLists) => WithAttributeLists(attributeLists); + public new ParenthesizedLambdaExpressionSyntax WithAttributeLists(SyntaxList attributeLists) => Update(attributeLists, this.Modifiers, this.ParameterList, this.ArrowToken, this.Block, this.ExpressionBody); internal override AnonymousFunctionExpressionSyntax WithModifiersCore(SyntaxTokenList modifiers) => WithModifiers(modifiers); - public new ParenthesizedLambdaExpressionSyntax WithModifiers(SyntaxTokenList modifiers) => Update(modifiers, this.ParameterList, this.ArrowToken, this.Block, this.ExpressionBody); - public ParenthesizedLambdaExpressionSyntax WithParameterList(ParameterListSyntax parameterList) => Update(this.Modifiers, parameterList, this.ArrowToken, this.Block, this.ExpressionBody); + public new ParenthesizedLambdaExpressionSyntax WithModifiers(SyntaxTokenList modifiers) => Update(this.AttributeLists, modifiers, this.ParameterList, this.ArrowToken, this.Block, this.ExpressionBody); + public ParenthesizedLambdaExpressionSyntax WithParameterList(ParameterListSyntax parameterList) => Update(this.AttributeLists, this.Modifiers, parameterList, this.ArrowToken, this.Block, this.ExpressionBody); internal override LambdaExpressionSyntax WithArrowTokenCore(SyntaxToken arrowToken) => WithArrowToken(arrowToken); - public new ParenthesizedLambdaExpressionSyntax WithArrowToken(SyntaxToken arrowToken) => Update(this.Modifiers, this.ParameterList, arrowToken, this.Block, this.ExpressionBody); + public new ParenthesizedLambdaExpressionSyntax WithArrowToken(SyntaxToken arrowToken) => Update(this.AttributeLists, this.Modifiers, this.ParameterList, arrowToken, this.Block, this.ExpressionBody); internal override AnonymousFunctionExpressionSyntax WithBlockCore(BlockSyntax? block) => WithBlock(block); - public new ParenthesizedLambdaExpressionSyntax WithBlock(BlockSyntax? block) => Update(this.Modifiers, this.ParameterList, this.ArrowToken, block, this.ExpressionBody); + public new ParenthesizedLambdaExpressionSyntax WithBlock(BlockSyntax? block) => Update(this.AttributeLists, this.Modifiers, this.ParameterList, this.ArrowToken, block, this.ExpressionBody); internal override AnonymousFunctionExpressionSyntax WithExpressionBodyCore(ExpressionSyntax? expressionBody) => WithExpressionBody(expressionBody); - public new ParenthesizedLambdaExpressionSyntax WithExpressionBody(ExpressionSyntax? expressionBody) => Update(this.Modifiers, this.ParameterList, this.ArrowToken, this.Block, expressionBody); + public new ParenthesizedLambdaExpressionSyntax WithExpressionBody(ExpressionSyntax? expressionBody) => Update(this.AttributeLists, this.Modifiers, this.ParameterList, this.ArrowToken, this.Block, expressionBody); + internal override LambdaExpressionSyntax AddAttributeListsCore(params AttributeListSyntax[] items) => AddAttributeLists(items); + public new ParenthesizedLambdaExpressionSyntax AddAttributeLists(params AttributeListSyntax[] items) => WithAttributeLists(this.AttributeLists.AddRange(items)); internal override AnonymousFunctionExpressionSyntax AddModifiersCore(params SyntaxToken[] items) => AddModifiers(items); public new ParenthesizedLambdaExpressionSyntax AddModifiers(params SyntaxToken[] items) => WithModifiers(this.Modifiers.AddRange(items)); public ParenthesizedLambdaExpressionSyntax AddParameterListParameters(params ParameterSyntax[] items) => WithParameterList(this.ParameterList.WithParameters(this.ParameterList.Parameters.AddRange(items))); @@ -8946,47 +8971,56 @@ internal UsingDirectiveSyntax(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? { } - public SyntaxToken UsingKeyword => new SyntaxToken(this, ((Syntax.InternalSyntax.UsingDirectiveSyntax)this.Green).usingKeyword, Position, 0); + public SyntaxToken GlobalKeyword + { + get + { + var slot = ((Syntax.InternalSyntax.UsingDirectiveSyntax)this.Green).globalKeyword; + return slot != null ? new SyntaxToken(this, slot, Position, 0) : default; + } + } + + public SyntaxToken UsingKeyword => new SyntaxToken(this, ((Syntax.InternalSyntax.UsingDirectiveSyntax)this.Green).usingKeyword, GetChildPosition(1), GetChildIndex(1)); public SyntaxToken StaticKeyword { get { var slot = ((Syntax.InternalSyntax.UsingDirectiveSyntax)this.Green).staticKeyword; - return slot != null ? new SyntaxToken(this, slot, GetChildPosition(1), GetChildIndex(1)) : default; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(2), GetChildIndex(2)) : default; } } - public NameEqualsSyntax? Alias => GetRed(ref this.alias, 2); + public NameEqualsSyntax? Alias => GetRed(ref this.alias, 3); - public NameSyntax Name => GetRed(ref this.name, 3)!; + public NameSyntax Name => GetRed(ref this.name, 4)!; - public SyntaxToken SemicolonToken => new SyntaxToken(this, ((Syntax.InternalSyntax.UsingDirectiveSyntax)this.Green).semicolonToken, GetChildPosition(4), GetChildIndex(4)); + public SyntaxToken SemicolonToken => new SyntaxToken(this, ((Syntax.InternalSyntax.UsingDirectiveSyntax)this.Green).semicolonToken, GetChildPosition(5), GetChildIndex(5)); internal override SyntaxNode? GetNodeSlot(int index) => index switch { - 2 => GetRed(ref this.alias, 2), - 3 => GetRed(ref this.name, 3)!, + 3 => GetRed(ref this.alias, 3), + 4 => GetRed(ref this.name, 4)!, _ => null, }; internal override SyntaxNode? GetCachedSlot(int index) => index switch { - 2 => this.alias, - 3 => this.name, + 3 => this.alias, + 4 => this.name, _ => null, }; public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitUsingDirective(this); public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitUsingDirective(this); - public UsingDirectiveSyntax Update(SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + public UsingDirectiveSyntax Update(SyntaxToken globalKeyword, SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) { - if (usingKeyword != this.UsingKeyword || staticKeyword != this.StaticKeyword || alias != this.Alias || name != this.Name || semicolonToken != this.SemicolonToken) + if (globalKeyword != this.GlobalKeyword || usingKeyword != this.UsingKeyword || staticKeyword != this.StaticKeyword || alias != this.Alias || name != this.Name || semicolonToken != this.SemicolonToken) { - var newNode = SyntaxFactory.UsingDirective(usingKeyword, staticKeyword, alias, name, semicolonToken); + var newNode = SyntaxFactory.UsingDirective(globalKeyword, usingKeyword, staticKeyword, alias, name, semicolonToken); var annotations = GetAnnotations(); return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; } @@ -8994,11 +9028,12 @@ public UsingDirectiveSyntax Update(SyntaxToken usingKeyword, SyntaxToken staticK return this; } - public UsingDirectiveSyntax WithUsingKeyword(SyntaxToken usingKeyword) => Update(usingKeyword, this.StaticKeyword, this.Alias, this.Name, this.SemicolonToken); - public UsingDirectiveSyntax WithStaticKeyword(SyntaxToken staticKeyword) => Update(this.UsingKeyword, staticKeyword, this.Alias, this.Name, this.SemicolonToken); - public UsingDirectiveSyntax WithAlias(NameEqualsSyntax? alias) => Update(this.UsingKeyword, this.StaticKeyword, alias, this.Name, this.SemicolonToken); - public UsingDirectiveSyntax WithName(NameSyntax name) => Update(this.UsingKeyword, this.StaticKeyword, this.Alias, name, this.SemicolonToken); - public UsingDirectiveSyntax WithSemicolonToken(SyntaxToken semicolonToken) => Update(this.UsingKeyword, this.StaticKeyword, this.Alias, this.Name, semicolonToken); + public UsingDirectiveSyntax WithGlobalKeyword(SyntaxToken globalKeyword) => Update(globalKeyword, this.UsingKeyword, this.StaticKeyword, this.Alias, this.Name, this.SemicolonToken); + public UsingDirectiveSyntax WithUsingKeyword(SyntaxToken usingKeyword) => Update(this.GlobalKeyword, usingKeyword, this.StaticKeyword, this.Alias, this.Name, this.SemicolonToken); + public UsingDirectiveSyntax WithStaticKeyword(SyntaxToken staticKeyword) => Update(this.GlobalKeyword, this.UsingKeyword, staticKeyword, this.Alias, this.Name, this.SemicolonToken); + public UsingDirectiveSyntax WithAlias(NameEqualsSyntax? alias) => Update(this.GlobalKeyword, this.UsingKeyword, this.StaticKeyword, alias, this.Name, this.SemicolonToken); + public UsingDirectiveSyntax WithName(NameSyntax name) => Update(this.GlobalKeyword, this.UsingKeyword, this.StaticKeyword, this.Alias, name, this.SemicolonToken); + public UsingDirectiveSyntax WithSemicolonToken(SyntaxToken semicolonToken) => Update(this.GlobalKeyword, this.UsingKeyword, this.StaticKeyword, this.Alias, this.Name, semicolonToken); } /// Member declaration syntax. @@ -10111,6 +10146,7 @@ public InterfaceDeclarationSyntax Update(SyntaxList attribu /// This node is associated with the following syntax kinds: /// /// + /// /// /// public sealed partial class RecordDeclarationSyntax : TypeDeclarationSyntax @@ -10140,33 +10176,42 @@ public override SyntaxTokenList Modifiers public override SyntaxToken Keyword => new SyntaxToken(this, ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).keyword, GetChildPosition(2), GetChildIndex(2)); - public override SyntaxToken Identifier => new SyntaxToken(this, ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).identifier, GetChildPosition(3), GetChildIndex(3)); + public SyntaxToken ClassOrStructKeyword + { + get + { + var slot = ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).classOrStructKeyword; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(3), GetChildIndex(3)) : default; + } + } - public override TypeParameterListSyntax? TypeParameterList => GetRed(ref this.typeParameterList, 4); + public override SyntaxToken Identifier => new SyntaxToken(this, ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).identifier, GetChildPosition(4), GetChildIndex(4)); + + public override TypeParameterListSyntax? TypeParameterList => GetRed(ref this.typeParameterList, 5); - public ParameterListSyntax? ParameterList => GetRed(ref this.parameterList, 5); + public ParameterListSyntax? ParameterList => GetRed(ref this.parameterList, 6); - public override BaseListSyntax? BaseList => GetRed(ref this.baseList, 6); + public override BaseListSyntax? BaseList => GetRed(ref this.baseList, 7); - public override SyntaxList ConstraintClauses => new SyntaxList(GetRed(ref this.constraintClauses, 7)); + public override SyntaxList ConstraintClauses => new SyntaxList(GetRed(ref this.constraintClauses, 8)); public override SyntaxToken OpenBraceToken { get { var slot = ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).openBraceToken; - return slot != null ? new SyntaxToken(this, slot, GetChildPosition(8), GetChildIndex(8)) : default; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(9), GetChildIndex(9)) : default; } } - public override SyntaxList Members => new SyntaxList(GetRed(ref this.members, 9)); + public override SyntaxList Members => new SyntaxList(GetRed(ref this.members, 10)); public override SyntaxToken CloseBraceToken { get { var slot = ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).closeBraceToken; - return slot != null ? new SyntaxToken(this, slot, GetChildPosition(10), GetChildIndex(10)) : default; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(11), GetChildIndex(11)) : default; } } @@ -10175,7 +10220,7 @@ public override SyntaxToken SemicolonToken get { var slot = ((Syntax.InternalSyntax.RecordDeclarationSyntax)this.Green).semicolonToken; - return slot != null ? new SyntaxToken(this, slot, GetChildPosition(11), GetChildIndex(11)) : default; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(12), GetChildIndex(12)) : default; } } @@ -10183,11 +10228,11 @@ public override SyntaxToken SemicolonToken => index switch { 0 => GetRedAtZero(ref this.attributeLists)!, - 4 => GetRed(ref this.typeParameterList, 4), - 5 => GetRed(ref this.parameterList, 5), - 6 => GetRed(ref this.baseList, 6), - 7 => GetRed(ref this.constraintClauses, 7)!, - 9 => GetRed(ref this.members, 9)!, + 5 => GetRed(ref this.typeParameterList, 5), + 6 => GetRed(ref this.parameterList, 6), + 7 => GetRed(ref this.baseList, 7), + 8 => GetRed(ref this.constraintClauses, 8)!, + 10 => GetRed(ref this.members, 10)!, _ => null, }; @@ -10195,22 +10240,22 @@ public override SyntaxToken SemicolonToken => index switch { 0 => this.attributeLists, - 4 => this.typeParameterList, - 5 => this.parameterList, - 6 => this.baseList, - 7 => this.constraintClauses, - 9 => this.members, + 5 => this.typeParameterList, + 6 => this.parameterList, + 7 => this.baseList, + 8 => this.constraintClauses, + 10 => this.members, _ => null, }; public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitRecordDeclaration(this); public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitRecordDeclaration(this); - public RecordDeclarationSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, SyntaxToken openBraceToken, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) + public RecordDeclarationSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken classOrStructKeyword, SyntaxToken identifier, TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, SyntaxToken openBraceToken, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) { - if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || keyword != this.Keyword || identifier != this.Identifier || typeParameterList != this.TypeParameterList || parameterList != this.ParameterList || baseList != this.BaseList || constraintClauses != this.ConstraintClauses || openBraceToken != this.OpenBraceToken || members != this.Members || closeBraceToken != this.CloseBraceToken || semicolonToken != this.SemicolonToken) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || keyword != this.Keyword || classOrStructKeyword != this.ClassOrStructKeyword || identifier != this.Identifier || typeParameterList != this.TypeParameterList || parameterList != this.ParameterList || baseList != this.BaseList || constraintClauses != this.ConstraintClauses || openBraceToken != this.OpenBraceToken || members != this.Members || closeBraceToken != this.CloseBraceToken || semicolonToken != this.SemicolonToken) { - var newNode = SyntaxFactory.RecordDeclaration(attributeLists, modifiers, keyword, identifier, typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); + var newNode = SyntaxFactory.RecordDeclaration(this.Kind(), attributeLists, modifiers, keyword, classOrStructKeyword, identifier, typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); var annotations = GetAnnotations(); return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; } @@ -10219,28 +10264,29 @@ public RecordDeclarationSyntax Update(SyntaxList attributeL } internal override MemberDeclarationSyntax WithAttributeListsCore(SyntaxList attributeLists) => WithAttributeLists(attributeLists); - public new RecordDeclarationSyntax WithAttributeLists(SyntaxList attributeLists) => Update(attributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithAttributeLists(SyntaxList attributeLists) => Update(attributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override MemberDeclarationSyntax WithModifiersCore(SyntaxTokenList modifiers) => WithModifiers(modifiers); - public new RecordDeclarationSyntax WithModifiers(SyntaxTokenList modifiers) => Update(this.AttributeLists, modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithModifiers(SyntaxTokenList modifiers) => Update(this.AttributeLists, modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override TypeDeclarationSyntax WithKeywordCore(SyntaxToken keyword) => WithKeyword(keyword); - public new RecordDeclarationSyntax WithKeyword(SyntaxToken keyword) => Update(this.AttributeLists, this.Modifiers, keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithKeyword(SyntaxToken keyword) => Update(this.AttributeLists, this.Modifiers, keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public RecordDeclarationSyntax WithClassOrStructKeyword(SyntaxToken classOrStructKeyword) => Update(this.AttributeLists, this.Modifiers, this.Keyword, classOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override BaseTypeDeclarationSyntax WithIdentifierCore(SyntaxToken identifier) => WithIdentifier(identifier); - public new RecordDeclarationSyntax WithIdentifier(SyntaxToken identifier) => Update(this.AttributeLists, this.Modifiers, this.Keyword, identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithIdentifier(SyntaxToken identifier) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override TypeDeclarationSyntax WithTypeParameterListCore(TypeParameterListSyntax? typeParameterList) => WithTypeParameterList(typeParameterList); - public new RecordDeclarationSyntax WithTypeParameterList(TypeParameterListSyntax? typeParameterList) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, typeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); - public RecordDeclarationSyntax WithParameterList(ParameterListSyntax? parameterList) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, parameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithTypeParameterList(TypeParameterListSyntax? typeParameterList) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, typeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public RecordDeclarationSyntax WithParameterList(ParameterListSyntax? parameterList) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, parameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override BaseTypeDeclarationSyntax WithBaseListCore(BaseListSyntax? baseList) => WithBaseList(baseList); - public new RecordDeclarationSyntax WithBaseList(BaseListSyntax? baseList) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, baseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithBaseList(BaseListSyntax? baseList) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, baseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override TypeDeclarationSyntax WithConstraintClausesCore(SyntaxList constraintClauses) => WithConstraintClauses(constraintClauses); - public new RecordDeclarationSyntax WithConstraintClauses(SyntaxList constraintClauses) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, constraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithConstraintClauses(SyntaxList constraintClauses) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, constraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override BaseTypeDeclarationSyntax WithOpenBraceTokenCore(SyntaxToken openBraceToken) => WithOpenBraceToken(openBraceToken); - public new RecordDeclarationSyntax WithOpenBraceToken(SyntaxToken openBraceToken) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, openBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithOpenBraceToken(SyntaxToken openBraceToken) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, openBraceToken, this.Members, this.CloseBraceToken, this.SemicolonToken); internal override TypeDeclarationSyntax WithMembersCore(SyntaxList members) => WithMembers(members); - public new RecordDeclarationSyntax WithMembers(SyntaxList members) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, members, this.CloseBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithMembers(SyntaxList members) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, members, this.CloseBraceToken, this.SemicolonToken); internal override BaseTypeDeclarationSyntax WithCloseBraceTokenCore(SyntaxToken closeBraceToken) => WithCloseBraceToken(closeBraceToken); - public new RecordDeclarationSyntax WithCloseBraceToken(SyntaxToken closeBraceToken) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, closeBraceToken, this.SemicolonToken); + public new RecordDeclarationSyntax WithCloseBraceToken(SyntaxToken closeBraceToken) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, closeBraceToken, this.SemicolonToken); internal override BaseTypeDeclarationSyntax WithSemicolonTokenCore(SyntaxToken semicolonToken) => WithSemicolonToken(semicolonToken); - public new RecordDeclarationSyntax WithSemicolonToken(SyntaxToken semicolonToken) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, semicolonToken); + public new RecordDeclarationSyntax WithSemicolonToken(SyntaxToken semicolonToken) => Update(this.AttributeLists, this.Modifiers, this.Keyword, this.ClassOrStructKeyword, this.Identifier, this.TypeParameterList, this.ParameterList, this.BaseList, this.ConstraintClauses, this.OpenBraceToken, this.Members, this.CloseBraceToken, semicolonToken); internal override MemberDeclarationSyntax AddAttributeListsCore(params AttributeListSyntax[] items) => AddAttributeLists(items); public new RecordDeclarationSyntax AddAttributeLists(params AttributeListSyntax[] items) => WithAttributeLists(this.AttributeLists.AddRange(items)); diff --git a/src/Compilers/CSharp/Portable/LanguageVersion.cs b/src/Compilers/CSharp/Portable/LanguageVersion.cs index a3c7b1e2c43bb..4b7aff7955dad 100644 --- a/src/Compilers/CSharp/Portable/LanguageVersion.cs +++ b/src/Compilers/CSharp/Portable/LanguageVersion.cs @@ -119,19 +119,72 @@ public enum LanguageVersion /// /// C# language version 7.3 /// + /// + /// Features: + /// + /// Indexing fixed fields does not require pinning + /// ref local variables may be reassigned + /// stackalloc arrays support initializers + /// More types support the fixed statement + /// Enhanced generic constraints + /// Tuples support == and != + /// Attach attributes to the backing fields for auto-implemented properties + /// Method overload resolution improvements when arguments differ by 'in' + /// Extend expression variables in initializers + /// Improved overload candidates + /// New compiler options (-publicsign and -pathmap) + /// + /// CSharp7_3 = 703, /// /// C# language version 8.0 /// + /// + /// Features: + /// + /// Readonly members + /// Default interface methods + /// Pattern matching enhancements (switch expressions, property patterns, tuple patterns, and positional patterns) + /// Using declarations + /// Static local functions + /// Disposable ref structs + /// Nullable reference types + /// Asynchronous streams + /// Asynchronous disposable + /// Indices and ranges + /// Null-coalescing assignment + /// Unmanaged constructed types + /// Stackalloc in nested expressions + /// Enhancement of interpolated verbatim strings + /// + /// CSharp8 = 800, - // When this value is available in the released NuGet package, update LanguageVersionExtensions in the IDE layer to point to it. - // https://github.com/dotnet/roslyn/issues/43348 - // /// /// C# language version 9.0 /// + /// + /// Features: + /// + /// Records + /// Init only setters + /// Top-level statements + /// Pattern matching enhancements + /// Native sized integers + /// Function pointers + /// Suppress emitting localsinit flag + /// Target-typed new expressions + /// Static anonymous functions + /// Target-typed conditional expressions + /// Covariant return types + /// Extension GetEnumerator support for foreach loops + /// Lambda discard parameters + /// Attributes on local functions + /// Module initializers + /// New features for partial methods + /// + /// CSharp9 = 900, /// diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs index e349cd51c54d6..77e9e2fcf8da7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs @@ -72,6 +72,7 @@ internal override MethodSymbol Constructor } internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; internal override ImmutableArray InterfacesNoUseSiteDiagnostics(ConsList basesBeingResolved) diff --git a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureEnvironment.cs b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureEnvironment.cs index 34c834a3b9d07..c12e16225b510 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureEnvironment.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureEnvironment.cs @@ -142,6 +142,7 @@ internal override IEnumerable GetFieldsToEmit() IMethodSymbolInternal ISynthesizedMethodBodyImplementationSymbol.Method => _topLevelMethod; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs index 8f9338b4d1489..b3dd71d8252e4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs @@ -168,7 +168,7 @@ protected override ImmutableArray ExtraSynthesizedRefParameters => ImmutableArray.CastUp(_structEnvironments); internal int ExtraSynthesizedParameterCount => this._structEnvironments.IsDefault ? 0 : this._structEnvironments.Length; - internal override bool InheritsBaseMethodAttributes => BaseMethod is LocalFunctionSymbol; + internal override bool InheritsBaseMethodAttributes => true; internal override bool GenerateDebugInfo => !this.IsAsync; internal override bool IsExpressionBodied => false; diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorStateMachine.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorStateMachine.cs index ed2cac2c961be..673f7e4e02ee2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorStateMachine.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorStateMachine.cs @@ -61,6 +61,7 @@ internal override ImmutableArray InterfacesNoUseSiteDiagnostics internal override NamedTypeSymbol BaseTypeNoUseSiteDiagnostics => ContainingAssembly.GetSpecialType(SpecialType.System_Object); internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DynamicSiteContainer.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DynamicSiteContainer.cs index 520680e26e269..982c07056192a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DynamicSiteContainer.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DynamicSiteContainer.cs @@ -37,6 +37,7 @@ public sealed override bool AreLocalsZeroed } internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; bool ISynthesizedMethodBodyImplementationSymbol.HasMethodBodyDependency diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 4d749f2fd3537..9d628989bfcb2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -248,17 +248,25 @@ private static bool IsUnusedDeconstruction(BoundExpression node) public override BoundNode VisitLambda(BoundLambda node) { _sawLambdas = true; - CheckRefReadOnlySymbols(node.Symbol); + + var lambda = node.Symbol; + CheckRefReadOnlySymbols(lambda); var oldContainingSymbol = _factory.CurrentFunction; + var oldInstrumenter = _instrumenter; try { - _factory.CurrentFunction = node.Symbol; + _factory.CurrentFunction = lambda; + if (lambda.IsDirectlyExcludedFromCodeCoverage) + { + _instrumenter = RemoveDynamicAnalysisInjectors(oldInstrumenter); + } return base.VisitLambda(node)!; } finally { _factory.CurrentFunction = oldContainingSymbol; + _instrumenter = oldInstrumenter; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs index 94e96efb057c5..b8449b4d8b567 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.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.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -103,15 +104,26 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre public override BoundNode VisitWithExpression(BoundWithExpression withExpr) { - RoslynDebug.AssertNotNull(withExpr.CloneMethod); - Debug.Assert(withExpr.CloneMethod.ParameterCount == 0); - Debug.Assert(withExpr.Receiver.Type!.Equals(withExpr.Type, TypeCompareKind.ConsiderEverything)); + TypeSymbol type = withExpr.Type; + BoundExpression receiver = withExpr.Receiver; + Debug.Assert(receiver.Type!.Equals(type, TypeCompareKind.ConsiderEverything)); // for a with expression of the form // - // receiver with { P1 = e1, P2 = e2 } + // receiver with { P1 = e1, P2 = e2 } // P3 is copied implicitly // - // we want to lower it to a call to the receiver's `Clone` method, then + // if the receiver is a struct, duplicate the value, then set the given struct properties: + // + // var tmp = receiver; + // tmp.P1 = e1; + // tmp.P2 = e2; + // tmp + // + // if the receiver is an anonymous type, then invoke its constructor: + // + // new Type(e1, e2, receiver.P3); + // + // otherwise the receiver is a record class and we want to lower it to a call to its `Clone` method, then // set the given record properties. i.e. // // var tmp = (ReceiverType)receiver.Clone(); @@ -119,17 +131,84 @@ public override BoundNode VisitWithExpression(BoundWithExpression withExpr) // tmp.P2 = e2; // tmp - var cloneCall = _factory.Convert( - withExpr.Type, - _factory.Call( - VisitExpression(withExpr.Receiver), - withExpr.CloneMethod)); + BoundExpression rewrittenReceiver = VisitExpression(receiver); + + if (type.IsAnonymousType) + { + var anonymousType = (AnonymousTypeManager.AnonymousTypePublicSymbol)type; + var sideEffects = ArrayBuilder.GetInstance(); + var temps = ArrayBuilder.GetInstance(); + BoundLocal oldValue = _factory.StoreToTemp(rewrittenReceiver, out BoundAssignmentOperator boundAssignmentToTemp); + temps.Add(oldValue.LocalSymbol); + sideEffects.Add(boundAssignmentToTemp); + + BoundExpression value = _factory.New(anonymousType, getAnonymousTypeValues(withExpr, oldValue, anonymousType, sideEffects, temps)); + + return new BoundSequence(withExpr.Syntax, temps.ToImmutableAndFree(), sideEffects.ToImmutableAndFree(), value, type); + } + + BoundExpression expression; + if (type.IsValueType) + { + expression = rewrittenReceiver; + } + else + { + Debug.Assert(withExpr.CloneMethod is not null); + Debug.Assert(withExpr.CloneMethod.ParameterCount == 0); + + expression = _factory.Convert( + type, + _factory.Call( + rewrittenReceiver, + withExpr.CloneMethod)); + } return MakeExpressionWithInitializer( withExpr.Syntax, - cloneCall, + expression, withExpr.InitializerExpression, - withExpr.Type); + type); + + ImmutableArray getAnonymousTypeValues(BoundWithExpression withExpr, BoundExpression oldValue, AnonymousTypeManager.AnonymousTypePublicSymbol anonymousType, + ArrayBuilder sideEffects, ArrayBuilder temps) + { + // map: [propertyIndex] -> valueTemp + var valueTemps = ArrayBuilder.GetInstance(anonymousType.Properties.Length, fillWithValue: null); + + foreach (BoundExpression initializer in withExpr.InitializerExpression.Initializers) + { + var assignment = (BoundAssignmentOperator)initializer; + var left = (BoundObjectInitializerMember)assignment.Left; + Debug.Assert(left.MemberSymbol is not null); + + // We evaluate the values provided in source first + BoundLocal valueTemp = _factory.StoreToTemp(assignment.Right, out BoundAssignmentOperator boundAssignmentToTemp); + temps.Add(valueTemp.LocalSymbol); + sideEffects.Add(boundAssignmentToTemp); + + var property = left.MemberSymbol; + Debug.Assert(property.MemberIndexOpt!.Value >= 0 && property.MemberIndexOpt.Value < anonymousType.Properties.Length); + valueTemps[property.MemberIndexOpt.Value] = valueTemp; + } + + var builder = ArrayBuilder.GetInstance(anonymousType.Properties.Length); + foreach (var property in anonymousType.Properties) + { + if (valueTemps[property.MemberIndexOpt!.Value] is BoundExpression initializerValue) + { + builder.Add(initializerValue); + } + else + { + // The values that are implicitly copied over will get evaluated afterwards, in the order they are needed + builder.Add(_factory.Property(oldValue, property)); + } + } + + valueTemps.Free(); + return builder.ToImmutableAndFree(); + } } [return: NotNullIfNotNull("initializerExpressionOpt")] diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index e76c80f2e1c36..a8770d3e0a476 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -5,15 +5,11 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Operations; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -274,7 +270,7 @@ private BoundStatement UpdateStatement(BoundSpillSequenceBuilder builder, BoundS builder.AddStatement(statement); } - var result = _F.Block(builder.GetLocals(), builder.GetStatements()); + var result = new BoundBlock(statement.Syntax, builder.GetLocals(), builder.GetStatements()) { WasCompilerGenerated = true }; builder.Free(); return result; diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 51f1b6fc04241..947d3f817b779 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -633,6 +633,12 @@ public BoundObjectCreationExpression New(NamedTypeSymbol type, params BoundExpre public BoundObjectCreationExpression New(MethodSymbol ctor, params BoundExpression[] args) => New(ctor, args.ToImmutableArray()); + public BoundObjectCreationExpression New(NamedTypeSymbol type, ImmutableArray args) + { + var ctor = type.InstanceConstructors.Single(c => c.ParameterCount == args.Length); + return New(ctor, args); + } + public BoundObjectCreationExpression New(MethodSymbol ctor, ImmutableArray args) => new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true }; diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 6c414ad935b8a..90568f4cc70fe 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -266,7 +266,7 @@ public CSharpOperationFactory(SemanticModel semanticModel) case BoundKind.UnconvertedSwitchExpression: throw ExceptionUtilities.Unreachable; case BoundKind.ConvertedSwitchExpression: - return CreateBoundSwitchExpressionOperation((BoundSwitchExpression)boundNode); + return CreateBoundSwitchExpressionOperation((BoundConvertedSwitchExpression)boundNode); case BoundKind.SwitchExpressionArm: return CreateBoundSwitchExpressionArmOperation((BoundSwitchExpressionArm)boundNode); case BoundKind.ObjectOrCollectionValuePlaceholder: @@ -2150,13 +2150,26 @@ private ISwitchCaseOperation CreateBoundSwitchSectionOperation(BoundSwitchSectio return new SwitchCaseOperation(clauses, body, locals, condition: null, _semanticModel, boundSwitchSection.Syntax, isImplicit: boundSwitchSection.WasCompilerGenerated); } - private ISwitchExpressionOperation CreateBoundSwitchExpressionOperation(BoundSwitchExpression boundSwitchExpression) + private ISwitchExpressionOperation CreateBoundSwitchExpressionOperation(BoundConvertedSwitchExpression boundSwitchExpression) { IOperation value = Create(boundSwitchExpression.Expression); ImmutableArray arms = CreateFromArray(boundSwitchExpression.SwitchArms); + + bool isExhaustive; + if (boundSwitchExpression.DefaultLabel != null) + { + Debug.Assert(boundSwitchExpression.DefaultLabel is GeneratedLabelSymbol); + isExhaustive = false; + } + else + { + isExhaustive = true; + } + return new SwitchExpressionOperation( value, arms, + isExhaustive, _semanticModel, boundSwitchExpression.Syntax, boundSwitchExpression.GetPublicTypeSymbol(), diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 24b4d78971d71..88bc472607d29 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.Text; @@ -467,20 +466,20 @@ private void ParseNamespaceBody(ref SyntaxToken openBrace, ref NamespaceBodyBuil } else { - // incomplete members must be processed before we add any nodes to the body: - ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBrace, ref body, ref initialBadNodes); + parseUsingDirective(ref openBrace, ref body, ref initialBadNodes, ref seen, ref pendingIncompleteMembers); + } - var @using = this.ParseUsingDirective(); - if (seen > NamespaceParts.Usings) - { - @using = this.AddError(@using, ErrorCode.ERR_UsingAfterElements); - this.AddSkippedNamespaceText(ref openBrace, ref body, ref initialBadNodes, @using); - } - else - { - body.Usings.Add(@using); - seen = NamespaceParts.Usings; - } + reportUnexpectedToken = true; + break; + + case SyntaxKind.IdentifierToken: + if (this.CurrentToken.ContextualKind != SyntaxKind.GlobalKeyword || this.PeekToken(1).Kind != SyntaxKind.UsingKeyword) + { + goto default; + } + else + { + parseUsingDirective(ref openBrace, ref body, ref initialBadNodes, ref seen, ref pendingIncompleteMembers); } reportUnexpectedToken = true; @@ -584,6 +583,7 @@ MemberDeclarationSyntax adjustStateAndReportStatementOutOfOrder(ref NamespacePar case SyntaxKind.InterfaceDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: if (seen < NamespaceParts.TypesAndNamespaces) { seen = NamespaceParts.TypesAndNamespaces; @@ -600,6 +600,24 @@ MemberDeclarationSyntax adjustStateAndReportStatementOutOfOrder(ref NamespacePar return memberOrStatement; } + + void parseUsingDirective(ref SyntaxToken openBrace, ref NamespaceBodyBuilder body, ref SyntaxListBuilder initialBadNodes, ref NamespaceParts seen, ref SyntaxListBuilder pendingIncompleteMembers) + { + // incomplete members must be processed before we add any nodes to the body: + ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBrace, ref body, ref initialBadNodes); + + var @using = this.ParseUsingDirective(); + if (seen > NamespaceParts.Usings) + { + @using = this.AddError(@using, ErrorCode.ERR_UsingAfterElements); + this.AddSkippedNamespaceText(ref openBrace, ref body, ref initialBadNodes, @using); + } + else + { + body.Usings.Add(@using); + seen = NamespaceParts.Usings; + } + } } private GlobalStatementSyntax CheckTopLevelStatementsFeatureAvailability(GlobalStatementSyntax globalStatementSyntax) @@ -732,6 +750,12 @@ private UsingDirectiveSyntax ParseUsingDirective() return (UsingDirectiveSyntax)this.EatNode(); } + SyntaxToken globalToken = null; + if (this.CurrentToken.ContextualKind == SyntaxKind.GlobalKeyword) + { + globalToken = ConvertToKeyword(this.EatToken()); + } + Debug.Assert(this.CurrentToken.Kind == SyntaxKind.UsingKeyword); var usingToken = this.EatToken(SyntaxKind.UsingKeyword); @@ -775,12 +799,17 @@ private UsingDirectiveSyntax ParseUsingDirective() semicolon = this.EatToken(SyntaxKind.SemicolonToken); } - var usingDirective = _syntaxFactory.UsingDirective(usingToken, staticToken, alias, name, semicolon); + var usingDirective = _syntaxFactory.UsingDirective(globalToken, usingToken, staticToken, alias, name, semicolon); if (staticToken != null) { usingDirective = CheckFeatureAvailability(usingDirective, MessageID.IDS_FeatureUsingStatic); } + if (globalToken != null) + { + usingDirective = CheckFeatureAvailability(usingDirective, MessageID.IDS_FeatureGlobalUsing); + } + return usingDirective; } @@ -818,7 +847,8 @@ private SyntaxList ParseAttributeDeclarations() while (this.IsPossibleAttributeDeclaration()) { - attributes.Add(this.ParseAttributeDeclaration()); + var attribute = this.ParseAttributeDeclaration(); + attributes.Add(attribute); } _termState = saveTerm; @@ -1085,8 +1115,6 @@ internal static DeclarationModifiers GetModifier(SyntaxKind kind, SyntaxKind con return DeclarationModifiers.Async; case SyntaxKind.RefKeyword: return DeclarationModifiers.Ref; - case SyntaxKind.DataKeyword: - return DeclarationModifiers.Data; case SyntaxKind.IdentifierToken: switch (contextualKind) { @@ -1094,8 +1122,6 @@ internal static DeclarationModifiers GetModifier(SyntaxKind kind, SyntaxKind con return DeclarationModifiers.Partial; case SyntaxKind.AsyncKeyword: return DeclarationModifiers.Async; - case SyntaxKind.DataKeyword: - return DeclarationModifiers.Data; } goto default; @@ -1157,9 +1183,9 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors) // this is a partial ref struct declaration { var next = PeekToken(1); - if (next.Kind == SyntaxKind.StructKeyword || + if (isStructOrRecordKeyword(next) || (next.ContextualKind == SyntaxKind.PartialKeyword && - PeekToken(2).Kind == SyntaxKind.StructKeyword)) + isStructOrRecordKeyword(PeekToken(2)))) { modTok = this.EatToken(); modTok = CheckFeatureAvailability(modTok, MessageID.IDS_FeatureRefStructs); @@ -1186,9 +1212,6 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors) modTok = CheckFeatureAvailability(modTok, MessageID.IDS_FeatureAsync); break; - case DeclarationModifiers.Data: - return; - default: modTok = this.EatToken(); break; @@ -1196,6 +1219,25 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors) tokens.Add(modTok); } + + bool isStructOrRecordKeyword(SyntaxToken token) + { + if (token.Kind == SyntaxKind.StructKeyword) + { + return true; + } + + if (token.ContextualKind == SyntaxKind.RecordKeyword) + { + // This is an unusual use of LangVersion. Normally we only produce errors when the langversion + // does not support a feature, but in this case we are effectively making a language breaking + // change to consider "record" a type declaration in all ambiguous cases. To avoid breaking + // older code that is not using C# 9 we conditionally parse based on langversion + return IsFeatureEnabled(MessageID.IDS_FeatureRecords); + } + + return false; + } } private bool ShouldAsyncBeTreatedAsModifier(bool parsingStatementNotDeclaration) @@ -1327,7 +1369,8 @@ private static bool IsNonContextualModifier(SyntaxToken nextToken) private bool IsPartialType() { Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword); - switch (this.PeekToken(1).Kind) + var nextToken = this.PeekToken(1); + switch (nextToken.Kind) { case SyntaxKind.StructKeyword: case SyntaxKind.ClassKeyword: @@ -1335,6 +1378,15 @@ private bool IsPartialType() return true; } + if (nextToken.ContextualKind == SyntaxKind.RecordKeyword) + { + // This is an unusual use of LangVersion. Normally we only produce errors when the langversion + // does not support a feature, but in this case we are effectively making a language breaking + // change to consider "record" a type declaration in all ambiguous cases. To avoid breaking + // older code that is not using C# 9 we conditionally parse based on langversion + return IsFeatureEnabled(MessageID.IDS_FeatureRecords); + } + return false; } @@ -1382,6 +1434,12 @@ private bool IsPossibleMemberName() switch (this.CurrentToken.Kind) { case SyntaxKind.IdentifierToken: + if (this.CurrentToken.ContextualKind == SyntaxKind.GlobalKeyword && this.PeekToken(1).Kind == SyntaxKind.UsingKeyword) + { + return false; + } + + return true; case SyntaxKind.ThisKeyword: return true; default: @@ -1455,9 +1513,11 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis var keyword = ConvertToKeyword(this.EatToken()); var outerSaveTerm = _termState; + SyntaxToken? recordModifier = null; if (keyword.Kind == SyntaxKind.RecordKeyword) { _termState |= TerminatorState.IsEndOfRecordSignature; + recordModifier = eatRecordModifierIfAvailable(); } var saveTerm = _termState; @@ -1469,13 +1529,13 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis var paramList = keyword.Kind == SyntaxKind.RecordKeyword && CurrentToken.Kind == SyntaxKind.OpenParenToken ? ParseParenthesizedParameterList() : null; - var baseList = this.ParseBaseList(keyword, paramList is object); + var baseList = this.ParseBaseList(); _termState = saveTerm; // Parse class body bool parseMembers = true; - SyntaxListBuilder members = default(SyntaxListBuilder); - var constraints = default(SyntaxListBuilder); + SyntaxListBuilder members = default; + SyntaxListBuilder constraints = default; try { if (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword) @@ -1560,6 +1620,38 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis closeBrace = null; } + return constructTypeDeclaration(_syntaxFactory, attributes, modifiers, keyword, recordModifier, name, typeParameters, paramList, baseList, constraints, openBrace, members, closeBrace, semicolon); + } + finally + { + if (!members.IsNull) + { + _pool.Free(members); + } + + if (!constraints.IsNull) + { + _pool.Free(constraints); + } + } + + SyntaxToken? eatRecordModifierIfAvailable() + { + Debug.Assert(keyword.Kind == SyntaxKind.RecordKeyword); + if (CurrentToken.Kind is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword) + { + var result = EatToken(); + result = CheckFeatureAvailability(result, MessageID.IDS_FeatureRecordStructs); + return result; + } + + return null; + } + + static TypeDeclarationSyntax constructTypeDeclaration(ContextAwareSyntax syntaxFactory, SyntaxList attributes, SyntaxListBuilder modifiers, SyntaxToken keyword, SyntaxToken? recordModifier, + SyntaxToken name, TypeParameterListSyntax typeParameters, ParameterListSyntax? paramList, BaseListSyntax baseList, SyntaxListBuilder constraints, + SyntaxToken? openBrace, SyntaxListBuilder members, SyntaxToken? closeBrace, SyntaxToken semicolon) + { var modifiersList = (SyntaxList)modifiers.ToList(); var membersList = (SyntaxList)members; var constraintsList = (SyntaxList)constraints; @@ -1569,7 +1661,7 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis RoslynDebug.Assert(paramList is null); RoslynDebug.Assert(openBrace != null); RoslynDebug.Assert(closeBrace != null); - return _syntaxFactory.ClassDeclaration( + return syntaxFactory.ClassDeclaration( attributes, modifiersList, keyword, @@ -1586,7 +1678,7 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis RoslynDebug.Assert(paramList is null); RoslynDebug.Assert(openBrace != null); RoslynDebug.Assert(closeBrace != null); - return _syntaxFactory.StructDeclaration( + return syntaxFactory.StructDeclaration( attributes, modifiersList, keyword, @@ -1603,7 +1695,7 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis RoslynDebug.Assert(paramList is null); RoslynDebug.Assert(openBrace != null); RoslynDebug.Assert(closeBrace != null); - return _syntaxFactory.InterfaceDeclaration( + return syntaxFactory.InterfaceDeclaration( attributes, modifiersList, keyword, @@ -1617,10 +1709,16 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis semicolon); case SyntaxKind.RecordKeyword: - return _syntaxFactory.RecordDeclaration( + // record struct ... + // record ... + // record class ... + SyntaxKind declarationKind = recordModifier?.Kind == SyntaxKind.StructKeyword ? SyntaxKind.RecordStructDeclaration : SyntaxKind.RecordDeclaration; + return syntaxFactory.RecordDeclaration( + declarationKind, attributes, modifiers.ToList(), keyword, + classOrStructKeyword: recordModifier, name, typeParameters, paramList, @@ -1635,18 +1733,6 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis throw ExceptionUtilities.UnexpectedValue(keyword.Kind); } } - finally - { - if (!members.IsNull) - { - _pool.Free(members); - } - - if (!constraints.IsNull) - { - _pool.Free(constraints); - } - } } #nullable disable @@ -1743,7 +1829,7 @@ private bool IsPossibleAggregateClauseStartOrStop() || this.IsCurrentTokenWhereOfConstraintClause(); } - private BaseListSyntax ParseBaseList(SyntaxToken typeKeyword, bool haveParameters) + private BaseListSyntax ParseBaseList() { if (this.CurrentToken.Kind != SyntaxKind.ColonToken) { @@ -1761,11 +1847,6 @@ private BaseListSyntax ParseBaseList(SyntaxToken typeKeyword, bool haveParameter if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken) { argumentList = this.ParseParenthesizedArgumentList(); - - if (typeKeyword.Kind != SyntaxKind.RecordKeyword || !haveParameters) - { - argumentList = this.AddErrorToFirstToken(argumentList, ErrorCode.ERR_UnexpectedArgumentList); - } } list.Add(argumentList is object ? _syntaxFactory.PrimaryConstructorBaseType(firstType, argumentList) : (BaseTypeSyntax)_syntaxFactory.SimpleBaseType(firstType)); @@ -2051,6 +2132,7 @@ private bool CanReuseMemberDeclaration(SyntaxKind kind, bool isGlobal) case SyntaxKind.ConstructorDeclaration: case SyntaxKind.NamespaceDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return true; case SyntaxKind.FieldDeclaration: case SyntaxKind.MethodDeclaration: @@ -2504,7 +2586,6 @@ private bool IsMisplacedModifier(SyntaxListBuilder modifiers, SyntaxList // case 2: ( T x ) => // case 3: ( out T x, @@ -11056,6 +11160,8 @@ private bool ScanExplicitlyTypedLambda(Precedence precedence) // Advance past the open paren or comma. this.EatToken(); + _ = ParseAttributeDeclarations(); + // Eat 'out' or 'ref' for cases [3, 6]. Even though not allowed in a lambda, // we treat `params` similarly for better error recovery. switch (this.CurrentToken.Kind) @@ -11336,15 +11442,48 @@ bool isBinaryPatternKeyword() private bool IsPossibleLambdaExpression(Precedence precedence) { - // Only call into this if after `static` or after a legal identifier. + // Only call into this if after `static`, '[', or after a legal identifier. Debug.Assert( - this.CurrentToken.Kind == SyntaxKind.StaticKeyword || + this.CurrentToken.Kind is SyntaxKind.StaticKeyword or SyntaxKind.OpenBracketToken || this.IsTrueIdentifier(this.CurrentToken)); if (precedence > Precedence.Lambda) { return false; } + if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken) + { + var resetPoint = this.GetResetPoint(); + try + { + _ = ParseAttributeDeclarations(); + return CurrentToken.Kind switch + { + SyntaxKind.StaticKeyword or SyntaxKind.IdentifierToken => IsPossibleLambdaExpressionCore(precedence), + SyntaxKind.OpenParenToken => ScanParenthesizedLambda(precedence), + _ => false, + }; + } + finally + { + this.Reset(ref resetPoint); + this.Release(ref resetPoint); + } + } + else + { + return IsPossibleLambdaExpressionCore(precedence); + } + } + + private bool IsPossibleLambdaExpressionCore(Precedence precedence) + { + // Only call into this if after `static` or after a legal identifier. + Debug.Assert( + this.CurrentToken.Kind == SyntaxKind.StaticKeyword || + this.IsTrueIdentifier(this.CurrentToken)); + Debug.Assert(precedence <= Precedence.Lambda); + // If we start with `static` or `async static` then just jump past those and do the // analysis after that point. Note, we don't just blindly consume `async` in `static // async` because that `async` may not be a modifier (it may just be an identifier) and @@ -11448,7 +11587,7 @@ private bool IsPossibleLambdaExpression(Precedence precedence) } // Check whether looks like implicitly or explicitly typed lambda - bool isAsync = ScanParenthesizedImplicitlyTypedLambda(precedence) || ScanExplicitlyTypedLambda(precedence); + bool isAsync = ScanParenthesizedLambda(precedence); // Restore current token index this.Reset(ref resetPoint); @@ -12267,6 +12406,7 @@ private SyntaxList ParseAnonymousFunctionModifiers() private LambdaExpressionSyntax ParseLambdaExpression() { + var attributes = ParseAttributeDeclarations(); var parentScopeIsInAsync = this.IsInAsync; var result = parseLambdaExpressionWorker(); this.IsInAsync = parentScopeIsInAsync; @@ -12288,7 +12428,7 @@ LambdaExpressionSyntax parseLambdaExpressionWorker() var (block, expression) = ParseLambdaBody(); return _syntaxFactory.ParenthesizedLambdaExpression( - modifiers, paramList, arrow, block, expression); + attributes, modifiers, paramList, arrow, block, expression); } else { @@ -12302,7 +12442,7 @@ LambdaExpressionSyntax parseLambdaExpressionWorker() var (block, expression) = ParseLambdaBody(); return _syntaxFactory.SimpleLambdaExpression( - modifiers, parameter, arrow, block, expression); + attributes, modifiers, parameter, arrow, block, expression); } } } @@ -12379,6 +12519,7 @@ private bool IsPossibleLambdaParameter() case SyntaxKind.OutKeyword: case SyntaxKind.InKeyword: case SyntaxKind.OpenParenToken: // tuple + case SyntaxKind.OpenBracketToken: // attribute return true; case SyntaxKind.IdentifierToken: @@ -12402,6 +12543,8 @@ private PostSkipAction SkipBadLambdaParameterListTokens(ref SyntaxToken openPare private ParameterSyntax ParseLambdaParameter() { + var attributes = ParseAttributeDeclarations(); + // Params are actually illegal in a lambda, but we'll allow it for error recovery purposes and // give the "params unexpected" error at semantic analysis time. bool hasModifier = IsParameterModifier(this.CurrentToken.Kind); @@ -12420,7 +12563,7 @@ private ParameterSyntax ParseLambdaParameter() } SyntaxToken paramName = this.ParseIdentifierToken(); - var parameter = _syntaxFactory.Parameter(default(SyntaxList), modifiers.ToList(), paramType, paramName, null); + var parameter = _syntaxFactory.Parameter(attributes, modifiers.ToList(), paramType, paramName, null); _pool.Free(modifiers); return parameter; } diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 47fdde3be2e2f..780fce023cde3 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1 +1,30 @@ +Microsoft.CodeAnalysis.CSharp.SyntaxKind.RecordStructDeclaration = 9068 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind override Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetUsedAssemblyReferences(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax +abstract Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList +Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax +override Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList +Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax.WithClassOrStructKeyword(Microsoft.CodeAnalysis.SyntaxToken classOrStructKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.SyntaxToken classOrStructKeyword, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax baseList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SyntaxList members, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecordDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecordDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.SyntaxToken classOrStructKeyword, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax baseList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SyntaxList members, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax.ClassOrStructKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecordDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax baseList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.SyntaxList members) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecordDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxToken keyword, string identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax +override Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList +Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax parameter, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SimpleLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax parameter, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SimpleLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax parameter, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.UsingDirective(Microsoft.CodeAnalysis.SyntaxToken globalKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken staticKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameEqualsSyntax alias, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.GlobalKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken +Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken globalKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken staticKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameEqualsSyntax alias, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.WithGlobalKeyword(Microsoft.CodeAnalysis.SyntaxToken globalKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax +*REMOVED*Microsoft.CodeAnalysis.CSharp.SyntaxKind.DataKeyword = 8441 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.GetLineMappings(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IEnumerable diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 8b568e69e1e92..007d56403f024 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -4,7 +4,6 @@ #nullable disable -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -566,6 +565,8 @@ private static SymbolDisplayPartKind GetPartKind(INamedTypeSymbol symbol) { case TypeKind.Class when symbol.IsRecord: return SymbolDisplayPartKind.RecordClassName; + case TypeKind.Struct when symbol.IsRecord: + return SymbolDisplayPartKind.RecordStructName; case TypeKind.Submission: case TypeKind.Module: case TypeKind.Class: @@ -667,6 +668,22 @@ private void AddTypeKind(INamedTypeSymbol symbol) AddSpace(); break; + case TypeKind.Struct when symbol.IsRecord: + // In case ref record structs are allowed in future, call AddKeyword(SyntaxKind.RefKeyword) and remove assertion. + Debug.Assert(!symbol.IsRefLikeType); + + if (symbol.IsReadOnly) + { + AddKeyword(SyntaxKind.ReadOnlyKeyword); + AddSpace(); + } + + AddKeyword(SyntaxKind.RecordKeyword); + AddSpace(); + AddKeyword(SyntaxKind.StructKeyword); + AddSpace(); + break; + case TypeKind.Module: case TypeKind.Class: AddKeyword(SyntaxKind.ClassKeyword); diff --git a/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs index a20851371cfaf..6a1dd783477c9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs @@ -45,71 +45,40 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// IList<Symbol> SemanticModel.LookupSymbols(CSharpSyntaxNode location, NamespaceOrTypeSymbol container = null, string name = null, int? arity = null, LookupOptions options = LookupOptions.Default, List<Symbol> results = null); /// /// - internal sealed class AliasSymbol : Symbol + internal abstract class AliasSymbol : Symbol { - private readonly SyntaxToken _aliasName; - private readonly Binder _binder; - - private SymbolCompletionState _state; - private NamespaceOrTypeSymbol? _aliasTarget; private readonly ImmutableArray _locations; // NOTE: can be empty for the "global" alias. - - // lazy binding - private readonly NameSyntax? _aliasTargetName; + private readonly string _aliasName; private readonly bool _isExtern; - private BindingDiagnosticBag? _aliasTargetDiagnostics; + private readonly Symbol? _containingSymbol; - private AliasSymbol(Binder binder, NamespaceOrTypeSymbol target, SyntaxToken aliasName, ImmutableArray locations) + protected AliasSymbol(string aliasName, Symbol? containingSymbol, ImmutableArray locations, bool isExtern) { - _aliasName = aliasName; - _locations = locations; - _aliasTarget = target; - _binder = binder; - _state.NotePartComplete(CompletionPart.AliasTarget); - } + Debug.Assert(locations.Length == 1 || (locations.IsEmpty && aliasName == "global")); // It looks like equality implementation depends on this condition. - private AliasSymbol(Binder binder, SyntaxToken aliasName) - { + _locations = locations; _aliasName = aliasName; - _locations = ImmutableArray.Create(aliasName.GetLocation()); - _binder = binder; - } - - internal AliasSymbol(Binder binder, NameSyntax name, NameEqualsSyntax alias) - : this(binder, alias.Name.Identifier) - { - Debug.Assert(name.Parent.IsKind(SyntaxKind.UsingDirective)); - Debug.Assert(name.Parent == alias.Parent); - - _aliasTargetName = name; - } - - internal AliasSymbol(Binder binder, ExternAliasDirectiveSyntax syntax) - : this(binder, syntax.Identifier) - { - _isExtern = true; + _isExtern = isExtern; + _containingSymbol = containingSymbol; } // For the purposes of SemanticModel, it is convenient to have an AliasSymbol for the "global" namespace that "global::" binds // to. This alias symbol is returned only when binding "global::" (special case code). - internal static AliasSymbol CreateGlobalNamespaceAlias(NamespaceSymbol globalNamespace, Binder globalNamespaceBinder) + internal static AliasSymbol CreateGlobalNamespaceAlias(NamespaceSymbol globalNamespace) { - SyntaxToken aliasName = SyntaxFactory.Identifier(SyntaxFactory.TriviaList(), SyntaxKind.GlobalKeyword, "global", "global", SyntaxFactory.TriviaList()); - return new AliasSymbol(globalNamespaceBinder, globalNamespace, aliasName, ImmutableArray.Empty); + return new AliasSymbolFromResolvedTarget(globalNamespace, "global", globalNamespace, ImmutableArray.Empty, isExtern: false); } - internal static AliasSymbol CreateCustomDebugInfoAlias(NamespaceOrTypeSymbol targetSymbol, SyntaxToken aliasToken, Binder binder) + internal static AliasSymbol CreateCustomDebugInfoAlias(NamespaceOrTypeSymbol targetSymbol, SyntaxToken aliasToken, Symbol? containingSymbol, bool isExtern) { - return new AliasSymbol(binder, targetSymbol, aliasToken, ImmutableArray.Create(aliasToken.GetLocation())); + return new AliasSymbolFromResolvedTarget(targetSymbol, aliasToken.ValueText, containingSymbol, ImmutableArray.Create(aliasToken.GetLocation()), isExtern); } internal AliasSymbol ToNewSubmission(CSharpCompilation compilation) { - Debug.Assert(_binder.Compilation.IsSubmission); - // We can pass basesBeingResolved: null because base type cycles can't cross // submission boundaries - there's no way to depend on a subsequent submission. - var previousTarget = GetAliasTarget(basesBeingResolved: null); + var previousTarget = Target; if (previousTarget.Kind != SymbolKind.Namespace) { return this; @@ -117,15 +86,14 @@ internal AliasSymbol ToNewSubmission(CSharpCompilation compilation) var expandedGlobalNamespace = compilation.GlobalNamespace; var expandedNamespace = Imports.ExpandPreviousSubmissionNamespace((NamespaceSymbol)previousTarget, expandedGlobalNamespace); - var binder = new InContainerBinder(expandedGlobalNamespace, new BuckStopsHereBinder(compilation)); - return new AliasSymbol(binder, expandedNamespace, _aliasName, _locations); + return new AliasSymbolFromResolvedTarget(expandedNamespace, Name, ContainingSymbol, _locations, _isExtern); } - public override string Name + public sealed override string Name { get { - return _aliasName.ValueText; + return _aliasName; } } @@ -141,12 +109,9 @@ public override SymbolKind Kind /// Gets the for the /// namespace or type referenced by the alias. /// - public NamespaceOrTypeSymbol Target + public abstract NamespaceOrTypeSymbol Target { - get - { - return GetAliasTarget(basesBeingResolved: null); - } + get; } public override ImmutableArray Locations @@ -165,7 +130,7 @@ public override ImmutableArray DeclaringSyntaxReferences } } - public override bool IsExtern + public sealed override bool IsExtern { get { @@ -235,11 +200,11 @@ public override Accessibility DeclaredAccessibility /// return that as the "containing" symbol, even though the alias isn't a member of the /// namespace as such. /// - public override Symbol? ContainingSymbol + public sealed override Symbol? ContainingSymbol { get { - return _binder.ContainingMemberOrLambda; + return _containingSymbol; } } @@ -259,7 +224,94 @@ public override TResult Accept(CSharpSymbolVisitor visitor) } // basesBeingResolved is only used to break circular references. - internal NamespaceOrTypeSymbol GetAliasTarget(ConsList? basesBeingResolved) + internal abstract NamespaceOrTypeSymbol GetAliasTarget(ConsList? basesBeingResolved); + + internal void CheckConstraints(BindingDiagnosticBag diagnostics) + { + var target = this.Target as TypeSymbol; + if ((object?)target != null && Locations.Length > 0) + { + var corLibrary = this.ContainingAssembly.CorLibrary; + var conversions = new TypeConversions(corLibrary); + target.CheckAllConstraints(DeclaringCompilation, conversions, Locations[0], diagnostics); + } + } + + public override bool Equals(Symbol? obj, TypeCompareKind compareKind) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (ReferenceEquals(obj, null)) + { + return false; + } + + AliasSymbol? other = obj as AliasSymbol; + + return (object?)other != null && + Equals(this.Locations.FirstOrDefault(), other.Locations.FirstOrDefault()) && + this.ContainingAssembly.Equals(other.ContainingAssembly, compareKind); + } + + public override int GetHashCode() + { + if (this.Locations.Length > 0) + return this.Locations.First().GetHashCode(); + else + return Name.GetHashCode(); + } + + internal abstract override bool RequiresCompletion + { + get; + } + + protected override ISymbol CreateISymbol() + { + return new PublicModel.AliasSymbol(this); + } + } + + internal sealed class AliasSymbolFromSyntax : AliasSymbol + { + private readonly SyntaxReference _directive; + private SymbolCompletionState _state; + private NamespaceOrTypeSymbol? _aliasTarget; + + // lazy binding + private BindingDiagnosticBag? _aliasTargetDiagnostics; + + internal AliasSymbolFromSyntax(SourceNamespaceSymbol containingSymbol, UsingDirectiveSyntax syntax) + : base(syntax.Alias!.Name.Identifier.ValueText, containingSymbol, ImmutableArray.Create(syntax.Alias!.Name.Identifier.GetLocation()), isExtern: false) + { + Debug.Assert(syntax.Alias is object); + + _directive = syntax.GetReference(); + } + + internal AliasSymbolFromSyntax(SourceNamespaceSymbol containingSymbol, ExternAliasDirectiveSyntax syntax) + : base(syntax.Identifier.ValueText, containingSymbol, ImmutableArray.Create(syntax.Identifier.GetLocation()), isExtern: true) + { + _directive = syntax.GetReference(); + } + + /// + /// Gets the for the + /// namespace or type referenced by the alias. + /// + public override NamespaceOrTypeSymbol Target + { + get + { + return GetAliasTarget(basesBeingResolved: null); + } + } + + // basesBeingResolved is only used to break circular references. + internal override NamespaceOrTypeSymbol GetAliasTarget(ConsList? basesBeingResolved) { if (!_state.HasComplete(CompletionPart.AliasTarget)) { @@ -269,7 +321,7 @@ internal NamespaceOrTypeSymbol GetAliasTarget(ConsList? basesBeingRe NamespaceOrTypeSymbol symbol = this.IsExtern ? ResolveExternAliasTarget(newDiagnostics) : - ResolveAliasTarget(_aliasTargetName, newDiagnostics, basesBeingResolved); + ResolveAliasTarget(((UsingDirectiveSyntax)_directive.GetSyntax()).Name, newDiagnostics, basesBeingResolved); if ((object?)Interlocked.CompareExchange(ref _aliasTarget, symbol, null) == null) { @@ -303,23 +355,12 @@ internal BindingDiagnosticBag AliasTargetDiagnostics } } - internal void CheckConstraints(BindingDiagnosticBag diagnostics) - { - var target = this.Target as TypeSymbol; - if ((object?)target != null && _locations.Length > 0) - { - var corLibrary = this.ContainingAssembly.CorLibrary; - var conversions = new TypeConversions(corLibrary); - target.CheckAllConstraints(DeclaringCompilation, conversions, _locations[0], diagnostics); - } - } - private NamespaceSymbol ResolveExternAliasTarget(BindingDiagnosticBag diagnostics) { NamespaceSymbol? target; - if (!_binder.Compilation.GetExternAliasTarget(_aliasName.ValueText, out target)) + if (!ContainingSymbol!.DeclaringCompilation.GetExternAliasTarget(Name, out target)) { - diagnostics.Add(ErrorCode.ERR_BadExternAlias, _aliasName.GetLocation(), _aliasName.ValueText!); + diagnostics.Add(ErrorCode.ERR_BadExternAlias, Locations[0], Name); } RoslynDebug.Assert(target is object); @@ -328,47 +369,48 @@ private NamespaceSymbol ResolveExternAliasTarget(BindingDiagnosticBag diagnostic return target; } - private NamespaceOrTypeSymbol ResolveAliasTarget(NameSyntax? syntax, BindingDiagnosticBag diagnostics, ConsList? basesBeingResolved) + private NamespaceOrTypeSymbol ResolveAliasTarget(NameSyntax syntax, BindingDiagnosticBag diagnostics, ConsList? basesBeingResolved) { - var declarationBinder = _binder.WithAdditionalFlags(BinderFlags.SuppressConstraintChecks | BinderFlags.SuppressObsoleteChecks); + var declarationBinder = ContainingSymbol!.DeclaringCompilation.GetBinderFactory(syntax.SyntaxTree).GetBinder(syntax).WithAdditionalFlags(BinderFlags.SuppressConstraintChecks | BinderFlags.SuppressObsoleteChecks); return declarationBinder.BindNamespaceOrTypeSymbol(syntax, diagnostics, basesBeingResolved).NamespaceOrTypeSymbol; } - public override bool Equals(Symbol? obj, TypeCompareKind compareKind) + internal override bool RequiresCompletion { - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (ReferenceEquals(obj, null)) - { - return false; - } + get { return true; } + } + } - AliasSymbol? other = obj as AliasSymbol; + internal sealed class AliasSymbolFromResolvedTarget : AliasSymbol + { + private readonly NamespaceOrTypeSymbol _aliasTarget; - return (object?)other != null && - Equals(this.Locations.FirstOrDefault(), other.Locations.FirstOrDefault()) && - this.ContainingAssembly.Equals(other.ContainingAssembly, compareKind); + internal AliasSymbolFromResolvedTarget(NamespaceOrTypeSymbol target, string aliasName, Symbol? containingSymbol, ImmutableArray locations, bool isExtern) + : base(aliasName, containingSymbol, locations, isExtern) + { + _aliasTarget = target; } - public override int GetHashCode() + /// + /// Gets the for the + /// namespace or type referenced by the alias. + /// + public override NamespaceOrTypeSymbol Target { - if (this.Locations.Length > 0) - return this.Locations.First().GetHashCode(); - else - return Name.GetHashCode(); + get + { + return _aliasTarget; + } } - internal override bool RequiresCompletion + internal override NamespaceOrTypeSymbol GetAliasTarget(ConsList? basesBeingResolved) { - get { return true; } + return _aliasTarget; } - protected override ISymbol CreateISymbol() + internal override bool RequiresCompletion { - return new PublicModel.AliasSymbol(this); + get { return false; } } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.TypePublicSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.TypePublicSymbol.cs index 0d646b56d8d50..9547445b49564 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.TypePublicSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.TypePublicSymbol.cs @@ -329,6 +329,7 @@ internal override ImmutableArray GetDeclaredInterfaces(ConsList internal sealed override NamedTypeSymbol NativeIntegerUnderlyingType => null; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool Equals(TypeSymbol t2, TypeCompareKind comparison) { diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TemplateSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TemplateSymbol.cs index cec33a084a910..c0fa54d3730e1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TemplateSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TemplateSymbol.cs @@ -442,6 +442,8 @@ internal override AttributeUsageInfo GetAttributeUsageInfo() internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; + internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) { base.AddSynthesizedAttributes(moduleBuilder, ref attributes); diff --git a/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs index d8b3320ff0417..b6522f35a068b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs @@ -481,6 +481,8 @@ protected sealed override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnn internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; + /// /// Represents SZARRAY - zero-based one-dimensional array /// diff --git a/src/Compilers/CSharp/Portable/Symbols/ConstructedNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ConstructedNamedTypeSymbol.cs index d60ef4c6b348d..e3e2ee7990f20 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ConstructedNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ConstructedNamedTypeSymbol.cs @@ -27,6 +27,9 @@ internal SubstitutedNestedTypeSymbol(SubstitutedNamedTypeSymbol newContainer, Na { } + internal override bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo result, Symbol owner, ref HashSet checkedTypes) + => OriginalDefinition.GetUnificationUseSiteDiagnosticRecursive(ref result, owner, ref checkedTypes); + internal override ImmutableArray TypeArgumentsWithAnnotationsNoUseSiteDiagnostics { get { return GetTypeParametersAsTypeArguments(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs index d0cd05a8923d9..f58b18e71e109 100644 --- a/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs @@ -246,5 +246,7 @@ protected sealed override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnn } internal override bool IsRecord => false; + + internal override bool IsRecordStruct => false; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs b/src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs index a52ed21719a30..daee076481786 100644 --- a/src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs @@ -34,6 +34,7 @@ internal static TypeKind ToTypeKind(this DeclarationKind kind) return TypeKind.Interface; case DeclarationKind.Struct: + case DeclarationKind.RecordStruct: return TypeKind.Struct; default: diff --git a/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs index 91c9fce87fca3..2ad3086d7528d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs @@ -544,6 +544,7 @@ protected sealed override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnn } internal sealed override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal sealed override bool HasPossibleWellKnownCloneMethod() => false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerTypeSymbol.cs index 816eca0df9e59..c06a0a0acd1be 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerTypeSymbol.cs @@ -214,5 +214,7 @@ internal static bool IsCallingConventionModifier(NamedTypeSymbol modifierType) } internal override bool IsRecord => false; + + internal override bool IsRecordStruct => false; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs index 42763e0cbc05e..fecc94b66a8da 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs @@ -32,7 +32,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// consistent. /// /// - internal class MemberSignatureComparer : IEqualityComparer + internal sealed class MemberSignatureComparer : IEqualityComparer { /// /// This instance is used when trying to determine if one member explicitly implements another, @@ -287,6 +287,18 @@ internal class MemberSignatureComparer : IEqualityComparer considerRefKindDifferences: true, typeComparison: TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreDynamicAndTupleNames); + /// + /// Compare signatures of methods from a method group. + /// + internal static readonly MemberSignatureComparer MethodGroupSignatureComparer = new MemberSignatureComparer( + considerName: false, + considerExplicitlyImplementedInterfaces: false, + considerReturnType: true, + considerTypeConstraints: false, + considerRefKindDifferences: true, + considerCallingConvention: false, + typeComparison: TypeCompareKind.AllIgnoreOptions); + // Compare the "unqualified" part of the member name (no explicit part) private readonly bool _considerName; diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 42675e9d2b7e0..b8f298c26187a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -565,6 +565,9 @@ internal override bool IsRecord } } + // Record structs get erased when emitted to metadata + internal override bool IsRecordStruct => false; + public override Accessibility DeclaredAccessibility { get @@ -1084,7 +1087,6 @@ public int Compare(Symbol x, Symbol y) } } - private void EnsureEnumUnderlyingTypeIsLoaded(UncommonProperties uncommon) { if ((object)(uncommon.lazyEnumUnderlyingType) == null @@ -1958,7 +1960,6 @@ private static Dictionary> GroupByName return symbols.ToDictionary(s => s.Name, StringOrdinalComparer.Instance); } - internal override UseSiteInfo GetUseSiteInfo() { if (!_lazyCachedUseSiteInfo.IsInitialized) @@ -2255,8 +2256,6 @@ private AttributeUsageInfo DecodeAttributeUsageInfo() get { return null; } } - public IEnumerable fieldDefs { get; set; } - /// /// Returns the index of the first member of the specific kind. /// Returns the number of members if not found. diff --git a/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs index 7536f79854e32..28776f5395e56 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs @@ -70,7 +70,7 @@ internal override DiagnosticInfo ErrorInfo // Since we do not know what task was being performed, for now we just report a generic // "you must add a reference" error. - if (containingAssembly.IsMissing) + if (containingAssembly?.IsMissing == true) { // error CS0012: The type 'Blah' is defined in an assembly that is not referenced. You must add a reference to assembly 'Goo'. return new CSDiagnosticInfo(ErrorCode.ERR_NoTypeDef, this, containingAssembly.Identity); @@ -79,7 +79,7 @@ internal override DiagnosticInfo ErrorInfo { ModuleSymbol containingModule = this.ContainingModule; - if (containingModule.IsMissing) + if (containingModule?.IsMissing == true) { // It looks like required module wasn't added to the compilation. return new CSDiagnosticInfo(ErrorCode.ERR_NoTypeDefFromModule, this, containingModule.Name); @@ -94,25 +94,37 @@ internal override DiagnosticInfo ErrorInfo // NOTE: this is another case where we would like to base our decision on which compilation // is the "current" compilation, but we don't want to force consumers of the API to specify. - if (containingAssembly.Dangerous_IsFromSomeCompilation) + if (containingAssembly is object) { - // This scenario is quite tricky and involves a circular reference. Suppose we have - // assembly Alpha that has a type C. Assembly Beta refers to Alpha and uses type C. - // Now we create a new source assembly that replaces Alpha, and refers to Beta. - // The usage of C in Beta will be redirected to refer to the source assembly. - // If C is not in that source assembly then we give the following warning: - - // CS7068: Reference to type 'C' claims it is defined in this assembly, but it is not defined in source or any added modules - return new CSDiagnosticInfo(ErrorCode.ERR_MissingTypeInSource, this); + if (containingAssembly.Dangerous_IsFromSomeCompilation) + { + // This scenario is quite tricky and involves a circular reference. Suppose we have + // assembly Alpha that has a type C. Assembly Beta refers to Alpha and uses type C. + // Now we create a new source assembly that replaces Alpha, and refers to Beta. + // The usage of C in Beta will be redirected to refer to the source assembly. + // If C is not in that source assembly then we give the following warning: + + // CS7068: Reference to type 'C' claims it is defined in this assembly, but it is not defined in source or any added modules + return new CSDiagnosticInfo(ErrorCode.ERR_MissingTypeInSource, this); + } + else + { + // The more straightforward scenario is that we compiled Beta against a version of Alpha + // that had C, and then added a reference to a different version of Alpha that + // lacks the type C: + + // error CS7069: Reference to type 'C' claims it is defined in 'Alpha', but it could not be found + return new CSDiagnosticInfo(ErrorCode.ERR_MissingTypeInAssembly, this, containingAssembly.Name); + } + } + else if (ContainingType is ErrorTypeSymbol { ErrorInfo: { } info }) + { + return info; } else { - // The more straightforward scenario is that we compiled Beta against a version of Alpha - // that had C, and then added a reference to a different version of Alpha that - // lacks the type C: - - // error CS7069: Reference to type 'C' claims it is defined in 'Alpha', but it could not be found - return new CSDiagnosticInfo(ErrorCode.ERR_MissingTypeInAssembly, this, containingAssembly.Name); + // This is the best we can do at this point + return new CSDiagnosticInfo(ErrorCode.ERR_BogusType, string.Empty); } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs index 2a2cb900cad51..5360120f284e1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs @@ -345,11 +345,45 @@ public NamespaceSymbol GetModuleNamespace(INamespaceSymbol namespaceSymbol) throw new ArgumentNullException(nameof(namespaceSymbol)); } - var moduleNs = namespaceSymbol as NamespaceSymbol; - if ((object)moduleNs != null && moduleNs.Extent.Kind == NamespaceKind.Module && moduleNs.ContainingModule == this) + if (namespaceSymbol.NamespaceKind == NamespaceKind.Module) + { + var moduleNs = (namespaceSymbol as PublicModel.NamespaceSymbol)?.UnderlyingNamespaceSymbol; + if ((object)moduleNs != null && moduleNs.ContainingModule == this) + { + // this is already the correct module namespace + return moduleNs; + } + } + + if (namespaceSymbol.IsGlobalNamespace || (object)namespaceSymbol.ContainingNamespace == null) + { + return this.GlobalNamespace; + } + else + { + var cns = GetModuleNamespace(namespaceSymbol.ContainingNamespace); + if ((object)cns != null) + { + return cns.GetNestedNamespace(namespaceSymbol.Name); + } + return null; + } + } + + /// + /// Given a namespace symbol, returns the corresponding module specific namespace symbol + /// + public NamespaceSymbol GetModuleNamespace(NamespaceSymbol namespaceSymbol) + { + if (namespaceSymbol == null) + { + throw new ArgumentNullException(nameof(namespaceSymbol)); + } + + if (namespaceSymbol.Extent.Kind == NamespaceKind.Module && namespaceSymbol.ContainingModule == this) { // this is already the correct module namespace - return moduleNs; + return namespaceSymbol; } if (namespaceSymbol.IsGlobalNamespace || (object)namespaceSymbol.ContainingNamespace == null) diff --git a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs index e5c2b9678c774..6ccfcef201f1d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs @@ -164,6 +164,7 @@ internal override UseSiteInfo GetUseSiteInfo() internal sealed override NamedTypeSymbol NativeIntegerUnderlyingType => _underlyingType; internal sealed override bool IsRecord => false; + internal sealed override bool IsRecordStruct => false; internal sealed override bool HasPossibleWellKnownCloneMethod() => false; internal override bool Equals(TypeSymbol? other, TypeCompareKind comparison) diff --git a/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs index 8046e2384bd58..cdb060d1e39d1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs @@ -310,5 +310,7 @@ protected override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnnotation } internal override bool IsRecord => false; + + internal override bool IsRecordStruct => false; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/TypeSymbol.cs index 5b63a486582ca..476a5444dc0df 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/TypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/TypeSymbol.cs @@ -172,6 +172,6 @@ ImmutableArray ITypeSymbol.ToMinimalDisplayParts(SemanticMode bool ITypeSymbol.IsReadOnly => UnderlyingTypeSymbol.IsReadOnly; - bool ITypeSymbol.IsRecord => UnderlyingTypeSymbol.IsRecord; + bool ITypeSymbol.IsRecord => UnderlyingTypeSymbol.IsRecord || UnderlyingTypeSymbol.IsRecordStruct; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs index d4ed3f7489b52..211e8ba1ce1d3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs @@ -392,6 +392,7 @@ public sealed override bool AreLocalsZeroed internal sealed override NamedTypeSymbol NativeIntegerUnderlyingType => null; internal sealed override bool IsRecord => _underlyingType.IsRecord; + internal sealed override bool IsRecordStruct => _underlyingType.IsRecordStruct; internal sealed override bool HasPossibleWellKnownCloneMethod() => _underlyingType.HasPossibleWellKnownCloneMethod(); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs new file mode 100644 index 0000000000000..65173f0a90d00 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class LambdaParameterSymbol : SourceComplexParameterSymbol + { + private readonly SyntaxList _attributeLists; + + public LambdaParameterSymbol( + LambdaSymbol owner, + SyntaxList attributeLists, + TypeWithAnnotations parameterType, + int ordinal, + RefKind refKind, + string name, + bool isDiscard, + ImmutableArray locations) + : base(owner, ordinal, parameterType, refKind, name, locations, syntaxRef: null, isParams: false, isExtensionMethodThis: false) + { + _attributeLists = attributeLists; + IsDiscard = isDiscard; + } + + public override bool IsDiscard { get; } + + internal override bool IsMetadataOptional + { + get { return false; } + } + + public override bool IsParams + { + get { return false; } + } + + internal override bool HasDefaultArgumentSyntax + { + get { return false; } + } + + public override ImmutableArray RefCustomModifiers + { + get { return ImmutableArray.Empty; } + } + + internal override bool IsExtensionMethodThis + { + get { return false; } + } + + internal override OneOrMany> GetAttributeDeclarations() => OneOrMany.Create(_attributeLists); + } +} + diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs index 508b7bf45294b..5a19816808171 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs @@ -2,9 +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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -13,17 +10,18 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { - internal sealed class LambdaSymbol : SourceMethodSymbol + internal sealed class LambdaSymbol : SourceMethodSymbolWithAttributes { + private readonly Binder _binder; private readonly Symbol _containingSymbol; private readonly MessageID _messageID; - private readonly SyntaxNode _syntax; private readonly ImmutableArray _parameters; private RefKind _refKind; private TypeWithAnnotations _returnType; private readonly bool _isSynthesized; private readonly bool _isAsync; private readonly bool _isStatic; + private readonly BindingDiagnosticBag _declarationDiagnostics; /// /// This symbol is used as the return type of a LambdaSymbol when we are interpreting @@ -36,20 +34,21 @@ internal sealed class LambdaSymbol : SourceMethodSymbol /// internal static readonly TypeSymbol InferenceFailureReturnType = new UnsupportedMetadataTypeSymbol(); - private static readonly TypeWithAnnotations UnknownReturnType = TypeWithAnnotations.Create(ErrorTypeSymbol.UnknownResultType); - public LambdaSymbol( + Binder binder, CSharpCompilation compilation, Symbol containingSymbol, UnboundLambda unboundLambda, ImmutableArray parameterTypes, ImmutableArray parameterRefKinds, RefKind refKind, - TypeWithAnnotations returnType) + TypeWithAnnotations returnType) : + base(unboundLambda.Syntax.GetReference()) { + Debug.Assert(syntaxReferenceOpt is not null); + _binder = binder; _containingSymbol = containingSymbol; _messageID = unboundLambda.Data.MessageID; - _syntax = unboundLambda.Syntax; _refKind = refKind; _returnType = !returnType.HasType ? TypeWithAnnotations.Create(ReturnTypeIsBeingInferred) : returnType; _isSynthesized = unboundLambda.WasCompilerGenerated; @@ -57,6 +56,7 @@ public LambdaSymbol( _isStatic = unboundLambda.IsStatic; // No point in making this lazy. We are always going to need these soon after creation of the symbol. _parameters = MakeParameters(compilation, unboundLambda, parameterTypes, parameterRefKinds); + _declarationDiagnostics = new BindingDiagnosticBag(); } public MessageID MessageID { get { return _messageID; } } @@ -98,13 +98,6 @@ public override bool IsAsync get { return _isAsync; } } - internal sealed override ObsoleteAttributeData ObsoleteAttributeData - { - get { return null; } - } - - internal sealed override UnmanagedCallersOnlyAttributeData GetUnmanagedCallersOnlyAttributeData(bool forceComplete) => null; - internal sealed override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) { return false; @@ -138,41 +131,6 @@ internal override System.Reflection.MethodImplAttributes ImplementationAttribute get { return default(System.Reflection.MethodImplAttributes); } } - internal override bool RequiresSecurityObject - { - get { return false; } - } - - public override DllImportData GetDllImportData() - { - return null; - } - - public override bool AreLocalsZeroed - { - get { return AreContainingSymbolLocalsZeroed; } - } - - internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation - { - get { return null; } - } - - internal override bool HasDeclarativeSecurity - { - get { return false; } - } - - internal override IEnumerable GetSecurityInformation() - { - throw ExceptionUtilities.Unreachable; - } - - internal override ImmutableArray GetAppliedConditionalSymbols() - { - return ImmutableArray.Empty; - } - public override bool ReturnsVoid { get { return this.ReturnTypeWithAnnotations.HasType && this.ReturnType.IsVoidType(); } @@ -188,12 +146,6 @@ public override TypeWithAnnotations ReturnTypeWithAnnotations get { return _returnType; } } - public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; - - public override ImmutableHashSet ReturnNotNullIfParameterNotNull => ImmutableHashSet.Empty; - - public override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; - // In error recovery and type inference scenarios we do not know the return type // until after the body is bound, but the symbol is created before the body // is bound. Fill in the return type post hoc in these scenarios; the @@ -221,7 +173,7 @@ public override ImmutableArray ExplicitInterfaceImplementations get { return ImmutableArray.Empty; } } - public override Symbol AssociatedSymbol + public override Symbol? AssociatedSymbol { get { return null; } } @@ -246,7 +198,7 @@ public override ImmutableArray Parameters get { return _parameters; } } - internal override bool TryGetThisParameter(out ParameterSymbol thisParameter) + internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter) { // Lambda symbols have no "this" parameter thisParameter = null; @@ -262,7 +214,7 @@ public override ImmutableArray Locations { get { - return ImmutableArray.Create(_syntax.Location); + return ImmutableArray.Create(Syntax.Location); } } @@ -274,16 +226,12 @@ internal Location DiagnosticLocation { get { - switch (_syntax.Kind()) + return Syntax switch { - case SyntaxKind.AnonymousMethodExpression: - return ((AnonymousMethodExpressionSyntax)_syntax).DelegateKeyword.GetLocation(); - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.ParenthesizedLambdaExpression: - return ((LambdaExpressionSyntax)_syntax).ArrowToken.GetLocation(); - default: - return Locations[0]; - } + AnonymousMethodExpressionSyntax syntax => syntax.DelegateKeyword.GetLocation(), + LambdaExpressionSyntax syntax => syntax.ArrowToken.GetLocation(), + _ => Locations[0] + }; } } @@ -291,7 +239,7 @@ public override ImmutableArray DeclaringSyntaxReferences { get { - return ImmutableArray.Create(_syntax.GetReference()); + return ImmutableArray.Create(syntaxReferenceOpt); } } @@ -310,11 +258,35 @@ public override bool IsExtensionMethod get { return false; } } - public override bool HidesBaseMethodsByName + internal SyntaxNode Syntax => syntaxReferenceOpt.GetSyntax(); + + internal override Binder SignatureBinder => _binder; + + internal override Binder ParameterBinder => new WithLambdaParametersBinder(this, _binder); + + internal override OneOrMany> GetAttributeDeclarations() { - get { return false; } + return Syntax is LambdaExpressionSyntax lambdaSyntax ? + OneOrMany.Create(lambdaSyntax.AttributeLists) : + default; } + internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo) + { + foreach (var parameter in _parameters) + { + parameter.ForceComplete(locationOpt: null, cancellationToken: default); + } + + GetAttributes(); + GetReturnTypeAttributes(); + + addTo.AddRange(_declarationDiagnostics, allowMismatchInDependencyAccumulation: true); + } + + internal override void AddDeclarationDiagnostics(BindingDiagnosticBag diagnostics) + => _declarationDiagnostics.AddRange(diagnostics); + private ImmutableArray MakeParameters( CSharpCompilation compilation, UnboundLambda unboundLambda, @@ -366,11 +338,12 @@ private ImmutableArray MakeParameters( refKind = RefKind.None; } + var attributeLists = unboundLambda.ParameterAttributes(p); var name = unboundLambda.ParameterName(p); var location = unboundLambda.ParameterLocation(p); var locations = location == null ? ImmutableArray.Empty : ImmutableArray.Create(location); - var parameter = new SourceSimpleParameterSymbol(owner: this, type, ordinal: p, refKind, name, unboundLambda.ParameterIsDiscard(p), locations); + var parameter = new LambdaParameterSymbol(owner: this, attributeLists, type, ordinal: p, refKind, name, unboundLambda.ParameterIsDiscard(p), locations); builder.Add(parameter); } @@ -385,17 +358,22 @@ public sealed override bool Equals(Symbol symbol, TypeCompareKind compareKind) if ((object)this == symbol) return true; return symbol is LambdaSymbol lambda - && lambda._syntax == _syntax + && areEqual(lambda.syntaxReferenceOpt, syntaxReferenceOpt) && lambda._refKind == _refKind && TypeSymbol.Equals(lambda.ReturnType, this.ReturnType, compareKind) && ParameterTypesWithAnnotations.SequenceEqual(lambda.ParameterTypesWithAnnotations, compareKind, (p1, p2, compareKind) => p1.Equals(p2, compareKind)) && lambda.ContainingSymbol.Equals(ContainingSymbol, compareKind); + + static bool areEqual(SyntaxReference a, SyntaxReference b) + { + return (object)a.SyntaxTree == b.SyntaxTree && a.Span == b.Span; + } } public override int GetHashCode() { - return _syntax.GetHashCode(); + return syntaxReferenceOpt.GetHashCode(); } public override bool IsImplicitlyDeclared @@ -425,5 +403,9 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l } internal override bool IsNullableAnalysisEnabled() => throw ExceptionUtilities.Unreachable; + + protected override void NoteAttributesComplete(bool forReturnType) + { + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs index 3693bd309ef1f..3fa669b4b5ac2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs @@ -105,7 +105,7 @@ public LocalFunctionSymbol( /// internal Binder ScopeBinder { get; } - public Binder ParameterBinder => _binder; + internal override Binder ParameterBinder => _binder; internal LocalFunctionStatementSyntax Syntax => (LocalFunctionStatementSyntax)syntaxReferenceOpt.GetSyntax(); @@ -330,7 +330,7 @@ internal override TypeWithAnnotations IteratorElementTypeWithAnnotations public SyntaxToken NameToken => Syntax.Identifier; - public Binder SignatureBinder => _binder; + internal override Binder SignatureBinder => _binder; public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs index fd97bfd16563c..1d162213df4cd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs @@ -208,8 +208,6 @@ private static string ConvertSingleModifierToSyntaxText(DeclarationModifiers mod return SyntaxFacts.GetText(SyntaxKind.AsyncKeyword); case DeclarationModifiers.Ref: return SyntaxFacts.GetText(SyntaxKind.RefKeyword); - case DeclarationModifiers.Data: - return SyntaxFacts.GetText(SyntaxKind.DataKeyword); default: throw ExceptionUtilities.UnexpectedValue(modifier); } @@ -257,8 +255,6 @@ private static DeclarationModifiers ToDeclarationModifier(SyntaxKind kind) return DeclarationModifiers.Volatile; case SyntaxKind.RefKeyword: return DeclarationModifiers.Ref; - case SyntaxKind.DataKeyword: - return DeclarationModifiers.Data; default: throw ExceptionUtilities.UnexpectedValue(kind); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index ef175e1914940..a31c1ffbba242 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -54,8 +53,7 @@ public static ImmutableArray MakeParameters( isExtensionMethodThis: ordinal == 0 && thisKeyword.Kind() != SyntaxKind.None, addRefReadOnlyModifier, declarationDiagnostics); - } -); + }); } public static ImmutableArray MakeFunctionPointerParameters( diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/QuickAttributeChecker.cs b/src/Compilers/CSharp/Portable/Symbols/Source/QuickAttributeChecker.cs index 872c01daae467..ddd2376eb8bcd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/QuickAttributeChecker.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/QuickAttributeChecker.cs @@ -79,7 +79,7 @@ private void AddName(string name, QuickAttributes newAttributes) _nameToAttributeMap[name] = newValue; } - internal QuickAttributeChecker AddAliasesIfAny(SyntaxList usingsSyntax) + internal QuickAttributeChecker AddAliasesIfAny(SyntaxList usingsSyntax, bool onlyGlobalAliases = false) { if (usingsSyntax.Count == 0) { @@ -90,7 +90,7 @@ internal QuickAttributeChecker AddAliasesIfAny(SyntaxList foreach (var usingDirective in usingsSyntax) { - if (usingDirective.Alias != null) + if (usingDirective.Alias != null && (!onlyGlobalAliases || usingDirective.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword))) { string name = usingDirective.Alias.Name.Identifier.ValueText; string target = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 99581fe003e32..794cf7baeeadf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -49,7 +48,6 @@ internal SourceComplexParameterSymbol( : base(owner, parameterType, ordinal, refKind, name, locations) { Debug.Assert((syntaxRef == null) || (syntaxRef.GetSyntax().IsKind(SyntaxKind.Parameter))); - Debug.Assert(!(owner is LambdaSymbol)); // therefore we're not dealing with discard parameters _lazyHasOptionalAttribute = ThreeState.Unknown; _syntaxRef = syntaxRef; @@ -73,15 +71,15 @@ internal SourceComplexParameterSymbol( _lazyDefaultSyntaxValue = ConstantValue.Unset; } - private Binder ParameterBinderOpt => (ContainingSymbol as LocalFunctionSymbol)?.ParameterBinder; + private Binder ParameterBinderOpt => (ContainingSymbol as SourceMethodSymbolWithAttributes)?.ParameterBinder; - internal override SyntaxReference SyntaxReference => _syntaxRef; + internal sealed override SyntaxReference SyntaxReference => _syntaxRef; - internal ParameterSyntax CSharpSyntaxNode => (ParameterSyntax)_syntaxRef?.GetSyntax(); + private ParameterSyntax CSharpSyntaxNode => (ParameterSyntax)_syntaxRef?.GetSyntax(); - public sealed override bool IsDiscard => false; + public override bool IsDiscard => false; - internal override ConstantValue ExplicitDefaultConstantValue + internal sealed override ConstantValue ExplicitDefaultConstantValue { get { @@ -99,7 +97,7 @@ internal override ConstantValue ExplicitDefaultConstantValue } } - internal override ConstantValue DefaultValueFromAttributes + internal sealed override ConstantValue DefaultValueFromAttributes { get { @@ -108,7 +106,7 @@ internal override ConstantValue DefaultValueFromAttributes } } - internal override bool IsIDispatchConstant + internal sealed override bool IsIDispatchConstant => GetDecodedWellKnownAttributeData()?.HasIDispatchConstantAttribute == true; internal override bool IsIUnknownConstant @@ -123,11 +121,11 @@ private bool HasCallerFilePathAttribute private bool HasCallerMemberNameAttribute => GetEarlyDecodedWellKnownAttributeData()?.HasCallerMemberNameAttribute == true; - internal override bool IsCallerLineNumber => HasCallerLineNumberAttribute; + internal sealed override bool IsCallerLineNumber => HasCallerLineNumberAttribute; - internal override bool IsCallerFilePath => !HasCallerLineNumberAttribute && HasCallerFilePathAttribute; + internal sealed override bool IsCallerFilePath => !HasCallerLineNumberAttribute && HasCallerFilePathAttribute; - internal override bool IsCallerMemberName => !HasCallerLineNumberAttribute + internal sealed override bool IsCallerMemberName => !HasCallerLineNumberAttribute && !HasCallerFilePathAttribute && HasCallerMemberNameAttribute; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs index a0e05f31f5354..1c35bb314b9a9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs @@ -27,7 +27,8 @@ protected SourceConstructorSymbolBase( { Debug.Assert( syntax.IsKind(SyntaxKind.ConstructorDeclaration) || - syntax.IsKind(SyntaxKind.RecordDeclaration)); + syntax.IsKind(SyntaxKind.RecordDeclaration) || + syntax.IsKind(SyntaxKind.RecordStructDeclaration)); } protected sealed override void MethodChecks(BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFixedFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFixedFieldSymbol.cs index 2c3816d0fe93e..dfc78d31c7cd5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFixedFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFixedFieldSymbol.cs @@ -240,6 +240,7 @@ public sealed override bool AreLocalsZeroed => throw ExceptionUtilities.Unreachable; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index bf44e77a5e295..14e882e2d0feb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -275,7 +275,13 @@ private DeclarationModifiers MakeModifiers(TypeKind typeKind, BindingDiagnosticB break; case TypeKind.Struct: - allowedModifiers |= DeclarationModifiers.Partial | DeclarationModifiers.Ref | DeclarationModifiers.ReadOnly | DeclarationModifiers.Unsafe; + allowedModifiers |= DeclarationModifiers.Partial | DeclarationModifiers.ReadOnly | DeclarationModifiers.Unsafe; + + if (!this.IsRecordStruct) + { + allowedModifiers |= DeclarationModifiers.Ref; + } + break; case TypeKind.Interface: allowedModifiers |= DeclarationModifiers.Partial | DeclarationModifiers.Unsafe; @@ -826,6 +832,14 @@ internal override bool IsRecord } } + internal override bool IsRecordStruct + { + get + { + return this.declaration.Declarations[0].Kind == DeclarationKind.RecordStruct; + } + } + public override bool IsImplicitlyDeclared { get @@ -1148,7 +1162,7 @@ public override IEnumerable MemberNames { get { - return (IsTupleType || IsRecord) ? GetMembers().Select(m => m.Name) : this.declaration.MemberNames; + return (IsTupleType || IsRecord || IsRecordStruct) ? GetMembers().Select(m => m.Name) : this.declaration.MemberNames; } } @@ -1327,7 +1341,7 @@ internal override bool HasPossibleWellKnownCloneMethod() internal override ImmutableArray GetSimpleNonTypeMembers(string name) { - if (_lazyMembersDictionary != null || declaration.MemberNames.Contains(name) || declaration.Kind == DeclarationKind.Record) + if (_lazyMembersDictionary != null || declaration.MemberNames.Contains(name) || declaration.Kind is DeclarationKind.Record or DeclarationKind.RecordStruct) { return GetMembers(name); } @@ -1478,32 +1492,60 @@ internal void AssertMemberExposure(Symbol member, bool forDiagnostics = false) if (member is NamedTypeSymbol type) { - Debug.Assert(forDiagnostics); - Debug.Assert(Volatile.Read(ref _lazyTypeMembers)?.Values.Any(types => types.Contains(t => t == (object)type)) == true); + RoslynDebug.AssertOrFailFast(forDiagnostics); + RoslynDebug.AssertOrFailFast(Volatile.Read(ref _lazyTypeMembers)?.Values.Any(types => types.Contains(t => t == (object)type)) == true); return; } else if (member is TypeParameterSymbol || member is SynthesizedMethodBaseSymbol) { - Debug.Assert(forDiagnostics); + RoslynDebug.AssertOrFailFast(forDiagnostics); return; } else if (member is FieldSymbol field && field.AssociatedSymbol is EventSymbol e) { - Debug.Assert(forDiagnostics); + RoslynDebug.AssertOrFailFast(forDiagnostics); // Backing fields for field-like events are not added to the members list. member = e; } - var declared = Volatile.Read(ref _lazyDeclaredMembersAndInitializers); - Debug.Assert(declared != DeclaredMembersAndInitializers.UninitializedSentinel); + var membersAndInitializers = Volatile.Read(ref _lazyMembersAndInitializers); - if ((declared is object && (declared.NonTypeMembers.Contains(m => m == (object)member) || declared.RecordPrimaryConstructor == (object)member)) || - Volatile.Read(ref _lazyMembersAndInitializers)?.NonTypeMembers.Contains(m => m == (object)member) == true) + if (isMemberInCompleteMemberList(membersAndInitializers, member)) { return; } - Debug.Assert(false, "Premature symbol exposure."); + if (membersAndInitializers is null) + { + var declared = Volatile.Read(ref _lazyDeclaredMembersAndInitializers); + RoslynDebug.AssertOrFailFast(declared != DeclaredMembersAndInitializers.UninitializedSentinel); + + if (declared is object) + { + if (declared.NonTypeMembers.Contains(m => m == (object)member) || declared.RecordPrimaryConstructor == (object)member) + { + return; + } + } + else + { + // It looks like there was a race and we need to check _lazyMembersAndInitializers again + membersAndInitializers = Volatile.Read(ref _lazyMembersAndInitializers); + RoslynDebug.AssertOrFailFast(membersAndInitializers is object); + + if (isMemberInCompleteMemberList(membersAndInitializers, member)) + { + return; + } + } + } + + RoslynDebug.AssertOrFailFast(false, "Premature symbol exposure."); + + static bool isMemberInCompleteMemberList(MembersAndInitializers? membersAndInitializers, Symbol member) + { + return membersAndInitializers?.NonTypeMembers.Contains(m => m == (object)member) == true; + } } protected Dictionary> GetMembersByName() @@ -1634,7 +1676,8 @@ private void CheckMemberNamesDistinctFromType(BindingDiagnosticBag diagnostics) private void CheckRecordMemberNames(BindingDiagnosticBag diagnostics) { - if (declaration.Kind != DeclarationKind.Record) + if (declaration.Kind != DeclarationKind.Record && + declaration.Kind != DeclarationKind.RecordStruct) { return; } @@ -2242,7 +2285,7 @@ private void CheckForEqualityAndGetHashCode(BindingDiagnosticBag diagnostics) return; } - if (IsRecord) + if (IsRecord || IsRecordStruct) { // For records the warnings reported below are simply going to echo record specific errors, // producing more noise. @@ -2470,6 +2513,7 @@ private sealed class DeclaredMembersAndInitializersBuilder public readonly ArrayBuilder> InstanceInitializers = ArrayBuilder>.GetInstance(); public bool HaveIndexers; public RecordDeclarationSyntax? RecordDeclarationWithParameters; + public SynthesizedRecordConstructor? RecordPrimaryConstructor; public bool IsNullableEnabledForInstanceConstructorsAndFields; public bool IsNullableEnabledForStaticConstructorsAndFields; @@ -2929,9 +2973,19 @@ private void AddDeclaredNontypeMembers(DeclaredMembersAndInitializersBuilder bui break; case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: var recordDecl = (RecordDeclarationSyntax)syntax; - noteRecordParameters(recordDecl, builder, diagnostics); + var parameterList = recordDecl.ParameterList; + noteRecordParameters(recordDecl, parameterList, builder, diagnostics); AddNonTypeMembers(builder, recordDecl.Members, diagnostics); + + // We will allow declaring parameterless constructors + // Tracking issue https://github.com/dotnet/roslyn/issues/52240 + if (syntax.Kind() == SyntaxKind.RecordStructDeclaration && parameterList?.ParameterCount == 0) + { + diagnostics.Add(ErrorCode.ERR_StructsCantContainDefaultConstructor, parameterList.Location); + } + break; default: @@ -2939,9 +2993,8 @@ private void AddDeclaredNontypeMembers(DeclaredMembersAndInitializersBuilder bui } } - void noteRecordParameters(RecordDeclarationSyntax syntax, DeclaredMembersAndInitializersBuilder builder, BindingDiagnosticBag diagnostics) + void noteRecordParameters(RecordDeclarationSyntax syntax, ParameterListSyntax? parameterList, DeclaredMembersAndInitializersBuilder builder, BindingDiagnosticBag diagnostics) { - var parameterList = syntax.ParameterList; if (parameterList is null) { return; @@ -2954,8 +3007,8 @@ void noteRecordParameters(RecordDeclarationSyntax syntax, DeclaredMembersAndInit builder.RecordPrimaryConstructor = ctor; var compilation = DeclaringCompilation; - builder.UpdateIsNullableEnabledForConstructorsAndFields(ctor.IsStatic, compilation, syntax.ParameterList); - if (syntax.PrimaryConstructorBaseType?.ArgumentList is { } baseParamList) + builder.UpdateIsNullableEnabledForConstructorsAndFields(ctor.IsStatic, compilation, parameterList); + if (syntax is { PrimaryConstructorBaseTypeIfClass: { ArgumentList: { } baseParamList } }) { builder.UpdateIsNullableEnabledForConstructorsAndFields(ctor.IsStatic, compilation, baseParamList); } @@ -3388,6 +3441,13 @@ private void CheckForStructBadInitializers(DeclaredMembersAndInitializersBuilder { Debug.Assert(TypeKind == TypeKind.Struct); + if (builder.RecordDeclarationWithParameters is not null) + { + Debug.Assert(builder.RecordDeclarationWithParameters is RecordDeclarationSyntax { ParameterList: not null } record + && record.Kind() == SyntaxKind.RecordStructDeclaration); + return; + } + foreach (var initializers in builder.InstanceInitializers) { foreach (FieldOrPropertyInitializer initializer in initializers) @@ -3400,24 +3460,32 @@ private void CheckForStructBadInitializers(DeclaredMembersAndInitializersBuilder private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilder builder, DeclaredMembersAndInitializers declaredMembersAndInitializers, BindingDiagnosticBag diagnostics) { - if (declaration.Kind != DeclarationKind.Record) + if (declaration.Kind is not (DeclarationKind.Record or DeclarationKind.RecordStruct)) { return; } ParameterListSyntax? paramList = declaredMembersAndInitializers.RecordDeclarationWithParameters?.ParameterList; - var memberSignatures = s_duplicateRecordMemberSignatureDictionary.Allocate(); + var fieldsByName = PooledDictionary.GetInstance(); var membersSoFar = builder.GetNonTypeMembers(declaredMembersAndInitializers); var members = ArrayBuilder.GetInstance(membersSoFar.Count + 1); + var memberNames = PooledHashSet.GetInstance(); foreach (var member in membersSoFar) { + memberNames.Add(member.Name); + switch (member) { - case FieldSymbol: case EventSymbol: case MethodSymbol { MethodKind: not (MethodKind.Ordinary or MethodKind.Constructor) }: continue; + case FieldSymbol { Name: var fieldName }: + if (!fieldsByName.ContainsKey(fieldName)) + { + fieldsByName.Add(fieldName, member); + } + continue; } if (!memberSignatures.ContainsKey(member)) @@ -3427,6 +3495,7 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde } CSharpCompilation compilation = this.DeclaringCompilation; + bool isRecordClass = declaration.Kind == DeclarationKind.Record; // Positional record bool primaryAndCopyCtorAmbiguity = false; @@ -3434,26 +3503,39 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde { Debug.Assert(declaredMembersAndInitializers.RecordDeclarationWithParameters is object); + // primary ctor var ctor = declaredMembersAndInitializers.RecordPrimaryConstructor; Debug.Assert(ctor is object); members.Add(ctor); if (ctor.ParameterCount != 0) { + // properties and Deconstruct var existingOrAddedMembers = addProperties(ctor.Parameters); addDeconstruct(ctor, existingOrAddedMembers); } - primaryAndCopyCtorAmbiguity = ctor.ParameterCount == 1 && ctor.Parameters[0].Type.Equals(this, TypeCompareKind.AllIgnoreOptions); + if (isRecordClass) + { + primaryAndCopyCtorAmbiguity = ctor.ParameterCount == 1 && ctor.Parameters[0].Type.Equals(this, TypeCompareKind.AllIgnoreOptions); + } } - addCopyCtor(primaryAndCopyCtorAmbiguity); - addCloneMethod(); + if (isRecordClass) + { + addCopyCtor(primaryAndCopyCtorAmbiguity); + addCloneMethod(); + } - PropertySymbol equalityContract = addEqualityContract(); + PropertySymbol? equalityContract = isRecordClass ? addEqualityContract() : null; var thisEquals = addThisEquals(equalityContract); - addOtherEquals(); + + if (isRecordClass) + { + addBaseEquals(); + } + addObjectEquals(thisEquals); var getHashCode = addGetHashCode(equalityContract); addEqualityOperators(); @@ -3467,6 +3549,8 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde addToStringMethod(printMembers); memberSignatures.Free(); + fieldsByName.Free(); + memberNames.Free(); // We put synthesized record members first so that errors about conflicts show up on user-defined members rather than all // going to the record declaration @@ -3476,8 +3560,10 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde return; - void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray properties) + void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray positionalMembers) { + Debug.Assert(positionalMembers.All(p => p is PropertySymbol or FieldSymbol)); + var targetMethod = new SignatureOnlyMethodSymbol( WellKnownMemberNames.DeconstructMethodName, this, @@ -3497,7 +3583,7 @@ void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray addProperties(ImmutableArray recordParameters) + ImmutableArray addProperties(ImmutableArray recordParameters) { - var existingOrAddedMembers = ArrayBuilder.GetInstance(recordParameters.Length); + var existingOrAddedMembers = ArrayBuilder.GetInstance(recordParameters.Length); int addedCount = 0; foreach (ParameterSymbol param in recordParameters) { @@ -3713,16 +3801,29 @@ ImmutableArray addProperties(ImmutableArray rec ImmutableArray.Empty, isStatic: false, ImmutableArray.Empty); - - if (!memberSignatures.TryGetValue(targetProperty, out var existingMember)) + if (!memberSignatures.TryGetValue(targetProperty, out var existingMember) + && !fieldsByName.TryGetValue(param.Name, out existingMember)) { existingMember = OverriddenOrHiddenMembersHelpers.FindFirstHiddenMemberIfAny(targetProperty, memberIsFromSomeCompilation: true); isInherited = true; } + + // There should be an error if we picked a member that is hidden + // This will be fixed in C# 9 as part of 16.10. Tracked by https://github.com/dotnet/roslyn/issues/52630 + if (existingMember is null) { addProperty(new SynthesizedRecordPropertySymbol(this, syntax, param, isOverride: false, diagnostics)); } + else if (existingMember is FieldSymbol { IsStatic: false } field + && field.TypeWithAnnotations.Equals(param.TypeWithAnnotations, TypeCompareKind.AllIgnoreOptions)) + { + Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeaturePositionalFieldsInRecords, diagnostics); + if (!isInherited || checkMemberNotHidden(field, param)) + { + existingOrAddedMembers.Add(field); + } + } else if (existingMember is PropertySymbol { IsStatic: false, GetMethod: { } } prop && prop.TypeWithAnnotations.Equals(param.TypeWithAnnotations, TypeCompareKind.AllIgnoreOptions)) { @@ -3731,7 +3832,7 @@ ImmutableArray addProperties(ImmutableArray rec { addProperty(new SynthesizedRecordPropertySymbol(this, syntax, param, isOverride: true, diagnostics)); } - else + else if (!isInherited || checkMemberNotHidden(prop, param)) { // Deconstruct() is specified to simply assign from this property to the corresponding out parameter. existingOrAddedMembers.Add(prop); @@ -3762,6 +3863,16 @@ void addProperty(SynthesizedRecordPropertySymbol property) } return existingOrAddedMembers.ToImmutableAndFree(); + + bool checkMemberNotHidden(Symbol symbol, ParameterSymbol param) + { + if (memberNames.Contains(symbol.Name) || this.GetTypeMembersDictionary().ContainsKey(symbol.Name)) + { + diagnostics.Add(ErrorCode.ERR_HiddenPositionalMember, param.Locations[0], symbol); + return false; + } + return true; + } } void addObjectEquals(MethodSymbol thisEquals) @@ -3769,7 +3880,7 @@ void addObjectEquals(MethodSymbol thisEquals) members.Add(new SynthesizedRecordObjEquals(this, thisEquals, memberOffset: members.Count, diagnostics)); } - MethodSymbol addGetHashCode(PropertySymbol equalityContract) + MethodSymbol addGetHashCode(PropertySymbol? equalityContract) { var targetMethod = new SignatureOnlyMethodSymbol( WellKnownMemberNames.ObjectGetHashCode, @@ -3804,6 +3915,7 @@ MethodSymbol addGetHashCode(PropertySymbol equalityContract) PropertySymbol addEqualityContract() { + Debug.Assert(isRecordClass); var targetProperty = new SignatureOnlyPropertySymbol(SynthesizedRecordEqualityContractProperty.PropertyName, this, ImmutableArray.Empty, @@ -3860,7 +3972,7 @@ PropertySymbol addEqualityContract() return equalityContract; } - MethodSymbol addThisEquals(PropertySymbol equalityContract) + MethodSymbol addThisEquals(PropertySymbol? equalityContract) { var targetMethod = new SignatureOnlyMethodSymbol( WellKnownMemberNames.ObjectEquals, @@ -3909,7 +4021,8 @@ MethodSymbol addThisEquals(PropertySymbol equalityContract) void reportStaticOrNotOverridableAPIInRecord(Symbol symbol, BindingDiagnosticBag diagnostics) { - if (!IsSealed && + if (isRecordClass && + !IsSealed && ((!symbol.IsAbstract && !symbol.IsVirtual && !symbol.IsOverride) || symbol.IsSealed)) { diagnostics.Add(ErrorCode.ERR_NotOverridableAPIInRecord, symbol.Locations[0], symbol); @@ -3920,8 +4033,9 @@ void reportStaticOrNotOverridableAPIInRecord(Symbol symbol, BindingDiagnosticBag } } - void addOtherEquals() + void addBaseEquals() { + Debug.Assert(isRecordClass); if (!BaseTypeNoUseSiteDiagnostics.IsObjectType()) { members.Add(new SynthesizedRecordBaseEquals(this, memberOffset: members.Count, diagnostics)); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs index 1ff781f135864..1699f0c182ab8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs @@ -1261,7 +1261,8 @@ TypeWithAnnotations getNotNullIfNotNullOutputType(TypeWithAnnotations outputType for (var i = 0; i < baseParameters.Length; i++) { var overrideParam = overrideParameters[i + overrideParameterOffset]; - if (notNullIfParameterNotNull.Contains(overrideParam.Name) && !baseParameters[i].TypeWithAnnotations.NullableAnnotation.IsAnnotated()) + var baseParam = baseParameters[i]; + if (notNullIfParameterNotNull.Contains(overrideParam.Name) && NullableWalker.GetParameterState(baseParam.TypeWithAnnotations, baseParam.FlowAnalysisAnnotations).IsNotNull) { return outputType.AsNotAnnotated(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index cd7e8f403a2a0..503c3ffdaf5e6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -56,11 +56,17 @@ protected SourceMethodSymbolWithAttributes(SyntaxReference syntaxReferenceOpt) case CompilationUnitSyntax _ when this is SynthesizedSimpleProgramEntryPointSymbol entryPoint: return (CSharpSyntaxNode)entryPoint.ReturnTypeSyntax; case RecordDeclarationSyntax recordDecl: + Debug.Assert(recordDecl.IsKind(SyntaxKind.RecordDeclaration)); return recordDecl; default: return null; } } + + internal virtual Binder? SignatureBinder => null; + + internal virtual Binder? ParameterBinder => null; + #nullable disable internal SyntaxReference SyntaxRef @@ -278,7 +284,7 @@ private CustomAttributesBag GetAttributesBag(ref CustomAttr declarations, ref lazyCustomAttributesBag, symbolPart, - binderOpt: (this as LocalFunctionSymbol)?.SignatureBinder); + binderOpt: SignatureBinder); } if (bagCreatedOnThisThread) @@ -639,7 +645,7 @@ private void ValidateConditionalAttribute(CSharpAttributeData attribute, Attribu } else if (!this.CanBeReferencedByName || this.MethodKind == MethodKind.Destructor) { - // CS0577: The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation + // CS0577: The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation diagnostics.Add(ErrorCode.ERR_ConditionalOnSpecialMethod, node.Location, this); } else if (!this.ReturnsVoid) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs index d748909c5f46c..283ee801f565f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs @@ -191,12 +191,16 @@ public override NamespaceSymbol GlobalNamespace { if ((object)_globalNamespace == null) { - var diagnostics = new BindingDiagnosticBag(DiagnosticBag.GetInstance()); + var diagnostics = BindingDiagnosticBag.GetInstance(); var globalNS = new SourceNamespaceSymbol( this, this, DeclaringCompilation.MergedRootDeclaration, diagnostics); - Debug.Assert(diagnostics.DiagnosticBag.IsEmptyWithoutResolution); + + if (Interlocked.CompareExchange(ref _globalNamespace, globalNS, null) == null) + { + this.AddDeclarationDiagnostics(diagnostics); + } + diagnostics.Free(); - Interlocked.CompareExchange(ref _globalNamespace, globalNS, null); } return _globalNamespace; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs index 768e5400b9a86..712d6ca063559 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs @@ -91,6 +91,7 @@ internal SourceNamedTypeSymbol(NamespaceOrTypeSymbol containingSymbol, MergedTyp case DeclarationKind.Delegate: case DeclarationKind.Class: case DeclarationKind.Record: + case DeclarationKind.RecordStruct: break; default: Debug.Assert(false, "bad declaration kind"); @@ -123,6 +124,7 @@ private static SyntaxToken GetName(CSharpSyntaxNode node) case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((BaseTypeDeclarationSyntax)node).Identifier; default: return default(SyntaxToken); @@ -164,6 +166,7 @@ private ImmutableArray MakeTypeParameters(BindingDiagnostic case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: tpl = ((TypeDeclarationSyntax)typeDecl).TypeParameterList; break; @@ -462,6 +465,7 @@ private static SyntaxList GetConstraintClau case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: var typeDeclaration = (TypeDeclarationSyntax)node; typeParameterList = typeDeclaration.TypeParameterList; return typeDeclaration.ConstraintClauses; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs index 1ed394955d888..e52b2615acfcc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs @@ -127,8 +127,7 @@ protected override void CheckBase(BindingDiagnosticBag diagnostics) if (declaration.Kind == DeclarationKind.Record) { - if (SynthesizedRecordClone.FindValidCloneMethod(localBase, ref useSiteInfo) is null || - SynthesizedRecordPrintMembers.FindValidPrintMembersMethod(localBase, DeclaringCompilation) is null) + if (SynthesizedRecordClone.FindValidCloneMethod(localBase, ref useSiteInfo) is null) { diagnostics.Add(ErrorCode.ERR_BadRecordBase, baseLocation); } @@ -339,7 +338,7 @@ private Tuple> MakeDeclaredBase CompoundUseSiteInfo useSiteInfo = new CompoundUseSiteInfo(diagnostics, ContainingAssembly); - if (declaration.Kind == DeclarationKind.Record) + if (declaration.Kind is DeclarationKind.Record or DeclarationKind.RecordStruct) { var type = DeclaringCompilation.GetWellKnownType(WellKnownType.System_IEquatable_T).Construct(this); if (baseInterfaces.IndexOf(type, SymbolEqualityComparer.AllIgnoreOptions) < 0) @@ -493,6 +492,7 @@ private Tuple> MakeOneDeclaredB var info = diagnostics.Add(ErrorCode.ERR_StaticDerivedFromNonObject, location, this, localBase); localBase = new ExtendedErrorTypeSymbol(localBase, LookupResultKind.NotReferencable, info); } + checkPrimaryConstructorBaseType(baseTypeSyntax, localBase); continue; } } @@ -501,6 +501,11 @@ private Tuple> MakeOneDeclaredB baseType = baseBinder.BindType(typeSyntax, diagnostics, newBasesBeingResolved).Type; } + if (i == 0) + { + checkPrimaryConstructorBaseType(baseTypeSyntax, baseType); + } + switch (baseType.TypeKind) { case TypeKind.Interface: @@ -583,6 +588,15 @@ private Tuple> MakeOneDeclaredB } return new Tuple>(localBase, localInterfaces.ToImmutableAndFree()); + + void checkPrimaryConstructorBaseType(BaseTypeSyntax baseTypeSyntax, TypeSymbol baseType) + { + if (baseTypeSyntax is PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType && + (!IsRecord || TypeKind != TypeKind.Class || baseType.TypeKind == TypeKind.Interface || ((RecordDeclarationSyntax)decl.SyntaxReference.GetSyntax()).ParameterList is null)) + { + diagnostics.Add(ErrorCode.ERR_UnexpectedArgumentList, primaryConstructorBaseType.ArgumentList.Location); + } + } } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs new file mode 100644 index 0000000000000..ab846338eba59 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.AliasesAndUsings.cs @@ -0,0 +1,1120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + partial class SourceNamespaceSymbol + { + public Imports GetImports(CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + if (!compilationUnit.Externs.Any() && !compilationUnit.Usings.Any()) + { + var result = GetGlobalUsingImports(basesBeingResolved); +#if DEBUG + var calculated = GetAliasesAndUsingsForAsserts(declarationSyntax).GetImports(this, declarationSyntax, basesBeingResolved); + if (result == Imports.Empty || calculated == Imports.Empty) + { + Debug.Assert((object)result == calculated); + } + else + { + Debug.Assert(result.ExternAliases.SequenceEqual(calculated.ExternAliases)); + Debug.Assert(result.UsingAliases.SetEquals(calculated.UsingAliases)); + Debug.Assert(result.Usings.SequenceEqual(calculated.Usings)); + } +#endif + + return result; + } + break; + + case NamespaceDeclarationSyntax namespaceDecl: + if (!namespaceDecl.Externs.Any() && !namespaceDecl.Usings.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetImports(this, declarationSyntax, basesBeingResolved) == Imports.Empty); +#endif + return Imports.Empty; + } + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + return GetAliasesAndUsings(declarationSyntax).GetImports(this, declarationSyntax, basesBeingResolved); + } + + private AliasesAndUsings GetAliasesAndUsings(CSharpSyntaxNode declarationSyntax) + { + return _aliasesAndUsings[GetMatchingNamespaceDeclaration(declarationSyntax)]; + } + + private SingleNamespaceDeclaration GetMatchingNamespaceDeclaration(CSharpSyntaxNode declarationSyntax) + { + foreach (var declaration in _mergedDeclaration.Declarations) + { + var declarationSyntaxRef = declaration.SyntaxReference; + if (declarationSyntaxRef.SyntaxTree != declarationSyntax.SyntaxTree) + { + continue; + } + + if (declarationSyntaxRef.GetSyntax() == declarationSyntax) + { + return declaration; + } + } + + throw ExceptionUtilities.Unreachable; + } + +#if DEBUG + private AliasesAndUsings GetAliasesAndUsingsForAsserts(CSharpSyntaxNode declarationSyntax) + { + var singleDeclaration = GetMatchingNamespaceDeclaration(declarationSyntax); + + if (singleDeclaration.HasExternAliases || singleDeclaration.HasGlobalUsings || singleDeclaration.HasUsings) + { + return _aliasesAndUsings[singleDeclaration]; + } + + return _aliasesAndUsingsForAsserts[singleDeclaration]; + } +#endif + + public ImmutableArray GetExternAliases(CSharpSyntaxNode declarationSyntax) + { + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + if (!compilationUnit.Externs.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetExternAliases(this, declarationSyntax).IsEmpty); +#endif + return ImmutableArray.Empty; + } + break; + + case NamespaceDeclarationSyntax namespaceDecl: + if (!namespaceDecl.Externs.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetExternAliases(this, declarationSyntax).IsEmpty); +#endif + return ImmutableArray.Empty; + } + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + return GetAliasesAndUsings(declarationSyntax).GetExternAliases(this, declarationSyntax); + } + + public ImmutableArray GetUsingAliases(CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + if (!compilationUnit.Usings.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetUsingAliases(this, declarationSyntax, basesBeingResolved).IsEmpty); +#endif + return ImmutableArray.Empty; + } + break; + + case NamespaceDeclarationSyntax namespaceDecl: + if (!namespaceDecl.Usings.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetUsingAliases(this, declarationSyntax, basesBeingResolved).IsEmpty); +#endif + return ImmutableArray.Empty; + } + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + return GetAliasesAndUsings(declarationSyntax).GetUsingAliases(this, declarationSyntax, basesBeingResolved); + } + + public ImmutableDictionary GetUsingAliasesMap(CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + if (!compilationUnit.Usings.Any()) + { + var result = GetGlobalUsingAliasesMap(basesBeingResolved); +#if DEBUG + Debug.Assert(result.SetEquals(GetAliasesAndUsingsForAsserts(declarationSyntax).GetUsingAliasesMap(this, declarationSyntax, basesBeingResolved))); +#endif + return result; + } + break; + + case NamespaceDeclarationSyntax namespaceDecl: + if (!namespaceDecl.Usings.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetUsingAliasesMap(this, declarationSyntax, basesBeingResolved).IsEmpty); +#endif + return ImmutableDictionary.Empty; + } + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + return GetAliasesAndUsings(declarationSyntax).GetUsingAliasesMap(this, declarationSyntax, basesBeingResolved); + } + + public ImmutableArray GetUsingNamespacesOrTypes(CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + if (!compilationUnit.Usings.Any()) + { + var result = GetGlobalUsingNamespacesOrTypes(basesBeingResolved); +#if DEBUG + Debug.Assert(result.SequenceEqual(GetAliasesAndUsingsForAsserts(declarationSyntax).GetUsingNamespacesOrTypes(this, declarationSyntax, basesBeingResolved))); +#endif + return result; + } + break; + + case NamespaceDeclarationSyntax namespaceDecl: + if (!namespaceDecl.Usings.Any()) + { +#if DEBUG + Debug.Assert(GetAliasesAndUsingsForAsserts(declarationSyntax).GetUsingNamespacesOrTypes(this, declarationSyntax, basesBeingResolved).IsEmpty); +#endif + return ImmutableArray.Empty; + } + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + return GetAliasesAndUsings(declarationSyntax).GetUsingNamespacesOrTypes(this, declarationSyntax, basesBeingResolved); + } + + private Imports GetGlobalUsingImports(ConsList? basesBeingResolved) + { + return GetMergedGlobalAliasesAndUsings(basesBeingResolved).Imports; + } + + private ImmutableDictionary GetGlobalUsingAliasesMap(ConsList? basesBeingResolved) + { + return GetMergedGlobalAliasesAndUsings(basesBeingResolved).UsingAliasesMap!; + } + + private ImmutableArray GetGlobalUsingNamespacesOrTypes(ConsList? basesBeingResolved) + { + return GetMergedGlobalAliasesAndUsings(basesBeingResolved).UsingNamespacesOrTypes; + } + + private MergedGlobalAliasesAndUsings GetMergedGlobalAliasesAndUsings(ConsList? basesBeingResolved, CancellationToken cancellationToken = default) + { + if (_lazyMergedGlobalAliasesAndUsings is null) + { + if (!this.IsGlobalNamespace) + { + _lazyMergedGlobalAliasesAndUsings = MergedGlobalAliasesAndUsings.Empty; + } + else + { + ImmutableDictionary? mergedAliases = null; + var mergedNamespacesOrTypes = ArrayBuilder.GetInstance(); + var uniqueUsings = SpecializedSymbolCollections.GetPooledSymbolHashSetInstance(); + var diagnostics = DiagnosticBag.GetInstance(); + + try + { + bool haveExternAliases = false; + + foreach (var singleDeclaration in _mergedDeclaration.Declarations) + { + if (singleDeclaration.HasExternAliases) + { + haveExternAliases = true; + } + + if (singleDeclaration.HasGlobalUsings) + { + var aliases = _aliasesAndUsings[singleDeclaration].GetGlobalUsingAliasesMap(this, singleDeclaration.SyntaxReference, basesBeingResolved); + + cancellationToken.ThrowIfCancellationRequested(); + + if (!aliases.IsEmpty) + { + if (mergedAliases is null) + { + mergedAliases = aliases; + } + else + { + var builder = mergedAliases.ToBuilder(); + bool added = false; + + foreach (var pair in aliases) + { + if (builder.ContainsKey(pair.Key)) + { + // The using alias '{0}' appeared previously in this namespace + diagnostics.Add(ErrorCode.ERR_DuplicateAlias, pair.Value.Alias.Locations[0], pair.Key); + } + else + { + builder.Add(pair); + added = true; + } + } + + if (added) + { + mergedAliases = builder.ToImmutable(); + } + + cancellationToken.ThrowIfCancellationRequested(); + } + } + + var namespacesOrTypes = _aliasesAndUsings[singleDeclaration].GetGlobalUsingNamespacesOrTypes(this, singleDeclaration.SyntaxReference, basesBeingResolved); + + if (!namespacesOrTypes.IsEmpty) + { + if (mergedNamespacesOrTypes.Count == 0) + { + mergedNamespacesOrTypes.AddRange(namespacesOrTypes); + uniqueUsings.AddAll(namespacesOrTypes.Select(n => n.NamespaceOrType)); + } + else + { + foreach (var namespaceOrType in namespacesOrTypes) + { + if (!uniqueUsings.Add(namespaceOrType.NamespaceOrType)) + { + diagnostics.Add(ErrorCode.WRN_DuplicateUsing, namespaceOrType.UsingDirective!.Name.Location, namespaceOrType.NamespaceOrType); + } + else + { + mergedNamespacesOrTypes.Add(namespaceOrType); + } + } + } + } + + cancellationToken.ThrowIfCancellationRequested(); + } + } + + // Report a conflict between global using aliases and extern aliases from other compilation units + if (haveExternAliases && mergedAliases is object) + { + foreach (var singleDeclaration in _mergedDeclaration.Declarations) + { + if (singleDeclaration.HasExternAliases) + { + var externAliases = _aliasesAndUsings[singleDeclaration].GetExternAliases(this, singleDeclaration.SyntaxReference); + var globalAliasesMap = ImmutableDictionary.Empty; + + if (singleDeclaration.HasGlobalUsings) + { + globalAliasesMap = _aliasesAndUsings[singleDeclaration].GetGlobalUsingAliasesMap(this, singleDeclaration.SyntaxReference, basesBeingResolved); + } + + foreach (var externAlias in externAliases) + { + if (!externAlias.SkipInLookup && + !globalAliasesMap.ContainsKey(externAlias.Alias.Name) && // If we have a global alias with the same name declared in the same compilation unit, we already reported the conflict on the global alias. + mergedAliases.ContainsKey(externAlias.Alias.Name)) + { + // The using alias '{0}' appeared previously in this namespace + diagnostics.Add(ErrorCode.ERR_DuplicateAlias, externAlias.Alias.Locations[0], externAlias.Alias.Name); + } + } + } + } + } + + Interlocked.CompareExchange(ref _lazyMergedGlobalAliasesAndUsings, + new MergedGlobalAliasesAndUsings() + { + UsingAliasesMap = mergedAliases ?? ImmutableDictionary.Empty, + UsingNamespacesOrTypes = mergedNamespacesOrTypes.ToImmutableAndFree(), + Diagnostics = diagnostics.ToReadOnlyAndFree() + }, + null); + + mergedNamespacesOrTypes = null; + diagnostics = null; + } + finally + { + uniqueUsings.Free(); + mergedNamespacesOrTypes?.Free(); + diagnostics?.Free(); + } + } + } + + return _lazyMergedGlobalAliasesAndUsings; + } + + private class AliasesAndUsings + { + private ExternAliasesAndDiagnostics? _lazyExternAliases; + private UsingsAndDiagnostics? _lazyGlobalUsings; + private UsingsAndDiagnostics? _lazyUsings; + private Imports? _lazyImports; + + /// + /// Completion state that tracks whether validation was done/not done/currently in process. + /// + private SymbolCompletionState _state; + + internal ImmutableArray GetExternAliases(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax) + { + return GetExternAliasesAndDiagnostics(declaringSymbol, declarationSyntax).ExternAliases; + } + + internal ImmutableArray GetExternAliases(SourceNamespaceSymbol declaringSymbol, SyntaxReference declarationSyntax) + { + return (_lazyExternAliases ?? GetExternAliasesAndDiagnostics(declaringSymbol, (CSharpSyntaxNode)declarationSyntax.GetSyntax())).ExternAliases; + } + + private ExternAliasesAndDiagnostics GetExternAliasesAndDiagnostics(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax) + { + if (_lazyExternAliases is null) + { + SyntaxList externAliasDirectives; + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + externAliasDirectives = compilationUnit.Externs; + break; + + case NamespaceDeclarationSyntax namespaceDecl: + externAliasDirectives = namespaceDecl.Externs; + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + if (!externAliasDirectives.Any()) + { +#if DEBUG + var diagnostics = DiagnosticBag.GetInstance(); + var result = buildExternAliases(externAliasDirectives, declaringSymbol, diagnostics); + Debug.Assert(result.IsEmpty); + Debug.Assert(diagnostics.IsEmptyWithoutResolution); + diagnostics.Free(); +#endif + _lazyExternAliases = ExternAliasesAndDiagnostics.Empty; + } + else + { + var diagnostics = DiagnosticBag.GetInstance(); + Interlocked.CompareExchange( + ref _lazyExternAliases, + new ExternAliasesAndDiagnostics() { ExternAliases = buildExternAliases(externAliasDirectives, declaringSymbol, diagnostics), Diagnostics = diagnostics.ToReadOnlyAndFree() }, + null); + } + } + + return _lazyExternAliases; + + static ImmutableArray buildExternAliases( + SyntaxList syntaxList, + SourceNamespaceSymbol declaringSymbol, + DiagnosticBag diagnostics) + { + CSharpCompilation compilation = declaringSymbol.DeclaringCompilation; + + var builder = ArrayBuilder.GetInstance(); + + foreach (ExternAliasDirectiveSyntax aliasSyntax in syntaxList) + { + compilation.RecordImport(aliasSyntax); + bool skipInLookup = false; + + // Extern aliases not allowed in interactive submissions: + if (compilation.IsSubmission) + { + diagnostics.Add(ErrorCode.ERR_ExternAliasNotAllowed, aliasSyntax.Location); + skipInLookup = true; + } + else + { + // some n^2 action, but n should be very small. + foreach (var existingAlias in builder) + { + if (existingAlias.Alias.Name == aliasSyntax.Identifier.ValueText) + { + diagnostics.Add(ErrorCode.ERR_DuplicateAlias, existingAlias.Alias.Locations[0], existingAlias.Alias.Name); + break; + } + } + + if (aliasSyntax.Identifier.ContextualKind() == SyntaxKind.GlobalKeyword) + { + diagnostics.Add(ErrorCode.ERR_GlobalExternAlias, aliasSyntax.Identifier.GetLocation()); + } + } + + builder.Add(new AliasAndExternAliasDirective(new AliasSymbolFromSyntax(declaringSymbol, aliasSyntax), aliasSyntax, skipInLookup)); + } + + return builder.ToImmutableAndFree(); + } + } + + internal ImmutableArray GetUsingAliases(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + return GetUsingsAndDiagnostics(declaringSymbol, declarationSyntax, basesBeingResolved).UsingAliases; + } + + internal ImmutableArray GetGlobalUsingAliases(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + return GetGlobalUsingsAndDiagnostics(declaringSymbol, declarationSyntax, basesBeingResolved).UsingAliases; + } + + internal ImmutableDictionary GetUsingAliasesMap(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + return GetUsingsAndDiagnostics(declaringSymbol, declarationSyntax, basesBeingResolved).UsingAliasesMap ?? ImmutableDictionary.Empty; + } + + internal ImmutableDictionary GetGlobalUsingAliasesMap(SourceNamespaceSymbol declaringSymbol, SyntaxReference declarationSyntax, ConsList? basesBeingResolved) + { + return (_lazyGlobalUsings ?? GetGlobalUsingsAndDiagnostics(declaringSymbol, (CSharpSyntaxNode)declarationSyntax.GetSyntax(), basesBeingResolved)).UsingAliasesMap ?? ImmutableDictionary.Empty; + } + + internal ImmutableArray GetUsingNamespacesOrTypes(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + return GetUsingsAndDiagnostics(declaringSymbol, declarationSyntax, basesBeingResolved).UsingNamespacesOrTypes; + } + + private UsingsAndDiagnostics GetUsingsAndDiagnostics(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + return GetUsingsAndDiagnostics(ref _lazyUsings, declaringSymbol, declarationSyntax, basesBeingResolved, onlyGlobal: false); + } + + internal ImmutableArray GetGlobalUsingNamespacesOrTypes(SourceNamespaceSymbol declaringSymbol, SyntaxReference declarationSyntax, ConsList? basesBeingResolved) + { + return (_lazyGlobalUsings ?? GetGlobalUsingsAndDiagnostics(declaringSymbol, (CSharpSyntaxNode)declarationSyntax.GetSyntax(), basesBeingResolved)).UsingNamespacesOrTypes; + } + + private UsingsAndDiagnostics GetGlobalUsingsAndDiagnostics(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + return GetUsingsAndDiagnostics(ref _lazyGlobalUsings, declaringSymbol, declarationSyntax, basesBeingResolved, onlyGlobal: true); + } + + private UsingsAndDiagnostics GetUsingsAndDiagnostics(ref UsingsAndDiagnostics? usings, SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved, bool onlyGlobal) + { + if (usings is null) + { + SyntaxList usingDirectives; + bool? applyIsGlobalFilter; + switch (declarationSyntax) + { + case CompilationUnitSyntax compilationUnit: + applyIsGlobalFilter = onlyGlobal; + usingDirectives = compilationUnit.Usings; + break; + + case NamespaceDeclarationSyntax namespaceDecl: + Debug.Assert(!onlyGlobal); + applyIsGlobalFilter = null; // Global Using directives are not allowed in namespaces, treat them as regular, an error is reported elsewhere. + usingDirectives = namespaceDecl.Usings; + break; + + default: + throw ExceptionUtilities.UnexpectedValue(declarationSyntax); + } + + UsingsAndDiagnostics result; + if (!usingDirectives.Any()) + { + if (applyIsGlobalFilter != false) + { +#if DEBUG + var calculated = buildUsings(usingDirectives, declaringSymbol, declarationSyntax, applyIsGlobalFilter, basesBeingResolved); + Debug.Assert(calculated.UsingAliases.IsEmpty); + Debug.Assert(calculated.UsingAliasesMap?.IsEmpty ?? true); + Debug.Assert(calculated.UsingNamespacesOrTypes.IsEmpty); + Debug.Assert(calculated.Diagnostics?.IsEmptyWithoutResolution ?? true); +#endif + result = UsingsAndDiagnostics.Empty; + } + else + { + result = new UsingsAndDiagnostics() + { + UsingAliases = GetGlobalUsingAliases(declaringSymbol, declarationSyntax, basesBeingResolved), + UsingAliasesMap = declaringSymbol.GetGlobalUsingAliasesMap(basesBeingResolved), + UsingNamespacesOrTypes = declaringSymbol.GetGlobalUsingNamespacesOrTypes(basesBeingResolved), + Diagnostics = null + }; +#if DEBUG + var calculated = buildUsings(usingDirectives, declaringSymbol, declarationSyntax, applyIsGlobalFilter, basesBeingResolved); + Debug.Assert(calculated.UsingAliases.SequenceEqual(result.UsingAliases)); + Debug.Assert((calculated.UsingAliasesMap ?? ImmutableDictionary.Empty).SetEquals(result.UsingAliasesMap ?? ImmutableDictionary.Empty)); + Debug.Assert(calculated.UsingNamespacesOrTypes.SequenceEqual(result.UsingNamespacesOrTypes)); + Debug.Assert(calculated.Diagnostics?.IsEmptyWithoutResolution ?? true); +#endif + } + } + else + { + result = buildUsings(usingDirectives, declaringSymbol, declarationSyntax, applyIsGlobalFilter, basesBeingResolved); + } + + Interlocked.CompareExchange(ref usings, result, null); + } + + return usings; + + UsingsAndDiagnostics buildUsings( + SyntaxList usingDirectives, + SourceNamespaceSymbol declaringSymbol, + CSharpSyntaxNode declarationSyntax, + bool? applyIsGlobalFilter, + ConsList? basesBeingResolved) + { + // define all of the extern aliases first. They may be used by the target of a using + var externAliases = GetExternAliases(declaringSymbol, declarationSyntax); + var globalUsingAliasesMap = ImmutableDictionary.Empty; + var globalUsingNamespacesOrTypes = ImmutableArray.Empty; + var globalUsingAliases = ImmutableArray.Empty; + + if (applyIsGlobalFilter == false) + { + // Define all of the global usings. They may cause conflicts, etc. + globalUsingAliasesMap = declaringSymbol.GetGlobalUsingAliasesMap(basesBeingResolved); + globalUsingNamespacesOrTypes = declaringSymbol.GetGlobalUsingNamespacesOrTypes(basesBeingResolved); + globalUsingAliases = GetGlobalUsingAliases(declaringSymbol, declarationSyntax, basesBeingResolved); + } + + var diagnostics = new DiagnosticBag(); + + var compilation = declaringSymbol.DeclaringCompilation; + + ArrayBuilder? usings = null; + ImmutableDictionary.Builder? usingAliasesMap = null; + ArrayBuilder? usingAliases = null; + + // A binder that contains the extern aliases but not the usings. The resolution of the target of a using directive or alias + // should not make use of other peer usings. + Binder? declarationBinder = null; + + PooledHashSet? uniqueUsings = null; + + foreach (var usingDirective in usingDirectives) + { + if (applyIsGlobalFilter.HasValue && usingDirective.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword) != applyIsGlobalFilter.GetValueOrDefault()) + { + continue; + } + + compilation.RecordImport(usingDirective); + + if (usingDirective.Alias != null) + { + SyntaxToken identifier = usingDirective.Alias.Name.Identifier; + Location location = usingDirective.Alias.Name.Location; + + if (identifier.ContextualKind() == SyntaxKind.GlobalKeyword) + { + diagnostics.Add(ErrorCode.WRN_GlobalAliasDefn, location); + } + + if (usingDirective.StaticKeyword != default(SyntaxToken)) + { + diagnostics.Add(ErrorCode.ERR_NoAliasHere, location); + } + + SourceMemberContainerTypeSymbol.ReportTypeNamedRecord(identifier.Text, compilation, diagnostics, location); + + string identifierValueText = identifier.ValueText; + bool skipInLookup = false; + + if (usingAliasesMap?.ContainsKey(identifierValueText) ?? globalUsingAliasesMap.ContainsKey(identifierValueText)) + { + skipInLookup = true; + + // Suppress diagnostics if we're already broken. + if (!usingDirective.Name.IsMissing) + { + // The using alias '{0}' appeared previously in this namespace + diagnostics.Add(ErrorCode.ERR_DuplicateAlias, location, identifierValueText); + } + } + else + { + // an O(m*n) algorithm here but n (number of extern aliases) will likely be very small. + foreach (var externAlias in externAliases) + { + if (externAlias.Alias.Name == identifierValueText) + { + // The using alias '{0}' appeared previously in this namespace + diagnostics.Add(ErrorCode.ERR_DuplicateAlias, usingDirective.Location, identifierValueText); + break; + } + } + } + + // construct the alias sym with the binder for which we are building imports. That + // way the alias target can make use of extern alias definitions. + var aliasAndDirective = new AliasAndUsingDirective(new AliasSymbolFromSyntax(declaringSymbol, usingDirective), usingDirective); + + if (usingAliases is null) + { + usingAliases = ArrayBuilder.GetInstance(); + usingAliases.AddRange(globalUsingAliases); + } + + usingAliases.Add(aliasAndDirective); + + if (!skipInLookup) + { + if (usingAliasesMap == null) + { + usingAliasesMap = globalUsingAliasesMap.ToBuilder(); + } + + usingAliasesMap.Add(identifierValueText, aliasAndDirective); + } + } + else + { + if (usingDirective.Name.IsMissing) + { + //don't try to lookup namespaces inserted by parser error recovery + continue; + } + + var directiveDiagnostics = BindingDiagnosticBag.GetInstance(); + Debug.Assert(directiveDiagnostics.DiagnosticBag is object); + Debug.Assert(directiveDiagnostics.DependenciesBag is object); + + declarationBinder ??= compilation.GetBinderFactory(declarationSyntax.SyntaxTree).GetBinder(usingDirective.Name).WithAdditionalFlags(BinderFlags.SuppressConstraintChecks); + var imported = declarationBinder.BindNamespaceOrTypeSymbol(usingDirective.Name, directiveDiagnostics, basesBeingResolved).NamespaceOrTypeSymbol; + + if (imported.Kind == SymbolKind.Namespace) + { + Debug.Assert(directiveDiagnostics.DependenciesBag.IsEmpty()); + + if (usingDirective.StaticKeyword != default(SyntaxToken)) + { + diagnostics.Add(ErrorCode.ERR_BadUsingType, usingDirective.Name.Location, imported); + } + else if (!getOrCreateUniqueUsings(ref uniqueUsings, globalUsingNamespacesOrTypes).Add(imported)) + { + diagnostics.Add(ErrorCode.WRN_DuplicateUsing, usingDirective.Name.Location, imported); + } + else + { + getOrCreateUsingsBuilder(ref usings, globalUsingNamespacesOrTypes).Add(new NamespaceOrTypeAndUsingDirective(imported, usingDirective, dependencies: default)); + } + } + else if (imported.Kind == SymbolKind.NamedType) + { + if (usingDirective.StaticKeyword == default(SyntaxToken)) + { + diagnostics.Add(ErrorCode.ERR_BadUsingNamespace, usingDirective.Name.Location, imported); + } + else + { + var importedType = (NamedTypeSymbol)imported; + if (!getOrCreateUniqueUsings(ref uniqueUsings, globalUsingNamespacesOrTypes).Add(importedType)) + { + diagnostics.Add(ErrorCode.WRN_DuplicateUsing, usingDirective.Name.Location, importedType); + } + else + { + declarationBinder.ReportDiagnosticsIfObsolete(diagnostics, importedType, usingDirective.Name, hasBaseReceiver: false); + + getOrCreateUsingsBuilder(ref usings, globalUsingNamespacesOrTypes).Add(new NamespaceOrTypeAndUsingDirective(importedType, usingDirective, directiveDiagnostics.DependenciesBag.ToImmutableArray())); + } + } + } + else if (imported.Kind != SymbolKind.ErrorType) + { + // Do not report additional error if the symbol itself is erroneous. + + // error: '' is a '' but is used as 'type or namespace' + diagnostics.Add(ErrorCode.ERR_BadSKknown, usingDirective.Name.Location, + usingDirective.Name, + imported.GetKindText(), + MessageID.IDS_SK_TYPE_OR_NAMESPACE.Localize()); + } + + diagnostics.AddRange(directiveDiagnostics.DiagnosticBag); + directiveDiagnostics.Free(); + } + } + + uniqueUsings?.Free(); + + if (diagnostics.IsEmptyWithoutResolution) + { + diagnostics = null; + } + + return new UsingsAndDiagnostics() + { + UsingAliases = usingAliases?.ToImmutableAndFree() ?? globalUsingAliases, + UsingAliasesMap = usingAliasesMap?.ToImmutable() ?? globalUsingAliasesMap, + UsingNamespacesOrTypes = usings?.ToImmutableAndFree() ?? globalUsingNamespacesOrTypes, + Diagnostics = diagnostics + }; + + static PooledHashSet getOrCreateUniqueUsings(ref PooledHashSet? uniqueUsings, ImmutableArray globalUsingNamespacesOrTypes) + { + if (uniqueUsings is null) + { + uniqueUsings = SpecializedSymbolCollections.GetPooledSymbolHashSetInstance(); + uniqueUsings.AddAll(globalUsingNamespacesOrTypes.Select(n => n.NamespaceOrType)); + } + + return uniqueUsings; + } + + static ArrayBuilder getOrCreateUsingsBuilder(ref ArrayBuilder? usings, ImmutableArray globalUsingNamespacesOrTypes) + { + if (usings is null) + { + usings = ArrayBuilder.GetInstance(); + usings.AddRange(globalUsingNamespacesOrTypes); + } + + return usings; + } + } + } + + internal Imports GetImports(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, ConsList? basesBeingResolved) + { + if (_lazyImports is null) + { + Interlocked.CompareExchange(ref _lazyImports, + Imports.Create(GetUsingAliasesMap(declaringSymbol, declarationSyntax, basesBeingResolved), + GetUsingNamespacesOrTypes(declaringSymbol, declarationSyntax, basesBeingResolved), + GetExternAliases(declaringSymbol, declarationSyntax)), + null); + } + + return _lazyImports; + } + + internal void Complete(SourceNamespaceSymbol declaringSymbol, SyntaxReference declarationSyntax, CancellationToken cancellationToken) + { + var externAliasesAndDiagnostics = _lazyExternAliases ?? GetExternAliasesAndDiagnostics(declaringSymbol, (CSharpSyntaxNode)declarationSyntax.GetSyntax(cancellationToken)); + cancellationToken.ThrowIfCancellationRequested(); + + var globalUsingsAndDiagnostics = _lazyGlobalUsings ?? + (declaringSymbol.IsGlobalNamespace ? + GetGlobalUsingsAndDiagnostics(declaringSymbol, (CSharpSyntaxNode)declarationSyntax.GetSyntax(cancellationToken), basesBeingResolved: null) : + UsingsAndDiagnostics.Empty); + cancellationToken.ThrowIfCancellationRequested(); + + var usingsAndDiagnostics = _lazyUsings ?? GetUsingsAndDiagnostics(declaringSymbol, (CSharpSyntaxNode)declarationSyntax.GetSyntax(cancellationToken), basesBeingResolved: null); + cancellationToken.ThrowIfCancellationRequested(); + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + var incompletePart = _state.NextIncompletePart; + switch (incompletePart) + { + case CompletionPart.StartValidatingImports: + { + if (_state.NotePartComplete(CompletionPart.StartValidatingImports)) + { + Validate(declaringSymbol, declarationSyntax, externAliasesAndDiagnostics, usingsAndDiagnostics, globalUsingsAndDiagnostics.Diagnostics); + _state.NotePartComplete(CompletionPart.FinishValidatingImports); + } + } + break; + + case CompletionPart.FinishValidatingImports: + // some other thread has started validating imports (otherwise we would be in the case above) so + // we just wait for it to both finish and report the diagnostics. + Debug.Assert(_state.HasComplete(CompletionPart.StartValidatingImports)); + _state.SpinWaitComplete(CompletionPart.FinishValidatingImports, cancellationToken); + break; + + case CompletionPart.None: + return; + + default: + // any other values are completion parts intended for other kinds of symbols + _state.NotePartComplete(CompletionPart.All & ~CompletionPart.ImportsAll); + break; + } + + _state.SpinWaitComplete(incompletePart, cancellationToken); + } + } + + private static void Validate(SourceNamespaceSymbol declaringSymbol, SyntaxReference declarationSyntax, ExternAliasesAndDiagnostics externAliasesAndDiagnostics, UsingsAndDiagnostics usingsAndDiagnostics, DiagnosticBag? globalUsingDiagnostics) + { + var compilation = declaringSymbol.DeclaringCompilation; + DiagnosticBag semanticDiagnostics = compilation.DeclarationDiagnostics; + + // Check constraints within named aliases. + var diagnostics = BindingDiagnosticBag.GetInstance(); + Debug.Assert(diagnostics.DiagnosticBag is object); + Debug.Assert(diagnostics.DependenciesBag is object); + + if (usingsAndDiagnostics.UsingAliasesMap is object) + { + // Force resolution of named aliases. + foreach (var (_, alias) in usingsAndDiagnostics.UsingAliasesMap) + { + if (alias.UsingDirectiveReference!.SyntaxTree != declarationSyntax.SyntaxTree) + { + // Must be a global alias from a different compilation unit + Debug.Assert(declaringSymbol.IsGlobalNamespace); + continue; + } + + NamespaceOrTypeSymbol target = alias.Alias.GetAliasTarget(basesBeingResolved: null); + + diagnostics.Clear(); + if (alias.Alias is AliasSymbolFromSyntax aliasFromSyntax) + { + diagnostics.AddRange(aliasFromSyntax.AliasTargetDiagnostics); + } + + alias.Alias.CheckConstraints(diagnostics); + + semanticDiagnostics.AddRange(diagnostics.DiagnosticBag); + recordImportDependencies(alias.UsingDirective!, target); + } + } + + var corLibrary = compilation.SourceAssembly.CorLibrary; + var conversions = new TypeConversions(corLibrary); + foreach (var @using in usingsAndDiagnostics.UsingNamespacesOrTypes) + { + if (@using.UsingDirectiveReference!.SyntaxTree != declarationSyntax.SyntaxTree) + { + // Must be a global using directive from a different compilation unit + Debug.Assert(declaringSymbol.IsGlobalNamespace); + continue; + } + + diagnostics.Clear(); + diagnostics.AddDependencies(@using.Dependencies); + + NamespaceOrTypeSymbol target = @using.NamespaceOrType; + + // Check if `using static` directives meet constraints. + UsingDirectiveSyntax usingDirective = @using.UsingDirective!; + if (target.IsType) + { + var typeSymbol = (TypeSymbol)target; + var location = usingDirective.Name.Location; + typeSymbol.CheckAllConstraints(compilation, conversions, location, diagnostics); + } + + semanticDiagnostics.AddRange(diagnostics.DiagnosticBag); + recordImportDependencies(usingDirective, target); + } + + // Force resolution of extern aliases. + foreach (var alias in externAliasesAndDiagnostics.ExternAliases) + { + if (alias.SkipInLookup) + { + continue; + } + + var target = (NamespaceSymbol)alias.Alias.GetAliasTarget(null); + Debug.Assert(target.IsGlobalNamespace); + + if (alias.Alias is AliasSymbolFromSyntax aliasFromSyntax) + { + semanticDiagnostics.AddRange(aliasFromSyntax.AliasTargetDiagnostics.DiagnosticBag!); + } + + if (!Compilation.ReportUnusedImportsInTree(alias.ExternAliasDirective!.SyntaxTree)) + { + diagnostics.Clear(); + diagnostics.AddAssembliesUsedByNamespaceReference(target); + compilation.AddUsedAssemblies(diagnostics.DependenciesBag); + } + } + + semanticDiagnostics.AddRange(externAliasesAndDiagnostics.Diagnostics); + + if (usingsAndDiagnostics.Diagnostics?.IsEmptyWithoutResolution == false) + { + semanticDiagnostics.AddRange(usingsAndDiagnostics.Diagnostics.AsEnumerable()); + } + + if (globalUsingDiagnostics?.IsEmptyWithoutResolution == false) + { + semanticDiagnostics.AddRange(globalUsingDiagnostics.AsEnumerable()); + } + + diagnostics.Free(); + + void recordImportDependencies(UsingDirectiveSyntax usingDirective, NamespaceOrTypeSymbol target) + { + if (Compilation.ReportUnusedImportsInTree(usingDirective.SyntaxTree)) + { + compilation.RecordImportDependencies(usingDirective, diagnostics.DependenciesBag.ToImmutableArray()); + } + else + { + if (target.IsNamespace) + { + diagnostics.AddAssembliesUsedByNamespaceReference((NamespaceSymbol)target); + } + + compilation.AddUsedAssemblies(diagnostics.DependenciesBag); + } + } + } + + private class ExternAliasesAndDiagnostics + { + public static readonly ExternAliasesAndDiagnostics Empty = new ExternAliasesAndDiagnostics() { ExternAliases = ImmutableArray.Empty, Diagnostics = ImmutableArray.Empty }; + + public ImmutableArray ExternAliases { get; init; } + public ImmutableArray Diagnostics { get; init; } + } + + private class UsingsAndDiagnostics + { + public static readonly UsingsAndDiagnostics Empty = + new UsingsAndDiagnostics() + { + UsingAliases = ImmutableArray.Empty, + UsingAliasesMap = null, + UsingNamespacesOrTypes = ImmutableArray.Empty, + Diagnostics = null + }; + + public ImmutableArray UsingAliases { get; init; } + public ImmutableDictionary? UsingAliasesMap { get; init; } + public ImmutableArray UsingNamespacesOrTypes { get; init; } + public DiagnosticBag? Diagnostics { get; init; } + } + } + + + private class MergedGlobalAliasesAndUsings + { + private Imports? _lazyImports; + + /// + /// Completion state that tracks whether validation was done/not done/currently in process. + /// + private SymbolCompletionState _state; + + public static readonly MergedGlobalAliasesAndUsings Empty = + new MergedGlobalAliasesAndUsings() + { + UsingAliasesMap = ImmutableDictionary.Empty, + UsingNamespacesOrTypes = ImmutableArray.Empty, + Diagnostics = ImmutableArray.Empty, + _lazyImports = Imports.Empty + }; + + public ImmutableDictionary? UsingAliasesMap { get; init; } + public ImmutableArray UsingNamespacesOrTypes { get; init; } + public ImmutableArray Diagnostics { get; init; } + + public Imports Imports + { + get + { + if (_lazyImports is null) + { + Interlocked.CompareExchange(ref _lazyImports, + Imports.Create(UsingAliasesMap ?? ImmutableDictionary.Empty, + UsingNamespacesOrTypes, + ImmutableArray.Empty), + null); + } + + return _lazyImports; + } + } + + internal void Complete(SourceNamespaceSymbol declaringSymbol, CancellationToken cancellationToken) + { + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + var incompletePart = _state.NextIncompletePart; + switch (incompletePart) + { + case CompletionPart.StartValidatingImports: + { + if (_state.NotePartComplete(CompletionPart.StartValidatingImports)) + { + if (!Diagnostics.IsDefaultOrEmpty) + { + var compilation = declaringSymbol.DeclaringCompilation; + DiagnosticBag semanticDiagnostics = compilation.DeclarationDiagnostics; + semanticDiagnostics.AddRange(Diagnostics); + } + + _state.NotePartComplete(CompletionPart.FinishValidatingImports); + } + } + break; + + case CompletionPart.FinishValidatingImports: + // some other thread has started validating imports (otherwise we would be in the case above) so + // we just wait for it to both finish and report the diagnostics. + Debug.Assert(_state.HasComplete(CompletionPart.StartValidatingImports)); + _state.SpinWaitComplete(CompletionPart.FinishValidatingImports, cancellationToken); + break; + + case CompletionPart.None: + return; + + default: + // any other values are completion parts intended for other kinds of symbols + _state.NotePartComplete(CompletionPart.All & ~CompletionPart.ImportsAll); + break; + } + + _state.SpinWaitComplete(incompletePart, cancellationToken); + } + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs index a8a0b039f36f8..05c7da7a1fdb0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -27,6 +28,11 @@ internal sealed partial class SourceNamespaceSymbol : NamespaceSymbol private Dictionary> _nameToTypeMembersMap; private ImmutableArray _lazyAllMembers; private ImmutableArray _lazyTypeMembersUnordered; + private readonly ImmutableDictionary _aliasesAndUsings; +#if DEBUG + private readonly ImmutableDictionary _aliasesAndUsingsForAsserts; +#endif + private MergedGlobalAliasesAndUsings _lazyMergedGlobalAliasesAndUsings; private const int LazyAllMembersIsSorted = 0x1; // Set if "lazyAllMembers" is sorted. private int _flags; @@ -43,10 +49,30 @@ internal SourceNamespaceSymbol( _container = container; _mergedDeclaration = mergedDeclaration; + var builder = ImmutableDictionary.CreateBuilder(ReferenceEqualityComparer.Instance); +#if DEBUG + var builderForAsserts = ImmutableDictionary.CreateBuilder(ReferenceEqualityComparer.Instance); +#endif foreach (var singleDeclaration in mergedDeclaration.Declarations) { + if (singleDeclaration.HasExternAliases || singleDeclaration.HasGlobalUsings || singleDeclaration.HasUsings) + { + builder.Add(singleDeclaration, new AliasesAndUsings()); + } +#if DEBUG + else + { + builderForAsserts.Add(singleDeclaration, new AliasesAndUsings()); + } +#endif + diagnostics.AddRange(singleDeclaration.Diagnostics); } + + _aliasesAndUsings = builder.ToImmutable(); +#if DEBUG + _aliasesAndUsingsForAsserts = builderForAsserts.ToImmutable(); +#endif } internal MergedNamespaceDeclaration MergedDeclaration @@ -58,18 +84,6 @@ public override Symbol ContainingSymbol public override AssemblySymbol ContainingAssembly => _module.ContainingAssembly; - internal IEnumerable GetBoundImportsMerged() - { - var compilation = this.DeclaringCompilation; - foreach (var declaration in _mergedDeclaration.Declarations) - { - if (declaration.HasUsings || declaration.HasExternAliases) - { - yield return compilation.GetImports(declaration); - } - } - } - public override string Name => _mergedDeclaration.Name; @@ -396,6 +410,7 @@ private NamespaceOrTypeSymbol BuildSymbol(MergedNamespaceOrTypeDeclaration decla case DeclarationKind.Delegate: case DeclarationKind.Class: case DeclarationKind.Record: + case DeclarationKind.RecordStruct: return new SourceNamedTypeSymbol(this, (MergedTypeDeclaration)declaration, diagnostics); case DeclarationKind.Script: diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol_Completion.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol_Completion.cs index c6aa091cb2c88..5a1f90228bc54 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol_Completion.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol_Completion.cs @@ -27,18 +27,26 @@ internal override void ForceComplete(SourceLocation locationOpt, CancellationTok case CompletionPart.MembersCompleted: { + SingleNamespaceDeclaration targetDeclarationWithImports = null; + // ensure relevant imports are complete. foreach (var declaration in _mergedDeclaration.Declarations) { if (locationOpt == null || locationOpt.SourceTree == declaration.SyntaxReference.SyntaxTree) { - if (declaration.HasUsings || declaration.HasExternAliases) + if (declaration.HasGlobalUsings || declaration.HasUsings || declaration.HasExternAliases) { - this.DeclaringCompilation.GetImports(declaration).Complete(cancellationToken); + targetDeclarationWithImports = declaration; + _aliasesAndUsings[declaration].Complete(this, declaration.SyntaxReference, cancellationToken); } } } + if (IsGlobalNamespace && (locationOpt is null || targetDeclarationWithImports is object)) + { + GetMergedGlobalAliasesAndUsings(basesBeingResolved: null, cancellationToken).Complete(this, cancellationToken); + } + var members = this.GetMembers(); bool allCompleted = true; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs index 447a3e9a717f0..2fde8c4e4fe27 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs @@ -76,7 +76,7 @@ public static SourceParameterSymbol Create( (syntax.AttributeLists.Count == 0) && !owner.IsPartialMethod()) { - return new SourceSimpleParameterSymbol(owner, parameterType, ordinal, refKind, name, isDiscard: false, locations); + return new SourceSimpleParameterSymbol(owner, parameterType, ordinal, refKind, name, locations); } return new SourceComplexParameterSymbol( diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs index f603ca23fa293..8599e760f0a5c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.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.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -21,16 +19,14 @@ public SourceSimpleParameterSymbol( int ordinal, RefKind refKind, string name, - bool isDiscard, ImmutableArray locations) : base(owner, parameterType, ordinal, refKind, name, locations) { - IsDiscard = isDiscard; } - public override bool IsDiscard { get; } + public override bool IsDiscard => false; - internal override ConstantValue ExplicitDefaultConstantValue + internal override ConstantValue? ExplicitDefaultConstantValue { get { return null; } } @@ -55,7 +51,7 @@ public override ImmutableArray RefCustomModifiers get { return ImmutableArray.Empty; } } - internal override SyntaxReference SyntaxReference + internal override SyntaxReference? SyntaxReference { get { return null; } } @@ -97,7 +93,7 @@ internal override FlowAnalysisAnnotations FlowAnalysisAnnotations internal override ImmutableHashSet NotNullIfParameterNotNull => ImmutableHashSet.Empty; - internal override MarshalPseudoCustomAttributeData MarshallingInformation + internal override MarshalPseudoCustomAttributeData? MarshallingInformation { get { return null; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs index 070b65c225096..937bd94ce0f7a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs @@ -46,7 +46,7 @@ private SourceUserDefinedConversionSymbol( containingType, location, syntax, - MakeDeclarationModifiers(syntax, location, diagnostics), + MakeDeclarationModifiers(containingType.IsInterface, syntax, location, diagnostics), hasBody: syntax.HasAnyBody(), isExpressionBodied: syntax.Body == null && syntax.ExpressionBody != null, isIterator: SyntaxFacts.HasYieldOperations(syntax.Body), diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs index 7a87de55c1c6c..155a67e773e4c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs @@ -44,7 +44,7 @@ private SourceUserDefinedOperatorSymbol( containingType, location, syntax, - MakeDeclarationModifiers(syntax, location, diagnostics), + MakeDeclarationModifiers(containingType.IsInterface, syntax, location, diagnostics), hasBody: syntax.HasAnyBody(), isExpressionBodied: syntax.Body == null && syntax.ExpressionBody != null, isIterator: SyntaxFacts.HasYieldOperations(syntax.Body), diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs index 8adc46412e49b..45068883ccef4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs @@ -94,9 +94,9 @@ protected SourceUserDefinedOperatorSymbolBase( } } - protected static DeclarationModifiers MakeDeclarationModifiers(BaseMethodDeclarationSyntax syntax, Location location, BindingDiagnosticBag diagnostics) + protected static DeclarationModifiers MakeDeclarationModifiers(bool inInterface, BaseMethodDeclarationSyntax syntax, Location location, BindingDiagnosticBag diagnostics) { - var defaultAccess = DeclarationModifiers.Private; + var defaultAccess = inInterface ? DeclarationModifiers.Public : DeclarationModifiers.Private; var allowedModifiers = DeclarationModifiers.AccessibilityMask | DeclarationModifiers.Static | diff --git a/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs index b89065f06a1cf..303d57ab4acd9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs @@ -159,6 +159,8 @@ internal override ImmutableArray GetInterfacesToEmit() throw ExceptionUtilities.Unreachable; } + internal abstract override bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo result, Symbol owner, ref HashSet checkedTypes); + public sealed override IEnumerable MemberNames { get @@ -399,6 +401,7 @@ internal override IEnumerable GetCustomAttributesToEmit(PEM internal sealed override NamedTypeSymbol NativeIntegerUnderlyingType => null; internal sealed override bool IsRecord => _underlyingType.IsRecord; + internal sealed override bool IsRecordStruct => _underlyingType.IsRecordStruct; internal sealed override bool HasPossibleWellKnownCloneMethod() => _underlyingType.HasPossibleWellKnownCloneMethod(); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs index 3d6ef0535070b..08ce46c22283e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs @@ -447,7 +447,7 @@ private ImmutableArray GetAttributesToBind( var binder = rootBinderOpt ?? compilation.GetBinderFactory(syntaxTree).GetBinder(attributeDeclarationSyntaxList.Node); binder = new ContextualAttributeBinder(binder, this); - Debug.Assert(!binder.InAttributeArgument, "Possible cycle in attribute binding"); + Debug.Assert(!binder.InAttributeArgument || this is MethodSymbol { MethodKind: MethodKind.LambdaMethod }, "Possible cycle in attribute binding"); for (int i = 0; i < attributesToBindCount - prevCount; i++) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedLabelSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedLabelSymbol.cs index 91c42bdbc1c1c..53a891a8727d9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedLabelSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedLabelSymbol.cs @@ -16,6 +16,9 @@ internal sealed class GeneratedLabelSymbol : LabelSymbol public GeneratedLabelSymbol(string name) { _name = LabelName(name); +#if DEBUG + NameNoSequence = $"<{name}>"; +#endif } public override string Name @@ -27,6 +30,8 @@ public override string Name } #if DEBUG + internal string NameNoSequence { get; } + private static int s_sequence = 1; #endif private static string LabelName(string name) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordBaseEquals.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordBaseEquals.cs index 81324e032a34a..f08019899df87 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordBaseEquals.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordBaseEquals.cs @@ -36,7 +36,7 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray( new SourceSimpleParameterSymbol(owner: this, TypeWithAnnotations.Create(ContainingType.BaseTypeNoUseSiteDiagnostics, NullableAnnotation.Annotated), - ordinal: 0, RefKind.None, "other", isDiscard: false, Locations)), + ordinal: 0, RefKind.None, "other", Locations)), IsVararg: false, DeclaredConstraintsForOverrideOrImplementation: ImmutableArray.Empty); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs index 7f11dd32a3436..baece16bb7dac 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -11,9 +12,11 @@ internal sealed class SynthesizedRecordConstructor : SourceConstructorSymbolBase { public SynthesizedRecordConstructor( SourceMemberContainerTypeSymbol containingType, - RecordDeclarationSyntax syntax) : + TypeDeclarationSyntax syntax) : base(containingType, syntax.Identifier.GetLocation(), syntax, isIterator: false) { + Debug.Assert(syntax.Kind() is SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration); + this.MakeFlags( MethodKind.Constructor, containingType.IsAbstract ? DeclarationModifiers.Protected : DeclarationModifiers.Public, @@ -28,11 +31,16 @@ internal RecordDeclarationSyntax GetSyntax() return (RecordDeclarationSyntax)syntaxReferenceOpt.GetSyntax(); } - protected override ParameterListSyntax GetParameterList() => GetSyntax().ParameterList!; + protected override ParameterListSyntax GetParameterList() + { + var record = GetSyntax(); + return record.ParameterList!; + } protected override CSharpSyntaxNode? GetInitializer() { - return GetSyntax().PrimaryConstructorBaseType; + var record = GetSyntax(); + return record.PrimaryConstructorBaseTypeIfClass; } protected override bool AllowRefOrOut => false; @@ -53,6 +61,7 @@ protected override bool IsWithinExpressionOrBlockBody(int position, out int offs internal override ExecutableCodeBinder TryGetBodyBinder(BinderFactory? binderFactoryOpt = null, bool ignoreAccessibility = false) { TypeDeclarationSyntax typeDecl = GetSyntax(); + Debug.Assert(typeDecl is RecordDeclarationSyntax); InMethodBinder result = (binderFactoryOpt ?? this.DeclaringCompilation.GetBinderFactory(typeDecl.SyntaxTree)).GetRecordConstructorInMethodBinder(this); return new ExecutableCodeBinder(SyntaxNode, this, result.WithAdditionalFlags(ignoreAccessibility ? BinderFlags.IgnoreAccessibility : BinderFlags.None)); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordDeconstruct.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordDeconstruct.cs index 5050364c932cb..cf3781f667da1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordDeconstruct.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordDeconstruct.cs @@ -2,12 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Reflection; -using Microsoft.Cci; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -16,19 +13,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols internal sealed class SynthesizedRecordDeconstruct : SynthesizedRecordOrdinaryMethod { private readonly SynthesizedRecordConstructor _ctor; - private readonly ImmutableArray _properties; + private readonly ImmutableArray _positionalMembers; public SynthesizedRecordDeconstruct( SourceMemberContainerTypeSymbol containingType, SynthesizedRecordConstructor ctor, - ImmutableArray properties, + ImmutableArray positionalMembers, int memberOffset, BindingDiagnosticBag diagnostics) : base(containingType, WellKnownMemberNames.DeconstructMethodName, hasBody: true, memberOffset, diagnostics) { - Debug.Assert(properties.All(prop => prop.GetMethod is object)); + Debug.Assert(positionalMembers.All(p => p is PropertySymbol { GetMethod: not null } or FieldSymbol)); _ctor = ctor; - _properties = properties; + _positionalMembers = positionalMembers; } protected override DeclarationModifiers MakeDeclarationModifiers(DeclarationModifiers allowedModifiers, BindingDiagnosticBag diagnostics) @@ -50,7 +47,6 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray.GetInstance(_properties.Length + 1); - for (int i = 0; i < _properties.Length; i++) + var statementsBuilder = ArrayBuilder.GetInstance(_positionalMembers.Length + 1); + for (int i = 0; i < _positionalMembers.Length; i++) { var parameter = Parameters[i]; - var property = _properties[i]; + var positionalMember = _positionalMembers[i]; - if (!parameter.Type.Equals(property.Type, TypeCompareKind.AllIgnoreOptions)) + var type = positionalMember switch + { + PropertySymbol property => property.Type, + FieldSymbol field => field.Type, + _ => throw ExceptionUtilities.Unreachable + }; + + if (!parameter.Type.Equals(type, TypeCompareKind.AllIgnoreOptions)) { // There is a mismatch, an error was reported elsewhere statementsBuilder.Free(); @@ -84,8 +87,17 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, return; } - // parameter_i = property_i; - statementsBuilder.Add(F.Assignment(F.Parameter(parameter), F.Property(F.This(), property))); + switch (positionalMember) + { + case PropertySymbol property: + // parameter_i = property_i; + statementsBuilder.Add(F.Assignment(F.Parameter(parameter), F.Property(F.This(), property))); + break; + case FieldSymbol field: + // parameter_i = field_i; + statementsBuilder.Add(F.Assignment(F.Parameter(parameter), F.Field(F.This(), field))); + break; + } } statementsBuilder.Add(F.Return()); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperator.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperator.cs index 004086463a319..90ef9aedfa034 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperator.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperator.cs @@ -2,22 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; -using System.Globalization; -using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Roslyn.Utilities; - namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// /// The record type includes synthesized '==' and '!=' operators equivalent to operators declared as follows: - /// + /// + /// For record class: /// public static bool operator==(R? left, R? right) /// => (object) left == right || ((object)left != null && left.Equals(right)); /// public static bool operator !=(R? left, R? right) /// => !(left == right); - /// + /// + /// For record struct: + /// public static bool operator==(R left, R right) + /// => left.Equals(right); + /// public static bool operator !=(R left, R right) + /// => !(left == right); + /// ///The 'Equals' method called by the '==' operator is the 'Equals(R? other)' (). ///The '!=' operator delegates to the '==' operator. It is an error if the operators are declared explicitly. /// @@ -34,7 +35,10 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, try { + // For record class: // => (object)left == right || ((object)left != null && left.Equals(right)); + // For record struct: + // => left.Equals(right)); MethodSymbol? equals = null; foreach (var member in ContainingType.GetMembers(WellKnownMemberNames.ObjectEquals)) { @@ -57,11 +61,20 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, var left = F.Parameter(Parameters[0]); var right = F.Parameter(Parameters[1]); - BoundExpression objectEqual = F.ObjectEqual(left, right); - BoundExpression recordEquals = F.LogicalAnd(F.ObjectNotEqual(left, F.Null(F.SpecialType(SpecialType.System_Object))), + BoundExpression expression; + if (ContainingType.IsRecordStruct) + { + expression = F.Call(left, equals, right); + } + else + { + BoundExpression objectEqual = F.ObjectEqual(left, right); + BoundExpression recordEquals = F.LogicalAnd(F.ObjectNotEqual(left, F.Null(F.SpecialType(SpecialType.System_Object))), F.Call(left, equals, right)); + expression = F.LogicalOr(objectEqual, recordEquals); + } - F.CloseMethod(F.Block(F.Return(F.LogicalOr(objectEqual, recordEquals)))); + F.CloseMethod(F.Block(F.Return(expression))); } catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperatorBase.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperatorBase.cs index 205e01c058cea..22587d4cbd5db 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperatorBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityOperatorBase.cs @@ -13,12 +13,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// /// The record type includes synthesized '==' and '!=' operators equivalent to operators declared as follows: - /// + /// + /// For record class: /// public static bool operator==(R? left, R? right) /// => (object) left == right || ((object)left != null && left.Equals(right)); /// public static bool operator !=(R? left, R? right) /// => !(left == right); - /// + /// + /// For record struct: + /// public static bool operator==(R left, R right) + /// => left.Equals(right); + /// public static bool operator !=(R left, R right) + /// => !(left == right); + /// ///The 'Equals' method called by the '==' operator is the 'Equals(R? other)' (). ///The '!=' operator delegates to the '==' operator. It is an error if the operators are declared explicitly. /// @@ -54,14 +61,15 @@ protected sealed override (TypeWithAnnotations ReturnType, ImmutableArray( new SourceSimpleParameterSymbol(owner: this, - TypeWithAnnotations.Create(ContainingType, NullableAnnotation.Annotated), - ordinal: 0, RefKind.None, "left", isDiscard: false, Locations), + TypeWithAnnotations.Create(ContainingType, annotation), + ordinal: 0, RefKind.None, "left", Locations), new SourceSimpleParameterSymbol(owner: this, - TypeWithAnnotations.Create(ContainingType, NullableAnnotation.Annotated), - ordinal: 1, RefKind.None, "right", isDiscard: false, Locations))); + TypeWithAnnotations.Create(ContainingType, annotation), + ordinal: 1, RefKind.None, "right", Locations))); } protected override int GetParameterCountFromSyntax() => 2; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs index 81e8b3bbb1961..f42c39158282d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs @@ -2,13 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Reflection; -using Microsoft.Cci; using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -20,11 +16,12 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// internal sealed class SynthesizedRecordEquals : SynthesizedRecordOrdinaryMethod { - private readonly PropertySymbol _equalityContract; + private readonly PropertySymbol? _equalityContract; - public SynthesizedRecordEquals(SourceMemberContainerTypeSymbol containingType, PropertySymbol equalityContract, int memberOffset, BindingDiagnosticBag diagnostics) + public SynthesizedRecordEquals(SourceMemberContainerTypeSymbol containingType, PropertySymbol? equalityContract, int memberOffset, BindingDiagnosticBag diagnostics) : base(containingType, WellKnownMemberNames.ObjectEquals, hasBody: true, memberOffset, diagnostics) { + Debug.Assert(equalityContract is null == containingType.IsRecordStruct); _equalityContract = equalityContract; } @@ -35,15 +32,17 @@ protected override DeclarationModifiers MakeDeclarationModifiers(DeclarationModi return result; } - protected override (TypeWithAnnotations ReturnType, ImmutableArray Parameters, bool IsVararg, ImmutableArray DeclaredConstraintsForOverrideOrImplementation) MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics) + protected override (TypeWithAnnotations ReturnType, ImmutableArray Parameters, bool IsVararg, ImmutableArray DeclaredConstraintsForOverrideOrImplementation) + MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics) { var compilation = DeclaringCompilation; var location = ReturnTypeLocation; + var annotation = ContainingType.IsRecordStruct ? NullableAnnotation.Oblivious : NullableAnnotation.Annotated; return (ReturnType: TypeWithAnnotations.Create(Binder.GetSpecialType(compilation, SpecialType.System_Boolean, location, diagnostics)), Parameters: ImmutableArray.Create( new SourceSimpleParameterSymbol(owner: this, - TypeWithAnnotations.Create(ContainingType, NullableAnnotation.Annotated), - ordinal: 0, RefKind.None, "other", isDiscard: false, Locations)), + TypeWithAnnotations.Create(ContainingType, annotation), + ordinal: 0, RefKind.None, "other", Locations)), IsVararg: false, DeclaredConstraintsForOverrideOrImplementation: ImmutableArray.Empty); } @@ -62,8 +61,18 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, // This method is the strongly-typed Equals method where the parameter type is // the containing type. - if (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType()) + bool isRecordStruct = ContainingType.IsRecordStruct; + if (isRecordStruct) { + // We'll produce: + // bool Equals(T other) => + // field1 == other.field1 && ... && fieldN == other.fieldN; + // or simply true if no fields. + retExpr = null; + } + else if (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType()) + { + Debug.Assert(_equalityContract is not null); if (_equalityContract.GetMethod is null) { // The equality contract isn't usable, an error was reported elsewhere @@ -145,6 +154,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, } } } + if (fields.Count > 0 && !foundBadField) { retExpr = MethodBodySynthesizer.GenerateFieldEquals( @@ -153,9 +163,18 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, fields, F); } + else if (retExpr is null) + { + retExpr = F.Literal(true); + } fields.Free(); - retExpr = F.LogicalOr(F.ObjectEqual(F.This(), other), retExpr); + + if (!isRecordStruct) + { + retExpr = F.LogicalOr(F.ObjectEqual(F.This(), other), retExpr); + } + F.CloseMethod(F.Block(F.Return(retExpr))); } catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordGetHashCode.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordGetHashCode.cs index a2f280126324e..b171b39e3b5ed 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordGetHashCode.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordGetHashCode.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Reflection; -using Microsoft.Cci; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -17,17 +15,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// internal sealed class SynthesizedRecordGetHashCode : SynthesizedRecordObjectMethod { - private readonly PropertySymbol _equalityContract; + private readonly PropertySymbol? _equalityContract; - public SynthesizedRecordGetHashCode(SourceMemberContainerTypeSymbol containingType, PropertySymbol equalityContract, int memberOffset, BindingDiagnosticBag diagnostics) + public SynthesizedRecordGetHashCode(SourceMemberContainerTypeSymbol containingType, PropertySymbol? equalityContract, int memberOffset, BindingDiagnosticBag diagnostics) : base(containingType, WellKnownMemberNames.ObjectGetHashCode, memberOffset, diagnostics) { + Debug.Assert(containingType.IsRecordStruct == equalityContract is null); _equalityContract = equalityContract; } protected override SpecialMember OverriddenSpecialMember => SpecialMember.System_Object__GetHashCode; - protected override (TypeWithAnnotations ReturnType, ImmutableArray Parameters, bool IsVararg, ImmutableArray DeclaredConstraintsForOverrideOrImplementation) MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics) + protected override (TypeWithAnnotations ReturnType, ImmutableArray Parameters, bool IsVararg, ImmutableArray DeclaredConstraintsForOverrideOrImplementation) + MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics) { var compilation = DeclaringCompilation; var location = ReturnTypeLocation; @@ -47,10 +47,15 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, { MethodSymbol? equalityComparer_GetHashCode = null; MethodSymbol? equalityComparer_get_Default = null; - BoundExpression currentHashValue; + BoundExpression? currentHashValue; - if (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType()) + if (ContainingType.IsRecordStruct) { + currentHashValue = null; + } + else if (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType()) + { + Debug.Assert(_equalityContract is not null); if (_equalityContract.GetMethod is null) { // The equality contract isn't usable, an error was reported elsewhere @@ -67,7 +72,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, // There are no base record types. // Get hash code of the equality contract and combine it with hash codes for field values. ensureEqualityComparerHelpers(F, ref equalityComparer_GetHashCode, ref equalityComparer_get_Default); - currentHashValue = MethodBodySynthesizer.GenerateGetHashCode(equalityComparer_GetHashCode!, equalityComparer_get_Default!, F.Property(F.This(), _equalityContract), F); + currentHashValue = MethodBodySynthesizer.GenerateGetHashCode(equalityComparer_GetHashCode, equalityComparer_get_Default, F.Property(F.This(), _equalityContract), F); } else { @@ -93,12 +98,25 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, if (!f.IsStatic) { ensureEqualityComparerHelpers(F, ref equalityComparer_GetHashCode, ref equalityComparer_get_Default); - currentHashValue = MethodBodySynthesizer.GenerateHashCombine(currentHashValue, equalityComparer_GetHashCode!, equalityComparer_get_Default!, ref boundHashFactor, - F.Field(F.This(), f), - F); + if (currentHashValue is null) + { + // EqualityComparer.Default.GetHashCode(this.field) + currentHashValue = MethodBodySynthesizer.GenerateGetHashCode(equalityComparer_GetHashCode, equalityComparer_get_Default, F.Field(F.This(), f), F); + } + else + { + currentHashValue = MethodBodySynthesizer.GenerateHashCombine(currentHashValue, equalityComparer_GetHashCode, equalityComparer_get_Default, ref boundHashFactor, + F.Field(F.This(), f), + F); + } } } + if (currentHashValue is null) + { + currentHashValue = F.Literal(0); + } + F.CloseMethod(F.Block(F.Return(currentHashValue))); } catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) @@ -107,7 +125,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, F.CloseMethod(F.ThrowNull()); } - static void ensureEqualityComparerHelpers(SyntheticBoundNodeFactory F, ref MethodSymbol? equalityComparer_GetHashCode, ref MethodSymbol? equalityComparer_get_Default) + static void ensureEqualityComparerHelpers(SyntheticBoundNodeFactory F, [NotNull] ref MethodSymbol? equalityComparer_GetHashCode, [NotNull] ref MethodSymbol? equalityComparer_get_Default) { equalityComparer_GetHashCode ??= F.WellKnownMethod(WellKnownMember.System_Collections_Generic_EqualityComparer_T__GetHashCode); equalityComparer_get_Default ??= F.WellKnownMethod(WellKnownMember.System_Collections_Generic_EqualityComparer_T__get_Default); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordInequalityOperator.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordInequalityOperator.cs index 6292f422cfc07..66f78f0bc4f54 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordInequalityOperator.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordInequalityOperator.cs @@ -13,12 +13,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// /// The record type includes synthesized '==' and '!=' operators equivalent to operators declared as follows: - /// + /// + /// For record class: /// public static bool operator==(R? left, R? right) /// => (object) left == right || ((object)left != null && left.Equals(right)); /// public static bool operator !=(R? left, R? right) /// => !(left == right); - /// + /// + /// For record struct: + /// public static bool operator==(R left, R right) + /// => left.Equals(right); + /// public static bool operator !=(R left, R right) + /// => !(left == right); + /// ///The 'Equals' method called by the '==' operator is the 'Equals(R? other)' (). ///The '!=' operator delegates to the '==' operator. It is an error if the operators are declared explicitly. /// diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs index 33d60a82e996e..134c05c286a9c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -24,15 +23,17 @@ public SynthesizedRecordObjEquals(SourceMemberContainerTypeSymbol containingType protected override SpecialMember OverriddenSpecialMember => SpecialMember.System_Object__Equals; - protected override (TypeWithAnnotations ReturnType, ImmutableArray Parameters, bool IsVararg, ImmutableArray DeclaredConstraintsForOverrideOrImplementation) MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics) + protected override (TypeWithAnnotations ReturnType, ImmutableArray Parameters, bool IsVararg, ImmutableArray DeclaredConstraintsForOverrideOrImplementation) + MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics) { var compilation = DeclaringCompilation; var location = ReturnTypeLocation; + var annotation = ContainingType.IsRecordStruct ? NullableAnnotation.Oblivious : NullableAnnotation.Annotated; return (ReturnType: TypeWithAnnotations.Create(Binder.GetSpecialType(compilation, SpecialType.System_Boolean, location, diagnostics)), Parameters: ImmutableArray.Create( new SourceSimpleParameterSymbol(owner: this, - TypeWithAnnotations.Create(Binder.GetSpecialType(compilation, SpecialType.System_Object, location, diagnostics), NullableAnnotation.Annotated), - ordinal: 0, RefKind.None, "obj", isDiscard: false, Locations)), + TypeWithAnnotations.Create(Binder.GetSpecialType(compilation, SpecialType.System_Object, location, diagnostics), annotation), + ordinal: 0, RefKind.None, "obj", Locations)), IsVararg: false, DeclaredConstraintsForOverrideOrImplementation: ImmutableArray.Empty); } @@ -45,23 +46,27 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, try { + if (_typedRecordEquals.ReturnType.SpecialType != SpecialType.System_Boolean) + { + // There is a signature mismatch, an error was reported elsewhere + F.CloseMethod(F.ThrowNull()); + return; + } + var paramAccess = F.Parameter(Parameters[0]); BoundExpression expression; - if (ContainingType.IsStructType()) + if (ContainingType.IsRecordStruct) { - throw ExceptionUtilities.Unreachable; + // For record structs: + // return other is R && Equals((R)other) + expression = F.LogicalAnd( + F.Is(paramAccess, ContainingType), + F.Call(F.This(), _typedRecordEquals, F.Convert(ContainingType, paramAccess))); } else { - if (_typedRecordEquals.ReturnType.SpecialType != SpecialType.System_Boolean) - { - // There is a signature mismatch, an error was reported elsewhere - F.CloseMethod(F.ThrowNull()); - return; - } - - // For classes: + // For record classes: // return this.Equals(param as ContainingType); expression = F.Call(F.This(), _typedRecordEquals, F.As(paramAccess, ContainingType)); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs index c1d1e92d24953..3f05883416741 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs @@ -29,11 +29,11 @@ public SynthesizedRecordPrintMembers( protected override DeclarationModifiers MakeDeclarationModifiers(DeclarationModifiers allowedModifiers, BindingDiagnosticBag diagnostics) { - var result = (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType() && ContainingType.IsSealed) ? + var result = (ContainingType.IsRecordStruct || (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType() && ContainingType.IsSealed)) ? DeclarationModifiers.Private : DeclarationModifiers.Protected; - if (virtualPrintInBase() is object) + if (ContainingType.IsRecord && !ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType()) { result |= DeclarationModifiers.Override; } @@ -48,21 +48,14 @@ protected override DeclarationModifiers MakeDeclarationModifiers(DeclarationModi #endif return result; - MethodSymbol? virtualPrintInBase() +#if DEBUG + bool modifiersAreValid(DeclarationModifiers modifiers) { - NamedTypeSymbol baseType = ContainingType.BaseTypeNoUseSiteDiagnostics; - - if (!baseType.IsObjectType()) + if (ContainingType.IsRecordStruct) { - return FindValidPrintMembersMethod(baseType, ContainingType.DeclaringCompilation); + return modifiers == DeclarationModifiers.Private; } - return null; - } - -#if DEBUG - static bool modifiersAreValid(DeclarationModifiers modifiers) - { if ((modifiers & DeclarationModifiers.AccessibilityMask) != DeclarationModifiers.Private && (modifiers & DeclarationModifiers.AccessibilityMask) != DeclarationModifiers.Protected) { @@ -88,17 +81,31 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray( new SourceSimpleParameterSymbol(owner: this, - TypeWithAnnotations.Create(Binder.GetWellKnownType(compilation, WellKnownType.System_Text_StringBuilder, diagnostics, location), NullableAnnotation.NotAnnotated), - ordinal: 0, RefKind.None, "builder", isDiscard: false, Locations)), + TypeWithAnnotations.Create(Binder.GetWellKnownType(compilation, WellKnownType.System_Text_StringBuilder, diagnostics, location), annotation), + ordinal: 0, RefKind.None, "builder", Locations)), IsVararg: false, DeclaredConstraintsForOverrideOrImplementation: ImmutableArray.Empty); } protected override int GetParameterCountFromSyntax() => 1; + protected override void MethodChecks(BindingDiagnosticBag diagnostics) + { + base.MethodChecks(diagnostics); + + var overridden = OverriddenMethod; + + if (overridden is object && + !overridden.ContainingType.Equals(ContainingType.BaseTypeNoUseSiteDiagnostics, TypeCompareKind.AllIgnoreOptions)) + { + diagnostics.Add(ErrorCode.ERR_DoesNotOverrideBaseMethod, Locations[0], this, ContainingType.BaseTypeNoUseSiteDiagnostics); + } + } + internal override void GenerateMethodBody(TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { var F = new SyntheticBoundNodeFactory(this, ContainingType.GetNonNullSyntaxNode(), compilationState, diagnostics); @@ -113,9 +120,9 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, return; } - ArrayBuilder? block = printableMembers.IsEmpty ? null : ArrayBuilder.GetInstance(); + ArrayBuilder block; BoundParameter builder = F.Parameter(this.Parameters[0]); - if (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType()) + if (ContainingType.BaseTypeNoUseSiteDiagnostics.IsObjectType() || ContainingType.IsRecordStruct) { if (printableMembers.IsEmpty) { @@ -123,17 +130,19 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, F.CloseMethod(F.Return(F.Literal(false))); return; } + block = ArrayBuilder.GetInstance(); } else { - MethodSymbol? printMethod = FindValidPrintMembersMethod(ContainingType.BaseTypeNoUseSiteDiagnostics, DeclaringCompilation); - if (printMethod is null) + MethodSymbol? basePrintMethod = OverriddenMethod; + if (basePrintMethod is null || + basePrintMethod.ReturnType.SpecialType != SpecialType.System_Boolean) { F.CloseMethod(F.ThrowNull()); // an error was reported in base checks already return; } - var basePrintCall = F.Call(receiver: F.Base(ContainingType.BaseTypeNoUseSiteDiagnostics), printMethod, builder); + var basePrintCall = F.Call(receiver: F.Base(ContainingType.BaseTypeNoUseSiteDiagnostics), basePrintMethod, builder); if (printableMembers.IsEmpty) { // return base.PrintMembers(builder); @@ -142,13 +151,14 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, } else { + block = ArrayBuilder.GetInstance(); // if (base.PrintMembers(builder)) // builder.Append(", ") - block!.Add(F.If(basePrintCall, makeAppendString(F, builder, ", "))); + block.Add(F.If(basePrintCall, makeAppendString(F, builder, ", "))); } } - Debug.Assert(!printableMembers.IsEmpty && block is object); + Debug.Assert(!printableMembers.IsEmpty); for (var i = 0; i < printableMembers.Length; i++) { @@ -227,41 +237,6 @@ static bool isPrintable(Symbol m) } } - internal static MethodSymbol? FindValidPrintMembersMethod(TypeSymbol containingType, CSharpCompilation compilation) - { - if (containingType.IsObjectType()) - { - return null; - } - - MethodSymbol? candidate = null; - var stringBuilder = TypeWithAnnotations.Create(compilation.GetWellKnownType(WellKnownType.System_Text_StringBuilder)); - - foreach (var member in containingType.GetMembers(WellKnownMemberNames.PrintMembersMethodName)) - { - if (member is MethodSymbol { DeclaredAccessibility: Accessibility.Protected, IsStatic: false, ParameterCount: 1, Arity: 0 } method && - method.ParameterTypesWithAnnotations[0].Equals(stringBuilder, TypeCompareKind.AllIgnoreOptions)) - { - if (candidate is object) - { - // An ambiguity case, can come from metadata, treat as an error for simplicity. - return null; - } - - candidate = method; - } - } - - if (candidate is null || - !(containingType.IsSealed || candidate.IsOverride || candidate.IsVirtual) || - candidate.ReturnType.SpecialType != SpecialType.System_Boolean) - { - return null; - } - - return candidate; - } - internal static void VerifyOverridesPrintMembersFromBase(MethodSymbol overriding, BindingDiagnosticBag diagnostics) { NamedTypeSymbol baseType = overriding.ContainingType.BaseTypeNoUseSiteDiagnostics; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs index 1ff36f826ebf2..496f928ffbcfa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -30,7 +30,7 @@ public SynthesizedRecordPropertySymbol( hasInitializer: true, // Synthesized record properties always have a synthesized initializer isAutoProperty: true, isExpressionBodied: false, - isInitOnly: true, + isInitOnly: ShouldUseInit(containingType), RefKind.None, backingParameter.Name, indexerNameAttributeLists: new SyntaxList(), @@ -60,14 +60,21 @@ protected override SourcePropertyAccessorSymbol CreateSetAccessorSymbol(bool isA return CreateAccessorSymbol(isGet: false, CSharpSyntaxNode, diagnostics); } + private static bool ShouldUseInit(TypeSymbol container) + { + // the setter is always init-only in record class and in readonly record struct + return !container.IsStructType() || container.IsReadOnly; + } + private SourcePropertyAccessorSymbol CreateAccessorSymbol( bool isGet, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics) { + var usesInit = !isGet && ShouldUseInit(ContainingType); return SourcePropertyAccessorSymbol.CreateAccessorSymbol( isGet, - usesInit: !isGet, // the setter is always init-only + usesInit, ContainingType, this, _modifiers, diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs index 1e67686ae0cd8..17f70250dc4fe 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs @@ -35,7 +35,8 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray.Empty, IsVararg: false, DeclaredConstraintsForOverrideOrImplementation: ImmutableArray.Empty); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs index 73bec1612d0ba..6a428d3b1d7cf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs @@ -89,6 +89,7 @@ public sealed override bool AreLocalsZeroed } internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; private sealed class DelegateConstructor : SynthesizedInstanceConstructor diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs index fe1d12e16e612..a7dea9cdb4ffd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs @@ -201,6 +201,7 @@ public SynthesizedEmbeddedAttributeSymbol( public override ImmutableArray Constructors => _constructors; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNativeIntegerAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNativeIntegerAttributeSymbol.cs index 83f6876dcd985..a88f0f36891f6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNativeIntegerAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNativeIntegerAttributeSymbol.cs @@ -62,6 +62,7 @@ public SynthesizedEmbeddedNativeIntegerAttributeSymbol( public override ImmutableArray Constructors => _constructors; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; internal override AttributeUsageInfo GetAttributeUsageInfo() diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs index 0c6b76e41284d..f8bed1d223ed8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs @@ -66,6 +66,7 @@ public SynthesizedEmbeddedNullableAttributeSymbol( public override ImmutableArray Constructors => _constructors; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; internal override AttributeUsageInfo GetAttributeUsageInfo() diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableContextAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableContextAttributeSymbol.cs index 135cbe84eb2f3..d3dfaf267e8d1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableContextAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableContextAttributeSymbol.cs @@ -50,6 +50,7 @@ public SynthesizedEmbeddedNullableContextAttributeSymbol( public override ImmutableArray Constructors => _constructors; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; internal override AttributeUsageInfo GetAttributeUsageInfo() diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol.cs index 9f82052ed245f..f679c752f0bcc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol.cs @@ -50,6 +50,7 @@ public SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol( public override ImmutableArray Constructors => _constructors; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; internal override AttributeUsageInfo GetAttributeUsageInfo() diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 5bdc3b670364f..6778b714ce4a4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -449,10 +449,7 @@ internal override BoundBlock CreateBody(BindingDiagnosticBag diagnostics) // Creates a new top-level binder that just contains the global imports for the compilation. // The imports are required if a consumer of the scripting API is using a Task implementation // that uses extension methods. - var binder = new InContainerBinder( - container: null, - next: new BuckStopsHereBinder(compilation), - imports: compilation.GlobalImports); + Binder binder = WithUsingNamespacesAndTypesBinder.Create(compilation.GlobalImports, next: new BuckStopsHereBinder(compilation), withImportChainEntry: true); binder = new InContainerBinder(compilation.GlobalNamespace, binder); var ctor = _containingType.GetScriptConstructor(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index 9d39714d7c0ea..64ced300122d1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Emit; @@ -84,27 +83,27 @@ internal override ConstantValue? ExplicitDefaultConstantValue internal override bool IsIDispatchConstant { - get { return false; } + get { throw ExceptionUtilities.Unreachable; } } internal override bool IsIUnknownConstant { - get { return false; } + get { throw ExceptionUtilities.Unreachable; } } internal override bool IsCallerLineNumber { - get { return false; } + get { throw ExceptionUtilities.Unreachable; } } internal override bool IsCallerFilePath { - get { return false; } + get { throw ExceptionUtilities.Unreachable; } } internal override bool IsCallerMemberName { - get { return false; } + get { throw ExceptionUtilities.Unreachable; } } internal override FlowAnalysisAnnotations FlowAnalysisAnnotations @@ -285,5 +284,27 @@ public override ImmutableArray GetAttributes() public bool HasEnumeratorCancellationAttribute => _baseParameterForAttributes?.HasEnumeratorCancellationAttribute ?? false; internal override MarshalPseudoCustomAttributeData? MarshallingInformation => _baseParameterForAttributes?.MarshallingInformation; + + internal override bool IsMetadataOptional => _baseParameterForAttributes?.IsMetadataOptional == true; + + internal override ConstantValue? ExplicitDefaultConstantValue => _baseParameterForAttributes?.ExplicitDefaultConstantValue; + + internal override FlowAnalysisAnnotations FlowAnalysisAnnotations + { + get + { + Debug.Assert(_baseParameterForAttributes is null); + return base.FlowAnalysisAnnotations; + } + } + + internal override ImmutableHashSet NotNullIfParameterNotNull + { + get + { + Debug.Assert(_baseParameterForAttributes is null); + return base.NotNullIfParameterNotNull; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs index 121af2b8a5232..b0d83d6c24ea7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs @@ -195,12 +195,16 @@ private ExecutableCodeBinder CreateBodyBinder(bool ignoreAccessibility) CSharpCompilation compilation = DeclaringCompilation; Binder result = new BuckStopsHereBinder(compilation); - result = new InContainerBinder(compilation.GlobalNamespace, result, SyntaxNode, inUsing: false); + var globalNamespace = compilation.GlobalNamespace; + var declaringSymbol = (SourceNamespaceSymbol)compilation.SourceModule.GlobalNamespace; + var syntaxNode = SyntaxNode; + result = WithExternAndUsingAliasesBinder.Create(declaringSymbol, syntaxNode, WithUsingNamespacesAndTypesBinder.Create(declaringSymbol, syntaxNode, result)); + result = new InContainerBinder(globalNamespace, result); result = new InContainerBinder(ContainingType, result); result = new InMethodBinder(this, result); result = result.WithAdditionalFlags(ignoreAccessibility ? BinderFlags.IgnoreAccessibility : BinderFlags.None); - return new ExecutableCodeBinder(SyntaxNode, this, result); + return new ExecutableCodeBinder(syntaxNode, this, result); } internal ExecutableCodeBinder GetBodyBinder(bool ignoreAccessibility) diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs index e63b07303b0d7..f492c1ca1b57a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs @@ -692,5 +692,7 @@ protected sealed override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnn } internal override bool IsRecord => false; + + internal override bool IsRecordStruct => false; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs index 44ba88eb60fe8..12d51cf25be87 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs @@ -2254,5 +2254,7 @@ ITypeSymbol ITypeSymbolInternal.GetITypeSymbol() } internal abstract bool IsRecord { get; } + + internal abstract bool IsRecordStruct { get; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index cab3810b084f9..a875bfa14eed4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -366,9 +366,9 @@ public static bool IsPointerOrFunctionPointer(this TypeSymbol type) // If the type is a delegate type, it returns it. If the type is an // expression tree type associated with a delegate type, it returns // the delegate type. Otherwise, null. - public static NamedTypeSymbol? GetDelegateType(this TypeSymbol type) + public static NamedTypeSymbol? GetDelegateType(this TypeSymbol? type) { - if ((object)type == null) return null; + if (type is null) return null; if (type.IsExpressionTree()) { type = ((NamedTypeSymbol)type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; @@ -377,7 +377,7 @@ public static bool IsPointerOrFunctionPointer(this TypeSymbol type) return type.IsDelegateType() ? (NamedTypeSymbol)type : null; } - public static TypeSymbol? GetDelegateOrFunctionPointerType(this TypeSymbol type) + public static TypeSymbol? GetDelegateOrFunctionPointerType(this TypeSymbol? type) { return (TypeSymbol?)GetDelegateType(type) ?? type as FunctionPointerTypeSymbol; } @@ -385,23 +385,45 @@ public static bool IsPointerOrFunctionPointer(this TypeSymbol type) /// /// return true if the type is constructed from System.Linq.Expressions.Expression`1 /// - public static bool IsExpressionTree(this TypeSymbol _type) + public static bool IsExpressionTree(this TypeSymbol type) { - return _type.OriginalDefinition is NamedTypeSymbol type && - type.Arity == 1 && - type.MangleName && - type.Name == "Expression" && - CheckFullName(type.ContainingSymbol, s_expressionsNamespaceName); + return type.IsGenericOrNonGenericExpressionType(out bool isGenericType) && isGenericType; + } + + public static bool IsNonGenericExpressionType(this TypeSymbol type) + { + return type.IsGenericOrNonGenericExpressionType(out bool isGenericType) && !isGenericType; } + public static bool IsGenericOrNonGenericExpressionType(this TypeSymbol _type, out bool isGenericType) + { + if (_type.OriginalDefinition is NamedTypeSymbol type && + type.Name == "Expression" && + CheckFullName(type.ContainingSymbol, s_expressionsNamespaceName)) + { + if (type.Arity == 0) + { + isGenericType = false; + return true; + } + if (type.Arity == 1 && + type.MangleName) + { + isGenericType = true; + return true; + } + } + isGenericType = false; + return false; + } /// /// return true if the type is constructed from a generic interface that /// might be implemented by an array. /// - public static bool IsPossibleArrayGenericInterface(this TypeSymbol _type) + public static bool IsPossibleArrayGenericInterface(this TypeSymbol type) { - if (!(_type is NamedTypeSymbol t)) + if (!(type is NamedTypeSymbol t)) { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs b/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs index 3ba531b3cadbb..672c03a26d3c2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs @@ -87,7 +87,7 @@ public TypeWithAnnotations ToTypeWithAnnotations(CSharpCompilation compilation, if (Type?.IsTypeParameterDisallowingAnnotationInCSharp8() == true) { var type = TypeWithAnnotations.Create(Type, NullableAnnotation.NotAnnotated); - return State == NullableFlowState.MaybeDefault ? type.SetIsAnnotated(compilation) : type; + return (State == NullableFlowState.MaybeDefault || asAnnotatedType) ? type.SetIsAnnotated(compilation) : type; } NullableAnnotation annotation = asAnnotatedType ? (Type?.IsValueType == true ? NullableAnnotation.NotAnnotated : NullableAnnotation.Annotated) : diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpLineDirectiveMap.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpLineDirectiveMap.cs index 8e2ca05e556be..a254beef31ac5 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpLineDirectiveMap.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpLineDirectiveMap.cs @@ -135,6 +135,10 @@ public override LineVisibility GetLineVisibility(SourceText sourceText, int posi } } + // C# does not have unknown visibility state + protected override LineVisibility GetUnknownStateVisibility(int index) + => throw ExceptionUtilities.Unreachable; + internal override FileLinePositionSpan TranslateSpanAndVisibility(SourceText sourceText, string treeFilePath, TextSpan span, out bool isHiddenPosition) { var lines = sourceText.Lines; diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs index 61e9d55c79a11..4f15d219b8fa6 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs @@ -8,6 +8,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; @@ -607,6 +608,17 @@ public override IList GetChanges(SyntaxTree oldTree) #region LinePositions and Locations + private CSharpLineDirectiveMap GetDirectiveMap() + { + if (_lazyLineDirectiveMap == null) + { + // Create the line directive map on demand. + Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null); + } + + return _lazyLineDirectiveMap; + } + /// /// Gets the location in terms of path, line and column for a given span. /// @@ -617,9 +629,7 @@ public override IList GetChanges(SyntaxTree oldTree) /// /// The values are not affected by line mapping directives (#line). public override FileLinePositionSpan GetLineSpan(TextSpan span, CancellationToken cancellationToken = default) - { - return new FileLinePositionSpan(this.FilePath, GetLinePosition(span.Start), GetLinePosition(span.End)); - } + => new(FilePath, GetLinePosition(span.Start, cancellationToken), GetLinePosition(span.End, cancellationToken)); /// /// Gets the location in terms of path, line and column after applying source line mapping directives (#line). @@ -638,25 +648,18 @@ public override FileLinePositionSpan GetLineSpan(TextSpan span, CancellationToke /// /// public override FileLinePositionSpan GetMappedLineSpan(TextSpan span, CancellationToken cancellationToken = default) - { - if (_lazyLineDirectiveMap == null) - { - // Create the line directive map on demand. - Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null); - } - - return _lazyLineDirectiveMap.TranslateSpan(this.GetText(cancellationToken), this.FilePath, span); - } + => GetDirectiveMap().TranslateSpan(GetText(cancellationToken), this.FilePath, span); + /// public override LineVisibility GetLineVisibility(int position, CancellationToken cancellationToken = default) - { - if (_lazyLineDirectiveMap == null) - { - // Create the line directive map on demand. - Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null); - } + => GetDirectiveMap().GetLineVisibility(GetText(cancellationToken), position); - return _lazyLineDirectiveMap.GetLineVisibility(this.GetText(cancellationToken), position); + /// + public override IEnumerable GetLineMappings(CancellationToken cancellationToken = default) + { + var map = GetDirectiveMap(); + Debug.Assert(map.Entries.Length >= 1); + return (map.Entries.Length == 1) ? Array.Empty() : map.GetLineMappings(GetText(cancellationToken).Lines); } /// @@ -667,30 +670,14 @@ public override LineVisibility GetLineVisibility(int position, CancellationToken /// When the method returns, contains a boolean value indicating whether this span is considered hidden or not. /// A resulting . internal override FileLinePositionSpan GetMappedLineSpanAndVisibility(TextSpan span, out bool isHiddenPosition) - { - if (_lazyLineDirectiveMap == null) - { - // Create the line directive map on demand. - Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null); - } - - return _lazyLineDirectiveMap.TranslateSpanAndVisibility(this.GetText(), this.FilePath, span, out isHiddenPosition); - } + => GetDirectiveMap().TranslateSpanAndVisibility(GetText(), FilePath, span, out isHiddenPosition); /// /// Gets a boolean value indicating whether there are any hidden regions in the tree. /// /// True if there is at least one hidden region. public override bool HasHiddenRegions() - { - if (_lazyLineDirectiveMap == null) - { - // Create the line directive map on demand. - Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null); - } - - return _lazyLineDirectiveMap.HasAnyHiddenRegions(); - } + => GetDirectiveMap().HasAnyHiddenRegions(); /// /// Given the error code and the source location, get the warning state based on #pragma warning directives. @@ -756,10 +743,8 @@ bool isGeneratedHeuristic() private GeneratedKind _lazyIsGeneratedCode = GeneratedKind.Unknown; - private LinePosition GetLinePosition(int position) - { - return this.GetText().Lines.GetLinePosition(position); - } + private LinePosition GetLinePosition(int position, CancellationToken cancellationToken) + => GetText(cancellationToken).Lines.GetLinePosition(position); /// /// Gets a for the specified text . diff --git a/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs b/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs index 734feab9d1f65..769b6f96b50a9 100644 --- a/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs +++ b/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs @@ -417,6 +417,7 @@ internal static bool IsClosureScope(SyntaxNode node) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: // With dynamic analysis instrumentation, a type declaration can be the syntax associated // with the analysis payload local of a synthesized constructor. // If the synthesized constructor includes an initializer with a lambda, diff --git a/src/Compilers/CSharp/Portable/Syntax/ParenthesizedLambdaExpressionSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/ParenthesizedLambdaExpressionSyntax.cs index d5873b14117e0..64a454143eb49 100644 --- a/src/Compilers/CSharp/Portable/Syntax/ParenthesizedLambdaExpressionSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/ParenthesizedLambdaExpressionSyntax.cs @@ -29,6 +29,9 @@ internal override AnonymousFunctionExpressionSyntax WithAsyncKeywordCore(SyntaxT public ParenthesizedLambdaExpressionSyntax Update(SyntaxToken asyncKeyword, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) => Update(SyntaxFactory.TokenList(asyncKeyword), parameterList, arrowToken, block, expressionBody); + + public ParenthesizedLambdaExpressionSyntax Update(SyntaxTokenList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + => Update(this.AttributeLists, modifiers, parameterList, arrowToken, block, expressionBody); } } @@ -41,5 +44,11 @@ public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression( public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(ParameterListSyntax parameterList, BlockSyntax? block, ExpressionSyntax? expressionBody) => ParenthesizedLambdaExpression(default(SyntaxTokenList), parameterList, block, expressionBody); + + public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(SyntaxTokenList modifiers, ParameterListSyntax parameterList, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SyntaxFactory.ParenthesizedLambdaExpression(attributeLists: default, modifiers, parameterList, arrowToken, block, expressionBody); + + public static ParenthesizedLambdaExpressionSyntax ParenthesizedLambdaExpression(SyntaxTokenList modifiers, ParameterListSyntax parameterList, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SyntaxFactory.ParenthesizedLambdaExpression(attributeLists: default, modifiers, parameterList, block, expressionBody); } } diff --git a/src/Compilers/CSharp/Portable/Syntax/RecordDeclarationSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/RecordDeclarationSyntax.cs index 57ca6fa2f1727..7521bc038c6fc 100644 --- a/src/Compilers/CSharp/Portable/Syntax/RecordDeclarationSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/RecordDeclarationSyntax.cs @@ -2,16 +2,64 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Syntax; + namespace Microsoft.CodeAnalysis.CSharp.Syntax { public partial class RecordDeclarationSyntax { - internal PrimaryConstructorBaseTypeSyntax? PrimaryConstructorBaseType + internal PrimaryConstructorBaseTypeSyntax? PrimaryConstructorBaseTypeIfClass { get { + if (Kind() == SyntaxKind.RecordStructDeclaration) + { + return null; + } + return BaseList?.Types.FirstOrDefault() as PrimaryConstructorBaseTypeSyntax; } } + + public RecordDeclarationSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, + TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, + SyntaxToken openBraceToken, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) + { + return Update(attributeLists, modifiers, keyword, this.ClassOrStructKeyword, identifier, + typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); + } + } +} + +namespace Microsoft.CodeAnalysis.CSharp +{ + public partial class SyntaxFactory + { + public static RecordDeclarationSyntax RecordDeclaration(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, + TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList constraintClauses, + SyntaxToken openBraceToken, SyntaxList members, SyntaxToken closeBraceToken, SyntaxToken semicolonToken) + { + return RecordDeclaration(SyntaxKind.RecordDeclaration, attributeLists, modifiers, keyword, classOrStructKeyword: default, identifier, + typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); + } + + public static RecordDeclarationSyntax RecordDeclaration(SyntaxList attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier, + TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, BaseListSyntax baseList, SyntaxList constraintClauses, SyntaxList members) + { + return RecordDeclaration(SyntaxKind.RecordDeclaration, attributeLists, modifiers, keyword, classOrStructKeyword: default, identifier, + typeParameterList, parameterList, baseList, constraintClauses, openBraceToken: default, members, closeBraceToken: default, semicolonToken: default); + } + + public static RecordDeclarationSyntax RecordDeclaration(SyntaxToken keyword, string identifier) + { + return RecordDeclaration(keyword, SyntaxFactory.Identifier(identifier)); + } + + public static RecordDeclarationSyntax RecordDeclaration(SyntaxToken keyword, SyntaxToken identifier) + { + return RecordDeclaration(SyntaxKind.RecordDeclaration, attributeLists: default, modifiers: default, keyword, classOrStructKeyword: default, identifier, + typeParameterList: null, parameterList: null, baseList: null, constraintClauses: default, openBraceToken: default, members: default, closeBraceToken: default, semicolonToken: default); + } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SimpleLambdaExpressionSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/SimpleLambdaExpressionSyntax.cs index c08acbc988e4e..8c2cc6564e06d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SimpleLambdaExpressionSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SimpleLambdaExpressionSyntax.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 Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp.Syntax @@ -29,8 +27,11 @@ internal override AnonymousFunctionExpressionSyntax WithAsyncKeywordCore(SyntaxT public new SimpleLambdaExpressionSyntax WithAsyncKeyword(SyntaxToken asyncKeyword) => this.Update(asyncKeyword, this.Parameter, this.ArrowToken, this.Block, this.ExpressionBody); - public SimpleLambdaExpressionSyntax Update(SyntaxToken asyncKeyword, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax block, ExpressionSyntax expressionBody) + public SimpleLambdaExpressionSyntax Update(SyntaxToken asyncKeyword, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) => Update(SyntaxFactory.TokenList(asyncKeyword), parameter, arrowToken, block, expressionBody); + + public SimpleLambdaExpressionSyntax Update(SyntaxTokenList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + => Update(this.AttributeLists, modifiers, parameter, arrowToken, block, expressionBody); } } @@ -38,10 +39,16 @@ namespace Microsoft.CodeAnalysis.CSharp { public partial class SyntaxFactory { - public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxToken asyncKeyword, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax block, ExpressionSyntax expressionBody) - => SimpleLambdaExpression(TokenList(asyncKeyword), parameter, arrowToken, block, expressionBody); + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxToken asyncKeyword, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SimpleLambdaExpression(attributeLists: default, TokenList(asyncKeyword), parameter, arrowToken, block, expressionBody); + + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(ParameterSyntax parameter, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SimpleLambdaExpression(attributeLists: default, default(SyntaxTokenList), parameter, block, expressionBody); + + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxTokenList modifiers, ParameterSyntax parameter, SyntaxToken arrowToken, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SimpleLambdaExpression(attributeLists: default, modifiers, parameter, arrowToken, block, expressionBody); - public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(ParameterSyntax parameter, BlockSyntax block, ExpressionSyntax expressionBody) - => SimpleLambdaExpression(default(SyntaxTokenList), parameter, block, expressionBody); + public static SimpleLambdaExpressionSyntax SimpleLambdaExpression(SyntaxTokenList modifiers, ParameterSyntax parameter, BlockSyntax? block, ExpressionSyntax? expressionBody) + => SimpleLambdaExpression(attributeLists: default, modifiers, parameter, block, expressionBody); } } diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml index f24059e17aa41..92ce9b7773454 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -1386,6 +1386,7 @@ Provides the base class from which the classes that represent lambda expressions are derived. + @@ -1396,6 +1397,7 @@ + @@ -1444,6 +1446,7 @@ + @@ -3000,6 +3003,9 @@ + + + @@ -3352,11 +3358,16 @@ + + + + + diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index 7314c84580886..a2f323bca3095 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -2501,6 +2501,11 @@ public static UsingDirectiveSyntax UsingDirective(NameEqualsSyntax alias, NameSy semicolonToken: Token(SyntaxKind.SemicolonToken)); } + public static UsingDirectiveSyntax UsingDirective(SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + { + return UsingDirective(globalKeyword: default(SyntaxToken), usingKeyword, staticKeyword, alias, name, semicolonToken); + } + /// Creates a new ClassOrStructConstraintSyntax instance. public static ClassOrStructConstraintSyntax ClassOrStructConstraint(SyntaxKind kind, SyntaxToken classOrStructKeyword) { diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index 4686c56198157..a4a34e3c4048d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -381,8 +381,9 @@ public enum SyntaxKind : ushort AndKeyword = 8439, /// Represents . NotKeyword = 8440, - /// Represents . - DataKeyword = 8441, + + // Don't use 8441. It corresponds to a deleted kind (DataKeyword) that was previously shipped. + /// Represents . WithKeyword = 8442, /// Represents . @@ -854,5 +855,7 @@ public enum SyntaxKind : ushort FunctionPointerUnmanagedCallingConventionList = 9066, FunctionPointerUnmanagedCallingConvention = 9067, + + RecordStructDeclaration = 9068, } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index f9e137cc3ca59..00c44edd37bc8 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -344,6 +344,7 @@ public static bool IsTypeDeclaration(SyntaxKind kind) case SyntaxKind.DelegateDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return true; default: @@ -1119,7 +1120,6 @@ public static bool IsContextualKeyword(SyntaxKind kind) case SyntaxKind.OrKeyword: case SyntaxKind.AndKeyword: case SyntaxKind.NotKeyword: - case SyntaxKind.DataKeyword: case SyntaxKind.WithKeyword: case SyntaxKind.InitKeyword: case SyntaxKind.RecordKeyword: @@ -1234,8 +1234,6 @@ public static SyntaxKind GetContextualKeywordKind(string text) return SyntaxKind.OrKeyword; case "not": return SyntaxKind.NotKeyword; - case "data": - return SyntaxKind.DataKeyword; case "with": return SyntaxKind.WithKeyword; case "init": @@ -1671,8 +1669,6 @@ public static string GetText(SyntaxKind kind) return "or"; case SyntaxKind.NotKeyword: return "not"; - case SyntaxKind.DataKeyword: - return "data"; case SyntaxKind.WithKeyword: return "with"; case SyntaxKind.InitKeyword: diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs index 083c55d94adc6..a060593078b12 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs @@ -84,6 +84,9 @@ internal static bool CanHaveAssociatedLocalBinder(this SyntaxNode syntax) case SyntaxKind.RecordDeclaration: return ((RecordDeclarationSyntax)syntax).ParameterList is object; + case SyntaxKind.RecordStructDeclaration: + return false; + default: return syntax is StatementSyntax || IsValidScopeDesignator(syntax as ExpressionSyntax); diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs index d35c1fff5ff08..d5f482bb80f08 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs @@ -5,8 +5,6 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -220,6 +218,11 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) return LineBreaksAfterCloseBrace(currentToken, nextToken); case SyntaxKind.CloseParenToken: + if (currentToken.Parent is PositionalPatternClauseSyntax) + { + //don't break inside a recursive pattern + return 0; + } // Note: the `where` case handles constraints on method declarations // and also `where` clauses (consistently with other LINQ cases below) return (((currentToken.Parent is StatementSyntax) && nextToken.Parent != currentToken.Parent) @@ -237,7 +240,7 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) return LineBreaksAfterSemicolon(currentToken, nextToken); case SyntaxKind.CommaToken: - return currentToken.Parent is EnumDeclarationSyntax ? 1 : 0; + return currentToken.Parent is EnumDeclarationSyntax or SwitchExpressionSyntax ? 1 : 0; case SyntaxKind.ElseKeyword: return nextToken.Kind() != SyntaxKind.IfKeyword ? 1 : 0; case SyntaxKind.ColonToken: @@ -246,6 +249,8 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) return 1; } break; + case SyntaxKind.SwitchKeyword when currentToken.Parent is SwitchExpressionSyntax: + return 1; } if ((nextToken.IsKind(SyntaxKind.FromKeyword) && nextToken.Parent.IsKind(SyntaxKind.FromClause)) || @@ -291,7 +296,7 @@ private static int LineBreaksBeforeOpenBrace(SyntaxToken openBraceToken) { Debug.Assert(openBraceToken.IsKind(SyntaxKind.OpenBraceToken)); if (openBraceToken.Parent.IsKind(SyntaxKind.Interpolation) || - openBraceToken.Parent is InitializerExpressionSyntax || + openBraceToken.Parent is InitializerExpressionSyntax or PropertyPatternClauseSyntax || IsAccessorListWithoutAccessorsWithBlockBody(openBraceToken.Parent)) { return 0; @@ -306,7 +311,7 @@ private static int LineBreaksBeforeCloseBrace(SyntaxToken closeBraceToken) { Debug.Assert(closeBraceToken.IsKind(SyntaxKind.CloseBraceToken)); if (closeBraceToken.Parent.IsKind(SyntaxKind.Interpolation) || - closeBraceToken.Parent is InitializerExpressionSyntax) + closeBraceToken.Parent is InitializerExpressionSyntax or PropertyPatternClauseSyntax) { return 0; } @@ -318,7 +323,7 @@ private static int LineBreaksBeforeCloseBrace(SyntaxToken closeBraceToken) private static int LineBreaksAfterOpenBrace(SyntaxToken currentToken, SyntaxToken nextToken) { - if (currentToken.Parent is InitializerExpressionSyntax || + if (currentToken.Parent is InitializerExpressionSyntax or PropertyPatternClauseSyntax || currentToken.Parent.IsKind(SyntaxKind.Interpolation) || IsAccessorListWithoutAccessorsWithBlockBody(currentToken.Parent)) { @@ -332,7 +337,7 @@ private static int LineBreaksAfterOpenBrace(SyntaxToken currentToken, SyntaxToke private static int LineBreaksAfterCloseBrace(SyntaxToken currentToken, SyntaxToken nextToken) { - if (currentToken.Parent is InitializerExpressionSyntax || + if (currentToken.Parent is InitializerExpressionSyntax or SwitchExpressionSyntax or PropertyPatternClauseSyntax || currentToken.Parent.IsKind(SyntaxKind.Interpolation) || currentToken.Parent?.Parent is AnonymousFunctionExpressionSyntax || IsAccessorListFollowedByInitializer(currentToken.Parent)) @@ -391,6 +396,130 @@ private static int LineBreaksAfterSemicolon(SyntaxToken currentToken, SyntaxToke } } + private static bool NeedsSeparatorForPropertyPattern(SyntaxToken token, SyntaxToken next) + { + PropertyPatternClauseSyntax? propPattern; + if (token.Parent.IsKind(SyntaxKind.PropertyPatternClause)) + { + propPattern = (PropertyPatternClauseSyntax)token.Parent; + } + else if (next.Parent.IsKind(SyntaxKind.PropertyPatternClause)) + { + propPattern = (PropertyPatternClauseSyntax)next.Parent; + } + else + { + return false; + } + + var tokenIsOpenBrace = token.IsKind(SyntaxKind.OpenBraceToken); + var nextIsOpenBrace = next.IsKind(SyntaxKind.OpenBraceToken); + var tokenIsCloseBrace = token.IsKind(SyntaxKind.CloseBraceToken); + var nextIsCloseBrace = next.IsKind(SyntaxKind.CloseBraceToken); + + //inner + if (tokenIsOpenBrace) + { + return true; + } + if (nextIsCloseBrace) + { + return true; + } + + if (propPattern.Parent is RecursivePatternSyntax rps) + { + //outer + if (nextIsOpenBrace) + { + if (rps.Type != null || rps.PositionalPatternClause != null) + { + return true; + } + else + { + return false; + } + } + if (tokenIsCloseBrace) + { + if (rps.Designation is null) + { + return false; + } + else + { + return true; + } + } + } + return false; + } + + private static bool NeedsSeparatorForPositionalPattern(SyntaxToken token, SyntaxToken next) + { + PositionalPatternClauseSyntax? posPattern; + if (token.Parent.IsKind(SyntaxKind.PositionalPatternClause)) + { + posPattern = (PositionalPatternClauseSyntax)token.Parent; + } + else if (next.Parent.IsKind(SyntaxKind.PositionalPatternClause)) + { + posPattern = (PositionalPatternClauseSyntax)next.Parent; + } + else + { + return false; + } + + var tokenIsOpenParen = token.IsKind(SyntaxKind.OpenParenToken); + var nextIsOpenParen = next.IsKind(SyntaxKind.OpenParenToken); + var tokenIsCloseParen = token.IsKind(SyntaxKind.CloseParenToken); + var nextIsCloseParen = next.IsKind(SyntaxKind.CloseParenToken); + + //inner + if (tokenIsOpenParen) + { + return false; + } + if (nextIsCloseParen) + { + return false; + } + + if (posPattern.Parent is RecursivePatternSyntax rps) + { + //outer + if (nextIsOpenParen) + { + if (rps.Type != null) + { + return true; + } + else + { + return false; + } + } + if (tokenIsCloseParen) + { + if (rps.PropertyPatternClause is not null) + { + return false; + } + if (rps.Designation is null) + { + return false; + } + else + { + return true; + } + } + } + return false; + } + private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) { if (token.Parent == null || next.Parent == null) @@ -452,6 +581,11 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) return true; } + if (next.IsKind(SyntaxKind.SwitchKeyword) && next.Parent is SwitchExpressionSyntax) + { + return true; + } + if (token.IsKind(SyntaxKind.QuestionToken) && (token.Parent.IsKind(SyntaxKind.ConditionalExpression) || token.Parent is TypeSyntax) && !token.Parent.Parent.IsKind(SyntaxKind.TypeArgumentList)) @@ -468,7 +602,8 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) if (next.IsKind(SyntaxKind.ColonToken)) { if (next.Parent.IsKind(SyntaxKind.BaseList) || - next.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause)) + next.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause) || + next.Parent is ConstructorInitializerSyntax) { return true; } @@ -651,6 +786,35 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) } } + if (token.Parent is RelationalPatternSyntax) + { + //>, >=, <, <= + return true; + } + + switch (next.Kind()) + { + case SyntaxKind.AndKeyword: + case SyntaxKind.OrKeyword: + return true; + } + + switch (token.Kind()) + { + case SyntaxKind.AndKeyword: + case SyntaxKind.OrKeyword: + case SyntaxKind.NotKeyword: + return true; + } + if (NeedsSeparatorForPropertyPattern(token, next)) + { + return true; + } + if (NeedsSeparatorForPositionalPattern(token, next)) + { + return true; + } + return false; } @@ -1025,6 +1189,7 @@ private static int GetDeclarationDepth(SyntaxNode? node) node is AccessorDeclarationSyntax || node is TypeParameterConstraintClauseSyntax || node is SwitchSectionSyntax || + node is SwitchExpressionArmSyntax || node is UsingDirectiveSyntax || node is ExternAliasDirectiveSyntax || node is QueryExpressionSyntax || diff --git a/src/Compilers/CSharp/Portable/Syntax/TypeDeclarationSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/TypeDeclarationSyntax.cs index c9685f7d35429..314d4b8b09bda 100644 --- a/src/Compilers/CSharp/Portable/Syntax/TypeDeclarationSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/TypeDeclarationSyntax.cs @@ -62,6 +62,7 @@ private static SyntaxKind GetTypeDeclarationKeywordKind(SyntaxKind kind) case SyntaxKind.InterfaceDeclaration: return SyntaxKind.InterfaceKeyword; case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return SyntaxKind.RecordKeyword; default: throw ExceptionUtilities.UnexpectedValue(kind); @@ -113,9 +114,11 @@ public static TypeDeclarationSyntax TypeDeclaration( case SyntaxKind.InterfaceDeclaration: return SyntaxFactory.InterfaceDeclaration(attributes, modifiers, keyword, identifier, typeParameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); case SyntaxKind.RecordDeclaration: - return SyntaxFactory.RecordDeclaration(attributes, modifiers, keyword, identifier, typeParameterList, parameterList: null, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); + return SyntaxFactory.RecordDeclaration(SyntaxKind.RecordDeclaration, attributes, modifiers, keyword, classOrStructKeyword: default, identifier, typeParameterList, parameterList: null, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); + case SyntaxKind.RecordStructDeclaration: + return SyntaxFactory.RecordDeclaration(SyntaxKind.RecordStructDeclaration, attributes, modifiers, keyword, classOrStructKeyword: SyntaxFactory.Token(SyntaxKind.StructKeyword), identifier, typeParameterList, parameterList: null, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken); default: - throw new ArgumentException("kind"); + throw ExceptionUtilities.UnexpectedValue(kind); } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/UsingDirectiveSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/UsingDirectiveSyntax.cs new file mode 100644 index 0000000000000..d242ccc5da7f0 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Syntax/UsingDirectiveSyntax.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp.Syntax +{ + partial class UsingDirectiveSyntax + { + public UsingDirectiveSyntax Update(SyntaxToken usingKeyword, SyntaxToken staticKeyword, NameEqualsSyntax? alias, NameSyntax name, SyntaxToken semicolonToken) + { + return Update(globalKeyword: GlobalKeyword, usingKeyword, staticKeyword, alias, name, semicolonToken); + } + } +} diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index ac1a9531bd044..4dd8850d512fe 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ Atribut {0} není platný pro přístupové objekty události. Je platný jenom pro deklarace {1}. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. Automaticky implementovanou vlastnost {0} nelze označit modifikátorem readonly, protože má přístupový objekt set. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - Člen záznamu {0} musí být čitelná vlastnost instance typu {1}, která se bude shodovat s pozičním parametrem {2}. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + Člen záznamu {0} musí být čitelná vlastnost instance typu {1}, která se bude shodovat s pozičním parametrem {2}. @@ -162,11 +167,21 @@ Chyba syntaxe příkazového řádku: {0} není platná hodnota možnosti {1}. Hodnota musí mít tvar {2}. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Skupina &metody {0} se nedá převést na typ delegáta {0}. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Konvence volání managed se nedá kombinovat se specifikátory konvence nespravovaného volání. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + V typeof v atributu se nepodporuje používání typu ukazatele funkce. @@ -437,6 +452,16 @@ Ukazatel na funkci se nedá zavolat s pojmenovanými argumenty. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Příkaz goto nemůže přejít na místo před deklarací using ve stejném bloku. @@ -447,6 +472,11 @@ Příkaz goto nemůže přejít na místo za deklarací using. + + The positional member '{0}' found corresponding to this parameter is hidden. + Poziční člen {0}, který odpovídá tomuto parametru je skrytý. + + The suppression operator is not allowed in this context Operátor potlačení není v tomto kontextu povolený. @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + Dědění ze záznamu se zapečetěným objektem Object.ToString se v jazyce C# {0} nepodporuje. Použijte prosím jazykovou verzi {1} nebo vyšší. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + Modifikátor {0} není platný pro tuto položku v jazyce C# {1}. Použijte prosím verzi jazyka {2} nebo vyšší. @@ -647,11 +677,6 @@ Nepovedlo se určit výstupní adresář. - - The receiver type '{0}' is not a valid record type. - Typ příjemce {0} není platný typ záznamu. - - Record member '{0}' must be private. Člen záznamu {0} musí být privátní. @@ -972,14 +997,49 @@ discards + + global using directive + global using directive + + target-typed object creation Vytvoření objektu s cílovým typem + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + zapečetěný ToString v záznamu + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Smíšené deklarace a výrazy v dekonstrukci @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Částečné deklarace {0} musí být jen třídy, jen záznamy, jen struktury, nebo jen rozhraní. + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Částečné deklarace {0} musí být jen třídy, jen záznamy, jen struktury, nebo jen rozhraní. @@ -5101,8 +5161,8 @@ Pokud se taková třída používá jako základní třída a pokud odvozující - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Atribut Conditional není pro {0} platný, protože je to konstruktor, destruktor, operátor nebo explicitní implementace rozhraní. + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Atribut Conditional není pro {0} platný, protože je to konstruktor, destruktor, operátor nebo explicitní implementace rozhraní. @@ -8413,8 +8473,8 @@ Poskytněte kompilátoru nějaký způsob, jak metody rozlišit. Můžete např - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Parametry nebo lokální proměnné typu {0} nemůžou být deklarované v asynchronních metodách nebo lambda výrazech. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Parametry nebo lokální proměnné typu {0} nemůžou být deklarované v asynchronních metodách nebo asynchronních výrazech lambda. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index c13857bef8189..87df0b3c48442 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ Das Attribut "{0}" ist für Ereignisaccessoren nicht gültig. Es gilt nur für {1}-Deklarationen. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. Die automatisch implementierte Eigenschaft "{0}" kann nicht als "readonly" markiert werden, weil sie einen set-Accessor aufweist. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - Das Datensatzelement "{0}" muss eine lesbare Instanzeigenschaft vom Typ "{1}" sein, um dem Positionsparameter "{2}" zu entsprechen. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + Das Datensatzelement "{0}" muss eine lesbare Instanzeigenschaft vom Typ "{1}" sein, um dem Positionsparameter "{2}" zu entsprechen. @@ -162,11 +167,21 @@ Fehler in der Befehlszeilensyntax: "{0}" ist kein gültiger Wert für die Option "{1}". Der Wert muss im Format "{2}" vorliegen. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Die &Methodengruppe "{0}" kann nicht in den Delegattyp "{0}" konvertiert werden. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Die Aufrufkonvention "managed" kann nicht mit Spezifizierern für nicht verwaltete Aufrufkonventionen kombiniert werden. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + Die Verwendung eines Funktionszeigertyps in "typeof" in einem Attribut wird nicht unterstützt. @@ -437,6 +452,16 @@ Ein Funktionszeiger kann nicht mit benannten Argumenten aufgerufen werden. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Mit "goto" kann nicht an eine Position vor einer using-Deklaration im selben Block gesprungen werden. @@ -447,6 +472,11 @@ Mit "goto" kann nicht an eine Position hinter einer using-Deklaration gesprungen werden. + + The positional member '{0}' found corresponding to this parameter is hidden. + Das für diesen Parameter gefundene positionelle Element „{0}“ ist ausgeblendet. + + The suppression operator is not allowed in this context Ein Unterdrückungsoperator ist in diesem Kontext unzulässig. @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + Das Erben von einem Datensatz mit einem versiegelten "Object.ToString" wird in C# {0} nicht unterstützt. Verwenden Sie die Sprachversion "{1}" oder höher. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + Der Modifizierer "{0}" ist für dieses Element in C# {1} ungültig. Verwenden Sie Sprachversion {2} oder höher. @@ -647,11 +677,6 @@ Das Ausgabeverzeichnis konnte nicht bestimmt werden. - - The receiver type '{0}' is not a valid record type. - Der Empfängertyp "{0}" ist kein gültiger Datensatztyp. - - Record member '{0}' must be private. Der Datensatzmember "{0}" muss privat sein. @@ -972,14 +997,49 @@ Ausschussvariablen + + global using directive + global using directive + + target-typed object creation Objekterstellung mit Zieltyp + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + versiegelte "ToString" im Datensatz + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Gemischte Deklarationen und Ausdrücke in der Dekonstruktion @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Partielle Deklarationen von "{0}" müssen entweder nur Klassen, nur Datensätze, nur Strukturen oder nur Schnittstellen sein. + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Partielle Deklarationen von "{0}" müssen entweder nur Klassen, nur Datensätze, nur Strukturen oder nur Schnittstellen sein. @@ -5101,8 +5161,8 @@ Wenn solch eine Klasse als Basisklasse verwendet wird und die ableitende Klasse - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Das Conditional-Attribut ist für "{0}" nicht gültig, weil es sich hierbei um einen Konstruktor, einen Destruktor, einen Operator oder eine explizite Schnittstellenimplementierung handelt. + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Das Conditional-Attribut ist für "{0}" nicht gültig, weil es sich hierbei um einen Konstruktor, einen Destruktor, einen Operator oder eine explizite Schnittstellenimplementierung handelt. @@ -8413,8 +8473,8 @@ Unterstützen Sie den Compiler bei der Unterscheidung zwischen den Methoden. Daz - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Parameter oder lokale Variablen des Typs "{0}" können nicht in Async-Methoden oder Lambdaausdrücken deklariert werden. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Parameter oder lokale Variablen des Typs "{0}" können nicht in asynchronen Methoden oder in asynchronen Lambdaausdrücken deklariert werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index e77b6c0174b94..152290f4c9c0e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ El atributo "{0}" no es válido en descriptores de acceso de eventos. Solo es válido en declaraciones "{1}". + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. La propiedad implementada automáticamente "{0}" no se puede marcar como "readonly" porque tiene un descriptor de acceso "set". @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - El miembro de registro "{0}" debe ser una propiedad de instancia legible de tipo "{1}" para que coincida con el parámetro de posición "{2}". + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + El miembro de registro "{0}" debe ser una propiedad de instancia legible de tipo "{1}" para que coincida con el parámetro de posición "{2}". @@ -162,11 +167,21 @@ Error de sintaxis de la línea de comandos: "{0}" no es un valor válido para la opción "{1}". El valor debe tener el formato "{2}". + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. No se puede convertir el grupo de &métodos "{0}" en el tipo delegado "{0}". + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. La convención de llamada "managed" no se puede combinar con especificadores de convención de llamada no administrados. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + No se admite el uso de un tipo de puntero de función como valor "typeof" de un atributo. @@ -437,6 +452,16 @@ No se puede llamar a un puntero a función con argumentos con nombre. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Una instrucción goto no puede saltar a una ubicación antes que una declaración using dentro del mismo bloque. @@ -447,6 +472,11 @@ Una instrucción goto no puede saltar a una ubicación después de una declaración using. + + The positional member '{0}' found corresponding to this parameter is hidden. + El miembro posicional '{0}' que se corresponde con este parámetro está oculto. + + The suppression operator is not allowed in this context No se permite el operador de supresión en este contexto. @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + No se admite heredar desde un registro con 'Object.ToString' sellado en C# {0}. Utilice la versión de idioma '{1}' o superior. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + El modificador "{0}" no es válido para este elemento en C# {1}. Use la versión de lenguaje "{2}" o una posterior. @@ -647,11 +677,6 @@ No se pudo determinar el directorio de salida. - - The receiver type '{0}' is not a valid record type. - El tipo de receptor "{0}" no es un tipo de registro válido. - - Record member '{0}' must be private. El miembro de registro "{0}" debe ser privado. @@ -972,14 +997,49 @@ descartes + + global using directive + global using directive + + target-typed object creation creación de objetos con tipo de destino + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + ToString sellado en el registro + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Declaraciones y expresiones mixtas en la desconstrucción @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Las declaraciones parciales de "{0}" deben ser todas clases, todos registros, todas estructuras o todas interfaces + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Las declaraciones parciales de "{0}" deben ser todas clases, todos registros, todas estructuras o todas interfaces @@ -5101,8 +5161,8 @@ Si se utiliza una clase de este tipo como clase base y si la clase derivada defi - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - El atributo Conditional no es válido en '{0}' porque es un constructor, destructor, operador o la implementación de interfaz explícita + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + El atributo Conditional no es válido en '{0}' porque es un constructor, destructor, operador o la implementación de interfaz explícita @@ -8413,8 +8473,8 @@ Indique al compilador alguna forma de diferenciar los métodos. Por ejemplo, pue - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Los parámetros o las variables locales de tipo '{0}' no se pueden declarar en métodos asincrónicos o expresiones lambda. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Los parámetros o locales de tipo '{0}' no pueden declararse en expresiones lambda o métodos asincrónicos. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 09aa035471de8..4c722d4180d60 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ L'attribut '{0}' est non valide sur les accesseurs d'événement. Il est valide uniquement sur les déclarations '{1}'. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. La propriété implémentée automatiquement '{0}' ne peut pas être marquée 'readonly', car elle a un accesseur 'set'. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - Le membre d'enregistrement '{0}' doit être une propriété d'instance lisible de type '{1}' pour correspondre au paramètre positionnel '{2}'. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + Le membre d'enregistrement '{0}' doit être une propriété d'instance lisible de type '{1}' pour correspondre au paramètre positionnel '{2}'. @@ -162,11 +167,21 @@ Erreur de syntaxe de ligne de commande : '{0}' est une valeur non valide pour l'option '{1}'. La valeur doit se présenter sous la forme '{2}'. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Impossible de convertir le groupe de &méthodes '{0}' en type délégué '{0}'. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Impossible d'associer la convention d'appel 'managed' à des spécificateurs de convention d'appel non managés. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + L'utilisation d'un type de pointeur de fonction dans un 'typeof' au sein d'un attribut n'est pas prise en charge. @@ -437,6 +452,16 @@ Impossible d'appeler un pointeur de fonction avec des arguments nommés. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Un goto ne peut pas accéder à un emplacement avant une déclaration using dans le même bloc. @@ -447,6 +472,11 @@ Un goto ne peut pas accéder à un emplacement après une déclaration using. + + The positional member '{0}' found corresponding to this parameter is hidden. + Le membre '{0}' positionnel trouvé correspondant à ce paramètre est masqué. + + The suppression operator is not allowed in this context L'opérateur de suppression n'est pas autorisé dans ce contexte @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + L’héritage d’un enregistrement avec un 'Object.ToString' scellé n’est pas pris en charge dans C# {0}. Veuillez utiliser la version linguistique '{1}' ou version supérieure. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + Le modificateur '{0}' est non valide pour cet élément en C# {1}. Utilisez la version de langage '{2}' ou une version ultérieure. @@ -647,11 +677,6 @@ Impossible de déterminer le répertoire de sortie - - The receiver type '{0}' is not a valid record type. - Le récepteur de type '{0}' n'est pas un type d'enregistrement valide. - - Record member '{0}' must be private. Le membre d'enregistrement '{0}' doit être privé. @@ -972,14 +997,49 @@ discards (éléments ignorés) + + global using directive + global using directive + + target-typed object creation création d'un objet typé cible + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + ToString scellé dans l’enregistrement + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Mélange de déclarations et d'expressions dans la déconstruction @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Les déclarations partielles de '{0}' doivent être toutes des classes, des enregistrements, des structs ou des interfaces + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Les déclarations partielles de '{0}' doivent être toutes des classes, des enregistrements, des structs ou des interfaces @@ -5101,8 +5161,8 @@ Si une telle classe est utilisée en tant que classe de base et si la classe dé - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - L'attribut Conditional n'est pas valide sur '{0}', car il s'agit d'un constructeur, d'un destructeur, d'un opérateur ou d'une implémentation d'interface explicite + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + L'attribut Conditional n'est pas valide sur '{0}', car il s'agit d'un constructeur, d'un destructeur, d'un opérateur ou d'une implémentation d'interface explicite @@ -8413,8 +8473,8 @@ Permettez au compilateur de différencier les méthodes. Par exemple, vous pouve - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Les paramètres ou variables locales de type '{0}' ne peuvent pas être déclarés dans des méthodes async ou des expressions lambda. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Les paramètres ou variables locales de type '{0}' ne peuvent pas être déclarés dans des méthodes asynchrones ou des expressions asynchrones lambda. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 23ba661cefe46..c5fe96b48b4bd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ L'attributo '{0}' non è valido nelle funzioni di accesso a eventi. È valido solo nelle dichiarazioni di '{1}'. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. La proprietà implementata automaticamente '{0}' non può essere contrassegnata come 'readonly' perché include una funzione di accesso 'set'. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - Il membro di record '{0}' deve essere una proprietà di istanza leggibile di tipo '{1}' per corrispondere al parametro posizionale '{2}'. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + Il membro di record '{0}' deve essere una proprietà di istanza leggibile di tipo '{1}' per corrispondere al parametro posizionale '{2}'. @@ -162,11 +167,21 @@ Errore di sintassi della riga di comando: '{0}' non è un valore valido per l'opzione '{1}'. Il valore deve essere espresso nel formato '{2}'. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Non è possibile convertire il gruppo di &metodi '{0}' nel tipo delegato '{0}'. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Non è possibile combinare la convenzione di chiamata 'managed' con identificatori di convenzione di chiamata non gestita. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + L'uso di un tipo di puntatore a funzione in un elemento 'typeof' di un attributo non è supportato. @@ -437,6 +452,16 @@ Non è possibile chiamare un puntatore a funzione con argomenti denominati. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Un'istruzione goto non può passare a una posizione che precede una dichiarazione using all'interno dello stesso blocco. @@ -447,6 +472,11 @@ Un'istruzione goto non può passare a una posizione successiva a una dichiarazione using. + + The positional member '{0}' found corresponding to this parameter is hidden. + Il membro posizionale '{0}' trovato e corrispondente a questo parametro è nascosto. + + The suppression operator is not allowed in this context L'operatore di eliminazione non è consentito in questo contesto @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + L'ereditarietà da un record con un 'Object.ToString' di tipo sealed non è supportata in C# {0}. Usare la versione '{1}' o successiva del linguaggio. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + Il modificatore '{0}' non è valido per questo elemento in C# {1}. Usare la versione '{2}' o versioni successive del linguaggio. @@ -647,11 +677,6 @@ Non è stato possibile individuare la directory di output - - The receiver type '{0}' is not a valid record type. - Il tipo di ricevitore '{0}' non è un tipo di record valido. - - Record member '{0}' must be private. Il membro del record '{0}' deve essere privato. @@ -972,14 +997,49 @@ rimozioni + + global using directive + global using directive + + target-typed object creation creazione di oggetti con tipo di destinazione + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + ToString sealed nel record + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Dichiarazioni ed espressioni miste nella decostruzione @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Le dichiarazioni parziali di '{0}' devono essere costituite solo da classi, record, struct o interfacce + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Le dichiarazioni parziali di '{0}' devono essere costituite solo da classi, record, struct o interfacce @@ -5101,8 +5161,8 @@ Se si usa tale classe come classe base e se la classe di derivazione definisce u - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - L'attributo Conditional non è valido per '{0}' perché è l'implementazione di un costruttore, un distruttore, un operatore o un'interfaccia esplicita + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + L'attributo Conditional non è valido per '{0}' perché è l'implementazione di un costruttore, un distruttore, un operatore o un'interfaccia esplicita @@ -8413,8 +8473,8 @@ Impostare il compilatore in modo tale da distinguere i metodi, ad esempio assegn - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Non è possibile dichiarare parametri o variabili locali di tipo '{0}' in metodi asincroni o espressioni lambda + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Non è possibile dichiarare parametri o variabili locali di tipo '{0}' in metodi asincroni o espressioni lambda asincrone. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index b514b4403a277..ae625bf8f8479 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ 属性 '{0}' はイベント アクセサーでは無効です。'{1}' 宣言でのみ有効です。 + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. 'set' アクセサーがあるため、自動実装プロパティ '{0}' を 'readonly' とマークすることはできません。 @@ -149,12 +154,12 @@ Records may only inherit from object or another record - レコードの継承元にできるのは、オブジェクトか別のレコードだけです + レコードの継承元にできるのは、object か別のレコードだけです - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - レコード メンバー '{0}' は、位置指定パラメーター '{2}' に一致させるための型 '{1}' の読み取り可能なインスタンス プロパティである必要があります。 + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + レコード メンバー '{0}' は、位置指定パラメーター '{2}' に一致させるための型 '{1}' の読み取り可能なインスタンス プロパティである必要があります。 @@ -162,11 +167,21 @@ コマンドライン構文エラー: '{0}' は、'{1}' オプションの有効な値ではありません。値は '{2}' の形式にする必要があります。 + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. メソッド グループ '{0}' をデリゲート型 '{0}' に変換することはできません。(&M) + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. 'マネージド' 呼び出し規則をアンマネージド呼び出し規則指定子と組み合わせることはできません。 @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + 属性内の 'typeof' で関数ポインター型を使用することはサポートされていません。 @@ -437,6 +452,16 @@ 関数ポインターを名前付き引数で呼び出すことはできません。 + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. goto は同じブロック内の using 宣言より前の位置にはジャンプできません。 @@ -447,6 +472,11 @@ goto は using 宣言より後の位置にはジャンプできません。 + + The positional member '{0}' found corresponding to this parameter is hidden. + このパラメーターに対応する位置にあるメンバー '{0}' が非表示になっています。 + + The suppression operator is not allowed in this context このコンテキストでは抑制演算子が許可されていません @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + シールされた ' Object. ToString ' を含むレコードからの継承は、C# {0} ではサポートされていません。' {1} ' 以上の言語バージョンを使用してください。 @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + C# {1} では、修飾子 '{0}' はこの項目に対して有効ではありません。'{2}' 以上の言語バージョンをご使用ください。 @@ -647,11 +677,6 @@ 出力ディレクトリを特定できませんでした - - The receiver type '{0}' is not a valid record type. - レシーバーの種類 '{0}' は有効なレコードの種類ではありません。 - - Record member '{0}' must be private. レコード メンバー '{0}' は private でなければなりません。 @@ -972,14 +997,49 @@ ディスカード + + global using directive + global using directive + + target-typed object creation target-typed オブジェクトの作成 + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + レコードでシールされた ToString + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + 分解で宣言と式が混在しています @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - '{0}' の partial 宣言は、すべてのクラス、すべてのレコード、すべての構造体、またはすべてのインターフェイスにする必要があります + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + '{0}' の partial 宣言は、すべてのクラス、すべてのレコード、すべての構造体、またはすべてのインターフェイスにする必要があります @@ -5101,8 +5161,8 @@ If such a class is used as a base class and if the deriving class defines a dest - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - 条件付き属性は、コンストラクター、デストラクター、演算子または明示的インターフェイスの実装であるため、'{0}' では無効です + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + 条件付き属性は、コンストラクター、デストラクター、演算子または明示的インターフェイスの実装であるため、'{0}' では無効です @@ -8413,8 +8473,8 @@ C# では out と ref を区別しますが、CLR では同じと認識します - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - '{0}' 型のパラメーターまたはローカルは、非同期メソッドまたはラムダ式で宣言することができません。 + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + '{0}' 型のパラメーターまたはローカルは、非同期メソッドまたは非同期ラムダ式で宣言することができません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 4d1e5edcf9666..8f0f818383244 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ 이벤트 접근자에서 '{0}' 특성이 유효하지 않습니다. 이 특성은 '{1}' 선언에만 유효합니다. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. 'set' 접근자가 포함되어 있으므로 자동 구현 속성 '{0}'을(를) 'readonly'로 표시할 수 없습니다. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - 위치 매개 변수 '{2}'과(와) 일치하려면 레코드 멤버 '{0}'이(가) 유형 '{1}'의 읽을 수 있는 인스턴스 속성이어야 합니다. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + 위치 매개 변수 '{2}'과(와) 일치하려면 레코드 멤버 '{0}'이(가) 유형 '{1}'의 읽을 수 있는 인스턴스 속성이어야 합니다. @@ -162,11 +167,21 @@ 명령줄 구문 오류: '{0}'은(는) '{1}' 옵션에 유효한 값이 아닙니다. 값은 '{2}' 형식이어야 합니다. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. 메서드 그룹 '{0}'을(를) 대리자 형식 '{0}'(으)로 변환할 수 없습니다(&M). + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. '관리되는' 호출 규칙은 관리되지 않는 호출 규칙 지정자와 함께 사용할 수 없습니다. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + 특성의 'typeof'에 함수 포인터 형식을 사용할 수 없습니다. @@ -437,6 +452,16 @@ 함수 포인터는 명명된 인수를 사용하여 호출할 수 없습니다. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. goto는 동일한 블록 내의 using 선언 앞 위치로 이동할 수 없습니다. @@ -447,6 +472,11 @@ goto는 using 선언 뒤 위치로 이동할 수 없습니다. + + The positional member '{0}' found corresponding to this parameter is hidden. + 이 매개 변수에 해당 하는 위치 멤버 '{0}'이(가) 숨겨집니다. + + The suppression operator is not allowed in this context 이 컨텍스트에서는 비표시 오류(Suppression) 연산자를 사용할 수 없습니다. @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + 봉인된 'Object.ToString'이 있는 레코드에서 상속은 C# {0}에서 지원되지 않습니다. 언어 버전 '{1}'이상을 사용하세요. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + C# {1}의 이 항목에는 '{0}' 한정자가 유효하지 않습니다. 언어 버전 '{2}' 이상을 사용하세요. @@ -647,11 +677,6 @@ 출력 디렉터리를 확인할 수 없습니다. - - The receiver type '{0}' is not a valid record type. - 수신기 형식 '{0}'이(가) 유효한 레코드 형식이 아닙니다. - - Record member '{0}' must be private. 레코드 멤버 '{0}'은(는) 프라이빗이어야 합니다. @@ -972,14 +997,49 @@ 무시 항목 + + global using directive + global using directive + + target-typed object creation 대상으로 형식화된 개체 만들기 + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + 레코드의 봉인된 ToString + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + 분해의 혼합 선언 및 식 @@ -4161,8 +4221,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - '{0}'의 partial 선언은 모든 클래스, 모든 레코드, 모든 구조체 또는 모든 인터페이스여야 합니다. + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + '{0}'의 partial 선언은 모든 클래스, 모든 레코드, 모든 구조체 또는 모든 인터페이스여야 합니다. @@ -5100,8 +5160,8 @@ If such a class is used as a base class and if the deriving class defines a dest - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - '{0}'에는 생성자, 소멸자, 연산자 또는 명시적 인터페이스 구현이기 때문에 Conditional 특성이 유효하지 않습니다. + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + '{0}'에는 생성자, 소멸자, 연산자 또는 명시적 인터페이스 구현이기 때문에 Conditional 특성이 유효하지 않습니다. @@ -8412,8 +8472,8 @@ C#에서는 out과 ref를 구분하지만 CLR에서는 동일한 것으로 간 - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - '{0}' 형식의 매개 변수 또는 로컬은 비동기 메서드나 람다 식에서 선언할 수 없습니다. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + '{0}' 형식의 매개 변수 또는 로컬은 비동기 메서드나 비동기 람다 식에서 선언할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 761e1ae2d9251..c7e04062445c4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ Atrybut „{0}” nie jest prawidłowy w metodach dostępu zdarzenia. Jest on prawidłowy tylko w deklaracjach „{1}”. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. Właściwość implementowana automatycznie „{0}” nie może być zadeklarowana jako „readonly”, ponieważ ma metodę dostępu „set”. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - Element członkowski rekordu „{0}” musi być możliwą do odczytu właściwością wystąpienia typu „{1}”, aby dopasować parametr pozycyjny „{2}”. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + Element członkowski rekordu „{0}” musi być możliwą do odczytu właściwością wystąpienia typu „{1}”, aby dopasować parametr pozycyjny „{2}”. @@ -162,11 +167,21 @@ Błąd składni wiersza polecenia: „{0}” nie jest prawidłową wartością dla opcji „{1}”. Wartość musi mieć postać „{2}”. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Nie można przekonwertować grupy &metod „{0}” na typ delegata „{0}”. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Konwencji wywoływania „managed” nie można łączyć z niezarządzanymi specyfikatorami konwencji wywoływania. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + Użycie typu wskaźnika funkcji w elemencie „typeof” w atrybucie nie jest obsługiwane. @@ -437,6 +452,16 @@ Nie można wywołać wskaźnika funkcji przy użyciu argumentów nazwanych. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Instrukcja goto nie może przechodzić do lokalizacji występującej przed deklaracją using w tym samym bloku. @@ -447,6 +472,11 @@ Instrukcja goto nie może przechodzić do lokalizacji występującej po deklaracji using. + + The positional member '{0}' found corresponding to this parameter is hidden. + Odnaleziony członek pozycyjny „{0}” odpowiadający temu parametrowi jest ukryty. + + The suppression operator is not allowed in this context Operator pominięcia jest niedozwolony w tym kontekście @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + Dziedziczenie z rekordu z zapieczętowanym obiektem "Object.ToString" nie jest obsługiwane w języku C# {0}. Użyj wersji języka "{1}" lub nowszej. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + Modyfikator „{0}” nie jest prawidłowy dla tego elementu w języku C# {1}. Użyj wersji języka „{2}” lub nowszej. @@ -647,11 +677,6 @@ Nie można było określić katalogu wyjściowego - - The receiver type '{0}' is not a valid record type. - Typ odbiorcy „{0}” nie jest prawidłowym typem rekordu. - - Record member '{0}' must be private. Składowa rekordu „{0}” musi być prywatna. @@ -972,14 +997,49 @@ odrzucenia + + global using directive + global using directive + + target-typed object creation tworzenie obiektu z typem docelowym + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + zapieczętowany obiekt ToString w rekordzie + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Mieszane deklaracje i wyrażenia w dekonstrukcji @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Wszystkie częściowe deklaracje elementu „{0}” muszą być klasami, rekordami, strukturami lub interfejsami + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Wszystkie częściowe deklaracje elementu „{0}” muszą być klasami, rekordami, strukturami lub interfejsami @@ -5101,8 +5161,8 @@ Jeśli taka klasa zostanie użyta jako klasa bazowa i klasa pochodna definiuje d - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Atrybut Conditional jest nieprawidłowy w elemencie „{0}”, ponieważ jest to konstruktor, destruktor, operator lub jawna implementacja interfejsu + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Atrybut Conditional jest nieprawidłowy w elemencie „{0}”, ponieważ jest to konstruktor, destruktor, operator lub jawna implementacja interfejsu @@ -8413,7 +8473,7 @@ Musisz umożliwić kompilatorowi rozróżnienie metod. Możesz na przykład nada - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. Parametrów ani elementów lokalnych typu „{0}” nie można deklarować w metodach asynchronicznych ani wyrażeniach lambda. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 349c28c45bc43..abf8209997982 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ O atributo '{0}' não é válido em acessadores de evento. Ele é válido somente em declarações '{1}'. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. A propriedade autoimplementada '{0}' não pode ser marcada como 'readonly' porque ela tem um acessador 'set'. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - O membro do registro '{0}' precisa ser uma propriedade de instância legível do tipo '{1}' para corresponder ao parâmetro posicional '{2}'. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + O membro do registro '{0}' precisa ser uma propriedade de instância legível do tipo '{1}' para corresponder ao parâmetro posicional '{2}'. @@ -162,11 +167,21 @@ Erro de sintaxe de linha de comando: '{0}' não é um valor válido para a opção '{1}'. O valor precisa estar no formato '{2}'. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Não é possível converter o grupo de &métodos '{0}' no tipo delegado '{0}'. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. A convenção de chamada 'managed' não pode ser combinada com especificadores de convenção de chamada não gerenciados. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + Não há suporte para o uso de um tipo de ponteiro de função em um 'typeof' em um atributo. @@ -437,6 +452,16 @@ Um ponteiro de função não pode ser chamado com argumentos nomeados. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Um goto não pode saltar para um local antes de uma declaração using no mesmo bloco. @@ -447,6 +472,11 @@ Um goto não pode saltar para um local antes de uma declaração using. + + The positional member '{0}' found corresponding to this parameter is hidden. + O membro posicional “{0}” encontrado correspondente a este parâmetro está oculto. + + The suppression operator is not allowed in this context O operador de supressão não é permitido neste contexto @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + Herdar de um registro com um 'Object.ToString' selado não é compatível com C# {0}. Use a versão do idioma '{1}' ou superior. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + O modificador '{0}' não é válido para este item no C# {1}. Use a versão de linguagem '{2}' ou superior. @@ -647,11 +677,6 @@ Não foi possível determinar o diretório de saída - - The receiver type '{0}' is not a valid record type. - O tipo de receptor '{0}' não é um tipo de registro válido. - - Record member '{0}' must be private. O membro do registro '{0}' precisa ser privado. @@ -972,14 +997,49 @@ descarte + + global using directive + global using directive + + target-typed object creation criação de objeto de tipo de destino + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + ToString selado no registro + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Declarações e expressões mistas na desconstrução @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - As declarações parciais de '{0}' precisam ser todas classes, registros, structs ou interfaces + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + As declarações parciais de '{0}' precisam ser todas classes, registros, structs ou interfaces @@ -5101,8 +5161,8 @@ Se tal classe for usada como uma classe base e se a classe derivada definir um d - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - O atributo Conditional não é válido em "{0}" porque é um construtor, destruidor, operador ou implementação de interface implícita + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + O atributo Conditional não é válido em "{0}" porque é um construtor, destruidor, operador ou implementação de interface implícita @@ -8413,8 +8473,8 @@ Forneça ao compilador alguma forma de diferenciar os métodos. Por exemplo, voc - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Parâmetros ou locais do tipo "{0}" não podem ser declarados em métodos assíncronos ou expressões lambda. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Os parâmetros ou locais do tipo '{0}' não podem ser declarados nos métodos async ou expressões async lambda. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 6bcc64b24402b..0e780bbea42ee 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ Атрибут "{0}" запрещено использовать в методах доступа к событиям. Он допустим только для объявлений "{1}". + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. Автоматически реализуемое свойство "{0}" не может быть помечено как readonly, так как имеет метод доступа set. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - Элемент записи "{0}" должен быть доступным для чтения свойством экземпляра типа "{1}", чтобы соответствовать позиционному параметру "{2}". + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + Элемент записи "{0}" должен быть доступным для чтения свойством экземпляра типа "{1}", чтобы соответствовать позиционному параметру "{2}". @@ -162,11 +167,21 @@ Ошибка в синтаксисе командной строки: "{0}" не является допустимым значением для параметра "{1}". Значение должно иметь форму "{2}". + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. Не удается преобразовать &группу методов "{0}" в тип делегата "{0}". + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Соглашение о вызовах "managed" невозможно использовать вместе с спецификаторами неуправляемых соглашений о вызовах. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + Использование типа указателя на функцию в "typeof" атрибута не поддерживается. @@ -437,6 +452,16 @@ Невозможно вызвать указатель на функцию с именованными аргументами. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Оператор goto не может переходить к расположению раньше объявления using в том же блоке. @@ -447,6 +472,11 @@ Оператор goto не может переходить к расположению после объявления using. + + The positional member '{0}' found corresponding to this parameter is hidden. + Обнаруженный позиционный элемент "{0}", соответствующий этому параметру, скрыт. + + The suppression operator is not allowed in this context Оператор подавления недопустим в данном контексте. @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + Наследование от записи с запечатанным Object. ToString не поддерживается в C# {0}. Используйте версию языка "{1}" или более позднюю. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + Модификатор "{0}" недопустим для этого элемента в C# {1}. Используйте версию языка "{2}" или более позднюю. @@ -647,11 +677,6 @@ Не удалось определить выходной каталог - - The receiver type '{0}' is not a valid record type. - Тип получателя "{0}" не является допустимым типом записи. - - Record member '{0}' must be private. Элемент записи "{0}" должен быть закрытым. @@ -972,14 +997,49 @@ пустые переменные + + global using directive + global using directive + + target-typed object creation создание объекта с типом целевого объекта + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + запечатанный ToString в записи + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Смешанные объявления и выражения в деконструировании @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Все разделяемые объявления "{0}" должны относиться к одному типу (классы, записи, структуры или интерфейсы). + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Все разделяемые объявления "{0}" должны относиться к одному типу (классы, записи, структуры или интерфейсы). @@ -5101,8 +5161,8 @@ If such a class is used as a base class and if the deriving class defines a dest - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Атрибут Conditional недопустим для "{0}", так как это конструктор, деструктор, оператор или явная реализация интерфейса. + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Атрибут Conditional недопустим для "{0}", так как это конструктор, деструктор, оператор или явная реализация интерфейса. @@ -8413,8 +8473,8 @@ Give the compiler some way to differentiate the methods. For example, you can gi - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Параметры или локальные переменные типа "{0}" не могут объявляться в асинхронных методах или лямбда-выражениях. + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Параметры или локальные переменные типа "{0}" не могут объявляться в асинхронных методах и в асинхронных лямбда-выражениях. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index fcbbf5ccca853..9ab6883e83acc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ '{0}' özniteliği olay erişimcilerinde geçerli değil. Bu öznitelik yalnızca '{1}' bildirimlerinde geçerlidir. + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. Otomatik olarak uygulanan '{0}' özelliği 'set' erişimcisine sahip olduğundan 'readonly' olarak işaretlenemez. @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - '{0}' kayıt üyesi, '{2}' konumsal parametresi ile eşleşecek şekilde '{1}' türünde okunabilir bir örnek özelliği olmalıdır. + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + '{0}' kayıt üyesi, '{2}' konumsal parametresi ile eşleşecek şekilde '{1}' türünde okunabilir bir örnek özelliği olmalıdır. @@ -162,11 +167,21 @@ Komut satırı söz dizimi hatası: '{0}', '{1}' seçeneği için geçerli bir değer değil. Değer '{2}' biçiminde olmalıdır. + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. '{0}' &metot grubu, '{0}' temsilci türüne dönüştürülemiyor. + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. 'managed' çağırma kuralı, yönetilmeyen çağırma kuralı tanımlayıcılarla birleştirilemez. @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + Öznitelikte bir 'typeof' içinde işlev işaretçisi türü kullanılması desteklenmez. @@ -437,6 +452,16 @@ İşlev işaretçisi, adlandırılmış bağımsız değişkenler ile çağrılamaz. + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. Bir goto, aynı blok içinde yer alan using bildiriminden önceki bir konuma atlayamaz. @@ -447,6 +472,11 @@ Bir goto, using bildiriminden sonraki bir konuma atlayamaz. + + The positional member '{0}' found corresponding to this parameter is hidden. + Bu parametreye karşılık gelen konumsal üye '{0}' gizli. + + The suppression operator is not allowed in this context Gizleme işlecine bu bağlamda izin verilmez @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + Mühürlü bir 'Object.ToString' içeren bir kayıttan devralma işlemi C# {0} sürümünde desteklenmiyor. Lütfen '{1}' veya üstü bir dil sürümünü kullanın. @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + '{0}' değiştiricisi bu öğe için C# {1} sürümünde geçerli değil. Lütfen '{2}' veya daha yüksek bir dil sürümü kullanın. @@ -647,11 +677,6 @@ Çıkış dizini belirlenemedi - - The receiver type '{0}' is not a valid record type. - '{0}' alıcı türü geçerli bir kayıt türü değil. - - Record member '{0}' must be private. '{0}' kayıt üyesi özel olmalıdır. @@ -972,14 +997,49 @@ atılabilir değişkenler + + global using directive + global using directive + + target-typed object creation hedeflenen türde nesne oluşturma + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + kayıtta mühürlü ToString + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + Ayrıştırmada karışık bildirimler ve ifadeler @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - Kısmi '{0}' bildirimlerinin tümü sınıf, tümü kayıt, tümü yapı ya da tümü arabirim olmalıdır + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + Kısmi '{0}' bildirimlerinin tümü sınıf, tümü kayıt, tümü yapı ya da tümü arabirim olmalıdır @@ -5101,8 +5161,8 @@ Bu sınıf temel sınıf olarak kullanılırsa ve türetilen sınıf bir yıkıc - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Oluşturucu, yıkıcı, işleç veya açık arabirim uygulaması olduğundan Conditional özniteliği '{0}' üzerinde geçerli değil + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Oluşturucu, yıkıcı, işleç veya açık arabirim uygulaması olduğundan Conditional özniteliği '{0}' üzerinde geçerli değil @@ -8413,8 +8473,8 @@ Derleyiciye yöntemleri ayrıştırma yolu verin. Örneğin, bunlara farklı adl - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - Zaman uyumsuz yöntemlerde veya lambda ifadelerinde '{0}' türündeki parametrelerin veya yerel öğelerin bildirimi yapılamaz + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + Zaman uyumsuz yöntemlerde veya zaman uyumsuz lambda ifadelerinde '{0}' türündeki parametreler veya yerel öğeler bildirilemez. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 2b3e67c909fc6..d6d00dde3bc9b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ 特性“{0}”对事件访问器无效。它仅对“{1}”声明有效。 + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. 无法将自动实现的属性 "{0}" 标记为 "readonly",因为它具有 "set" 访问器。 @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - 记录成员“{0}"必须是“{1}”类型的可读实例属性才能匹配位置参数“{2}”。 + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + 记录成员“{0}"必须是“{1}”类型的可读实例属性才能匹配位置参数“{2}”。 @@ -162,11 +167,21 @@ 命令行语法错误:“{0}”不是“{1}”选项的有效值。值的格式必须为 "{2}"。 + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. 无法将方法组“{0}”转换为委托类型“{0}”(&M)。 + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. "managed" 调用约定不能与非托管调用约定说明符一起使用。 @@ -249,22 +264,22 @@ There is no target type for the default literal. - 默认文本没有目标类型。 + default 字面量缺少目标类型。 A default literal 'default' is not valid as a pattern. Use another literal (e.g. '0' or 'null') as appropriate. To match everything, use a discard pattern '_'. - 默认文本 "default" 作为模式无效。请相应使用其他文本(例如 "0" 或 "null")。若要匹配一切项,请使用放弃模式 "_"。 + 默认字面量“default”不能作为模式使用。请使用其他更适合的字面量(如“0”或“null”)。若要匹配任何输入,请使用放弃模式“_”。 A variable may not be declared within a 'not' or 'or' pattern. - 变量不能在 "not" 或 "or" 模式中声明。 + 在“not”或“or”模式中不能声明变量。 The discard pattern is not permitted as a case label in a switch statement. Use 'case var _:' for a discard pattern, or 'case @_:' for a constant named '_'. - 在 switch 语句中,不允许将放弃模式作为大小写标签。对于放弃模式,使用 "case var _:";对于名为 "_" 的常量,使用 "case @_:"。 + 在 switch 语句中,不允许将放弃模式作为 case 标签。请使用“case var _:”以表示放弃模式、使用“case @_:”以定义名为“_”的变量。 @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + 不支持在属性中使用 "typeof" 函数指针类型。 @@ -437,6 +452,16 @@ 不能使用命名参数调用函数指针。 + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. goto 无法跳转到同一块中 using 声明之前的某个位置。 @@ -447,6 +472,11 @@ goto 无法跳转到 using 声明后的某个位置。 + + The positional member '{0}' found corresponding to this parameter is hidden. + 已隐藏找到的此参数相应位置成员“{0}”。 + + The suppression operator is not allowed in this context 此上下文中不允许使用抑制运算符 @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + C# {0} 中不支持从包含密封 'Object.ToString' 的记录继承。请使用语言版本 '{1}’ 或更高版本。 @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + 在 C# {1} 中,修饰符 "{0}" 对此项无效。请使用语言版本 "{2}" 或更高版本。 @@ -647,11 +677,6 @@ 无法确定输出目录 - - The receiver type '{0}' is not a valid record type. - 指定的接收器类型“{0}”不是有效的记录类型。 - - Record member '{0}' must be private. 记录成员“{0}”必须是非公开的。 @@ -972,14 +997,49 @@ 弃元 + + global using directive + global using directive + + target-typed object creation 创建目标类型对象 + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + 记录的密封 ToString + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + 析构中混用的声明和表达式 @@ -1924,7 +1984,7 @@ default literal - 默认文本 + default 字面量 @@ -2184,7 +2244,7 @@ Converting null literal or possible null value to non-nullable type. - 将 null 文本或可能的 null 值转换为不可为 null 类型。 + 将 null 字面量或可能为 null 的值转换为非 null 类型。 @@ -2363,22 +2423,22 @@ Cannot convert null literal to non-nullable reference type. - 无法将 null 文本转换为不可为 null 的引用类型。 + 无法将 null 字面量转换为非 null 的引用类型。 Cannot convert null literal to non-nullable reference type. - 无法将 null 文本转换为不可为 null 的引用类型。 + 无法将 null 字面量转换为非 null 的引用类型。 Possible null reference argument for parameter '{0}' in '{1}'. - “{1}”中“{0}”形参的可能的 null 引用实参。 + “{1}”中的形参“{0}”可能传入 null 引用实参。 Possible null reference argument. - 可能的 null 引用参数。 + 引用类型参数可能为 null。 @@ -2388,12 +2448,12 @@ Possible null reference assignment. - 可能的 null 引用赋值。 + 引用类型赋值可能为 null。 Object or collection initializer implicitly dereferences possibly null member '{0}'. - 对象或集合初始值设定项会隐式取消引用可能为 null 的成员“{0}”。 + 对象或集合初始值设定项会隐式解引用可能为 null 的成员“{0}”。 @@ -2413,12 +2473,12 @@ Possible null reference return. - 可能的 null 引用返回。 + 可能返回 null 引用。 Possible null reference return. - 可能的 null 引用返回。 + 可能返回 null 引用。 @@ -2793,12 +2853,12 @@ Thrown value may be null. - 引发的值可为 null。 + 抛出的值可能为 null。 Thrown value may be null. - 引发的值可为 null。 + 抛出的值可能为 null。 @@ -3658,7 +3718,7 @@ The type caught or thrown must be derived from System.Exception - 捕获或抛弃的类型必须从 System.Exception 派生 + 捕获或抛出的值的类型必须从 System.Exception 派生 @@ -3668,7 +3728,7 @@ Control cannot leave the body of a finally clause - 控制不能离开 finally 子句主体 + 控制流不能从 finally 子句中离开 @@ -3863,7 +3923,7 @@ Use of default literal is not valid in this context - 在此背景下使用默认文本无效 + 在此上下文中不可使用 default 字面量 @@ -4167,8 +4227,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - “{0}”的分部声明必须是所有类、所有记录、所有结构或所有接口 + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + “{0}”的分部声明必须是所有类、所有记录、所有结构或所有接口 @@ -5106,8 +5166,8 @@ If such a class is used as a base class and if the deriving class defines a dest - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Conditional 特性在“{0}”上无效,因为它是构造函数、析构函数、运算符或显式接口实现 + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Conditional 特性在“{0}”上无效,因为它是构造函数、析构函数、运算符或显式接口实现 @@ -5732,7 +5792,7 @@ If such a class is used as a base class and if the deriving class defines a dest A throw statement with no arguments is not allowed in a finally clause that is nested inside the nearest enclosing catch clause - 在嵌套在最近的封闭 catch 子句内部的 finally 子句内不允许使用不带参数的 throw 语句 + 最近的封闭 catch 子句内嵌套的 finally 语句中不允许使用不带参数的 throw 语句 @@ -6017,7 +6077,7 @@ If such a class is used as a base class and if the deriving class defines a dest The first operand of an 'as' operator may not be a tuple literal without a natural type. - "as" 运算符的第一个操作数在没有自然类型的情况下可能不是元组文本。 + "as" 运算符的第一个操作数不能是一个没有自然类型的元组字面量。 @@ -6052,7 +6112,7 @@ If such a class is used as a base class and if the deriving class defines a dest An expression tree lambda may not contain a coalescing operator with a null or default literal left-hand side - 表达式树 lambda 不能包含左侧为 null 或默认文本的合并运算符 + lambda 表达式树不能包含左侧为 null 或 default 字面量的合并运算符 @@ -6102,7 +6162,7 @@ If such a class is used as a base class and if the deriving class defines a dest Too many characters in character literal - 字符文本中的字符太多 + 字符字面量中的字符太多 @@ -8418,8 +8478,8 @@ Give the compiler some way to differentiate the methods. For example, you can gi - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - 不能在异步方法或 lambda 表达式中声明“{0}”类型的参数或局部变量 + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + 不能在异步方法或异步 lambda 表达式中声明类型“{0}”的参数或局部变量。 @@ -9539,12 +9599,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Use Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal to create character literal tokens. - 使用 Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal 可创建字符文本标记。 + 使用 Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal 来创建字符字面量标记。 Use Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal to create numeric literal tokens. - 使用 Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal 可创建数字文本标记。 + 使用 Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal 来创建数字字面量标记。 @@ -10324,7 +10384,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. - 该程序所使用的用户字符串的合并后长度超出所允许的限制。请尝试减少字符串文本的使用。 + 该程序中的所有用户字符串在合并后,长度超出限制。请尝试减少字符串字面量的使用。 @@ -10459,12 +10519,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 'new' cannot be used with tuple type. Use a tuple literal expression instead. - '"new" 不能与元组类型共同使用。改用元组文本表达式。 + "new" 不能与元组类型共同使用。请改用元组字面量表达式。 Deconstruction 'var (...)' form disallows a specific type for 'var'. - 析构函数 "var (...)" 窗体驳回 "var" 的特定类型。 + “var (...)”形式的解构表达式不允许将“var”替换为某一特定类型。 @@ -10494,7 +10554,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple literal. - 表达式树不能包含元组文本。 + 表达式树不能包含元组字面量。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 2c811ba28ee0e..8356fd02a7354 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -77,6 +77,11 @@ 屬性 '{0}' 在事件存取子上無效。其只有在 '{1}' 宣告上才有效。 + + Attributes on lambda expressions require a parenthesized parameter list. + Attributes on lambda expressions require a parenthesized parameter list. + + Auto-implemented property '{0}' cannot be marked 'readonly' because it has a 'set' accessor. 因為自動實作屬性 '{0}' 有 'set' 存取子,所以無法將其標記為 'readonly'。 @@ -153,8 +158,8 @@ - Record member '{0}' must be a readable instance property of type '{1}' to match positional parameter '{2}'. - 記錄成員 '{0}' 必須是類型 '{1}' 的可讀取執行個體屬性,才能符合位置參數 '{2}'。 + Record member '{0}' must be a readable instance property or field of type '{1}' to match positional parameter '{2}'. + 記錄成員 '{0}' 必須是類型 '{1}' 的可讀取執行個體屬性,才能符合位置參數 '{2}'。 @@ -162,11 +167,21 @@ 命令列語法錯誤: '{0}' 對 '{1}' 選項而言不是有效的值。此值的格式必須是 '{2}'。 + + The receiver type '{0}' is not a valid record type and is not a struct type. + The receiver type '{0}' is not a valid record type and is not a struct type. + + Cannot convert &method group '{0}' to delegate type '{0}'. 無法將方法群組 '{0}' 轉換成委派類型 '{0}'(&M) + + The delegate type could not be inferred. + The delegate type could not be inferred. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. 'managed' 呼叫慣例不得與未受控的呼叫慣例指定名稱並用。 @@ -429,7 +444,7 @@ Using a function pointer type in a 'typeof' in an attribute is not supported. - Using a function pointer type in a 'typeof' in an attribute is not supported. + 不支援在屬性的 'typeof' 中使用函式指標類型。 @@ -437,6 +452,16 @@ 無法以具名引數呼叫函式指標。 + + A global using directive cannot be used in a namespace declaration. + A global using directive cannot be used in a namespace declaration. + + + + A global using directive must precede all non-global using directives. + A global using directive must precede all non-global using directives. + + A goto cannot jump to a location before a using declaration within the same block. 在相同區塊內,goto 不可跳到 using 宣告前的位置。 @@ -447,6 +472,11 @@ goto 不可跳到 using 宣告後的位置。 + + The positional member '{0}' found corresponding to this parameter is hidden. + 找到之與此參數對應的「{0}」位置成員已隱藏。 + + The suppression operator is not allowed in this context 此內容不允許隱藏項目運算子 @@ -484,7 +514,7 @@ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. - Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + C # {0} 不支援從具有密封的 'Object.ToString' 的記錄繼承。請使用 '{1}' 或更高的語言版本。 @@ -524,7 +554,7 @@ The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. - The modifier '{0}' is not valid for this item in C# {1}. Please use language version '{2}' or greater. + 修飾元 '{0}' 在 C# {1} 中對此項目無效。請使用 '{2}' 或更高的語言版本。 @@ -647,11 +677,6 @@ 無法判斷輸出目錄 - - The receiver type '{0}' is not a valid record type. - 接收器類型 '{0}' 不是有效的記錄類型。 - - Record member '{0}' must be private. 記錄成員 '{0}' 必須為私人。 @@ -972,14 +997,49 @@ Discard + + global using directive + global using directive + + target-typed object creation 建立具目標類型的物件 + + inferred delegate type + inferred delegate type + + + + lambda attributes + lambda attributes + + + + positional fields in records + positional fields in records + + + + record structs + record structs + 'record structs' is not localizable. + sealed ToString in record - sealed ToString in record + 記錄中有密封的 ToString + + + + with on anonymous types + with on anonymous types + + + + with on structs + with on structs @@ -1024,7 +1084,7 @@ Mixed declarations and expressions in deconstruction - Mixed declarations and expressions in deconstruction + 解構中的混合宣告與運算式 @@ -4162,8 +4222,8 @@ - Partial declarations of '{0}' must be all classes, all records, all structs, or all interfaces - '{0}' 中有一部分宣告必須全是類別、全是結構,或全是介面 + Partial declarations of '{0}' must be all classes, all record classes, all structs, all record structs, or all interfaces + '{0}' 中有一部分宣告必須全是類別、全是結構,或全是介面 @@ -5101,8 +5161,8 @@ If such a class is used as a base class and if the deriving class defines a dest - The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, or explicit interface implementation - Conditional 屬性在 '{0}' 上無效,因為其為建構函式、解構函式、運算子或明確介面實作 + The Conditional attribute is not valid on '{0}' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + Conditional 屬性在 '{0}' 上無效,因為其為建構函式、解構函式、運算子或明確介面實作 @@ -8413,8 +8473,8 @@ Give the compiler some way to differentiate the methods. For example, you can gi - Parameters or locals of type '{0}' cannot be declared in async methods or lambda expressions. - 類型 '{0}' 的參數或區域變數,不可在非同步方法或 Lambda 運算式中宣告。 + Parameters or locals of type '{0}' cannot be declared in async methods or async lambda expressions. + 類型 '{0}' 的參數或區域變數,不可在非同步方法或非同步 Lambda 運算式中宣告。 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 82781686a4f44..2c59e7cc97d26 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -61,13 +61,16 @@ static CommandLineTests() var cscDllPath = Path.Combine( Path.GetDirectoryName(typeof(CommandLineTests).GetTypeInfo().Assembly.Location), Path.Combine("dependency", "csc.dll")); - var dotnetExe = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + var dotnetExe = DotNetCoreSdk.ExePath; var netStandardDllPath = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(assembly => !assembly.IsDynamic && assembly.Location.EndsWith("netstandard.dll")).Location; var netStandardDllDir = Path.GetDirectoryName(netStandardDllPath); + // Since we are using references based on the UnitTest's runtime, we need to use + // its runtime config when executing out program. + var runtimeConfigPath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "runtimeconfig.json"); s_CSharpCompilerExecutable = $@"""{dotnetExe}"" ""{cscDllPath}"" /r:""{netStandardDllPath}"" /r:""{netStandardDllDir}/System.Private.CoreLib.dll"" /r:""{netStandardDllDir}/System.Console.dll"" /r:""{netStandardDllDir}/System.Runtime.dll"""; - s_DotnetCscRun = $@"""{dotnetExe}"" exec --runtimeconfig ""{Path.Combine(Path.GetDirectoryName(cscDllPath), "csc.runtimeconfig.json")}"""; + s_DotnetCscRun = $@"""{dotnetExe}"" exec --runtimeconfig ""{runtimeConfigPath}"""; s_CSharpScriptExecutable = s_CSharpCompilerExecutable.Replace("csc.dll", Path.Combine("csi", "csi.dll")); #else s_CSharpScriptExecutable = s_CSharpCompilerExecutable.Replace("csc.exe", Path.Combine("csi", "csi.exe")); @@ -11816,6 +11819,21 @@ public void TestCategoryBasedBulkAnalyzerDiagnosticConfiguration(DiagnosticSever dotnet_analyzer_diagnostic.category-{category}.severity = error"; TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Warn); + // Verify global config based configuration before diagnostic ID configuration is not respected. + analyzerConfigText = $@" +is_global = true +dotnet_analyzer_diagnostic.category-{category}.severity = error +dotnet_diagnostic.{diagnosticId}.severity = warning"; + TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Warn); + + // Verify global config based configuration after diagnostic ID configuration is not respected. + analyzerConfigText = $@" +is_global = true +dotnet_diagnostic.{diagnosticId}.severity = warning +dotnet_analyzer_diagnostic.category-{category}.severity = error"; + TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Warn); + + // Verify disabled by default analyzer is not enabled by category based configuration. analyzer = new NamedTypeAnalyzerWithConfigurableEnabledByDefault(isEnabledByDefault: false, defaultSeverity); analyzerConfigText = $@" @@ -11823,6 +11841,13 @@ public void TestCategoryBasedBulkAnalyzerDiagnosticConfiguration(DiagnosticSever dotnet_analyzer_diagnostic.category-{category}.severity = error"; TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Suppress); + // Verify disabled by default analyzer is not enabled by category based configuration in global config + analyzer = new NamedTypeAnalyzerWithConfigurableEnabledByDefault(isEnabledByDefault: false, defaultSeverity); + analyzerConfigText = $@" +is_global=true +dotnet_analyzer_diagnostic.category-{category}.severity = error"; + TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Suppress); + if (defaultSeverity == DiagnosticSeverity.Hidden || defaultSeverity == DiagnosticSeverity.Info && !errorlog) { @@ -11833,6 +11858,12 @@ public void TestCategoryBasedBulkAnalyzerDiagnosticConfiguration(DiagnosticSever // Verify that bulk configuration 'none' entry does not enable this analyzer. analyzerConfigText = $@" [*.cs] +dotnet_analyzer_diagnostic.category-{category}.severity = none"; + TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Suppress); + + // Verify that bulk configuration 'none' entry does not enable this analyzer via global config + analyzerConfigText = $@" +[*.cs] dotnet_analyzer_diagnostic.category-{category}.severity = none"; TestBulkAnalyzerConfigurationCore(analyzer, analyzerConfigText, errorlog, expectedDiagnosticSeverity: ReportDiagnostic.Suppress); } diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Conditional.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Conditional.cs index 698a8e4dd0971..00d3221326118 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Conditional.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Conditional.cs @@ -5,17 +5,14 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.CSharp.UnitTests.Emit; -using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; -using System.Collections.Generic; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { @@ -690,6 +687,29 @@ class Bar Diagnostic(ErrorCode.ERR_BadAttributeArgument, "Goo.M")); } + [Fact] + public void ConditionalAttributeArgument_Null() + { + var source = +@"using System.Diagnostics; +class Program +{ + [Conditional(""A"")] + [Conditional(null)] + [Conditional(""B"")] + static void Main() + { + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,18): error CS0633: The argument to the 'Conditional' attribute must be a valid identifier + // [Conditional(null)] + Diagnostic(ErrorCode.ERR_BadArgumentToAttribute, "null").WithArguments("Conditional").WithLocation(5, 18)); + var method = comp.GetMember("Program.Main"); + Assert.Equal(new[] { "A", null, "B" }, method.GetAppliedConditionalSymbols()); + } + #endregion } } diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Locations.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Locations.cs index d227fad8838d0..18fc9ecd2f117 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Locations.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Locations.cs @@ -170,6 +170,102 @@ struct S Diagnostic(ErrorCode.WRN_InvalidAttributeLocation, "delegate").WithArguments("delegate", "type")); } + [Fact] + public void OnRecordStruct() + { + var source = @" +using System; + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true)] +public class A : Attribute { } + +[A] +[assembly: A] +[module: A] +[type: A] +[method: A] +[field: A] +[property: A] +[event: A] +[return: A] +[param: A] +[typevar: A] +[delegate: A] +record struct S +{ +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (8,2): warning CS0657: 'assembly' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "assembly").WithArguments("assembly", "type"), + // (9,2): warning CS0657: 'module' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "module").WithArguments("module", "type"), + // (11,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type"), + // (12,2): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "type"), + // (13,2): warning CS0657: 'property' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "property").WithArguments("property", "type"), + // (14,2): warning CS0657: 'event' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "event").WithArguments("event", "type"), + // (15,2): warning CS0657: 'return' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "return").WithArguments("return", "type"), + // (16,2): warning CS0657: 'param' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "param").WithArguments("param", "type"), + // (17,2): warning CS0657: 'typevar' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "typevar").WithArguments("typevar", "type"), + // (18,2): warning CS0658: 'delegate' is not a recognized attribute location. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_InvalidAttributeLocation, "delegate").WithArguments("delegate", "type")); + } + + [Fact] + public void OnRecordClass() + { + var source = @" +using System; + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true)] +public class A : Attribute { } + +[A] +[assembly: A] +[module: A] +[type: A] +[method: A] +[field: A] +[property: A] +[event: A] +[return: A] +[param: A] +[typevar: A] +[delegate: A] +record class S +{ +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (8,2): warning CS0657: 'assembly' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "assembly").WithArguments("assembly", "type"), + // (9,2): warning CS0657: 'module' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "module").WithArguments("module", "type"), + // (11,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type"), + // (12,2): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "type"), + // (13,2): warning CS0657: 'property' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "property").WithArguments("property", "type"), + // (14,2): warning CS0657: 'event' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "event").WithArguments("event", "type"), + // (15,2): warning CS0657: 'return' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "return").WithArguments("return", "type"), + // (16,2): warning CS0657: 'param' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "param").WithArguments("param", "type"), + // (17,2): warning CS0657: 'typevar' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "typevar").WithArguments("typevar", "type"), + // (18,2): warning CS0658: 'delegate' is not a recognized attribute location. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + Diagnostic(ErrorCode.WRN_InvalidAttributeLocation, "delegate").WithArguments("delegate", "type")); + } + [Fact] public void OnEnum() { diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Security.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Security.cs index 562a6ea2aaa9e..75590db21b1b3 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Security.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Security.cs @@ -786,6 +786,44 @@ static void local1() {} }); } + [Fact] + public void CheckSecurityAttributeOnLambda() + { + string source = +@"using System; +using System.Security.Permissions; +class Program +{ + static void Main() + { + Action a = [PrincipalPermission(SecurityAction.Demand, Role=@""User1"")] () => { }; + } +}"; + var compilation = CreateCompilationWithMscorlib40(source, options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(compilation, symbolValidator: module => + { + ValidateDeclSecurity(module, new DeclSecurityEntry + { + ActionFlags = DeclarativeSecurityAction.Demand, + ParentKind = SymbolKind.Method, + ParentNameOpt = @"
b__0_0", + PermissionSet = + "." + // always start with a dot + "\u0001" + // number of attributes (small enough to fit in 1 byte) + "\u0080\u0085" + // length of UTF-8 string (0x80 indicates a 2-byte encoding) + "System.Security.Permissions.PrincipalPermissionAttribute, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" + // attr type name + "\u000e" + // number of bytes in the encoding of the named arguments + "\u0001" + // number of named arguments + "\u0054" + // property (vs field) + "\u000e" + // type string + "\u0004" + // length of UTF-8 string (small enough to fit in 1 byte) + "Role" + // property name + "\u0005" + // length of UTF-8 string (small enough to fit in 1 byte) + "User1", // argument value (@"User1") + }); + }); + } + [Fact] public void CheckMultipleSecurityAttributes_SameType_SameAction() { diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs index 1001d66ff3e92..022e63e1cb9b1 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs @@ -11221,6 +11221,96 @@ void local() Assert.False(verifier.HasLocalsInit("C.g__local|0_0")); } + [Theory] + [InlineData(false, false, false, true)] + [InlineData(false, false, true, false)] + [InlineData(false, true, false, false)] + [InlineData(false, true, true, false)] + [InlineData(true, false, false, false)] + [InlineData(true, false, true, false)] + [InlineData(true, true, false, false)] + [InlineData(true, true, true, false)] + public void SkipLocalsInit_Lambda_01(bool skipLocalsType, bool skipLocalsMethod, bool skipLocalsLambda, bool expectedAreLocalsZeroed) + { + var source = +$@"using System; +using System.Runtime.CompilerServices; +{GetSkipLocalsInit(skipLocalsType)} class Program +{{ + {GetSkipLocalsInit(skipLocalsMethod)} static void Main() + {{ + Action a = {GetSkipLocalsInit(skipLocalsLambda)} () => {{ int x = 1; x = x + x + x; }}; + }} +}}"; + var verifier = CompileAndVerifyWithSkipLocalsInit(source, parseOptions: TestOptions.RegularPreview, verify: Verification.Skipped); + Assert.Equal(expectedAreLocalsZeroed, verifier.HasLocalsInit("Program.<>c.
b__0_0()")); + + var comp = (CSharpCompilation)verifier.Compilation; + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + var lambda = exprs.SelectAsArray(e => model.GetSymbolInfo(e).Symbol).Single().GetSymbol(); + Assert.Equal(expectedAreLocalsZeroed, lambda.AreLocalsZeroed); + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, false)] + public void SkipLocalsInit_Lambda_02(bool skipLocalsType, bool skipLocalsLambda, bool expectedAreLocalsZeroed) + { + var source = +$@"using System; +using System.Runtime.CompilerServices; +{GetSkipLocalsInit(skipLocalsType)} class Program +{{ + Action A = {GetSkipLocalsInit(skipLocalsLambda)} () => {{ int x = 1; x = x + x + x; }}; +}}"; + var verifier = CompileAndVerifyWithSkipLocalsInit(source, parseOptions: TestOptions.RegularPreview, verify: Verification.Skipped); + Assert.Equal(expectedAreLocalsZeroed, verifier.HasLocalsInit("Program.<>c.<.ctor>b__1_0()")); + + var comp = (CSharpCompilation)verifier.Compilation; + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + var lambda = exprs.SelectAsArray(e => model.GetSymbolInfo(e).Symbol).Single().GetSymbol(); + Assert.Equal(expectedAreLocalsZeroed, lambda.AreLocalsZeroed); + } + + // [SkipLocalsInit] on constructor is ignored + [Theory] + [InlineData(false, false, false, true)] + [InlineData(false, false, true, false)] + [InlineData(false, true, false, true)] + [InlineData(false, true, true, false)] + [InlineData(true, false, false, false)] + [InlineData(true, false, true, false)] + [InlineData(true, true, false, false)] + [InlineData(true, true, true, false)] + public void SkipLocalsInit_Lambda_03(bool skipLocalsType, bool skipLocalsMethod, bool skipLocalsLambda, bool expectedAreLocalsZeroed) + { + var source = +$@"using System; +using System.Runtime.CompilerServices; +{GetSkipLocalsInit(skipLocalsType)} class Program +{{ + Action A = {GetSkipLocalsInit(skipLocalsLambda)} () => {{ int x = 1; x = x + x + x; }}; + {GetSkipLocalsInit(skipLocalsMethod)} Program() {{ }} +}}"; + var verifier = CompileAndVerifyWithSkipLocalsInit(source, parseOptions: TestOptions.RegularPreview, verify: Verification.Skipped); + Assert.Equal(expectedAreLocalsZeroed, verifier.HasLocalsInit("Program.<>c.<.ctor>b__1_0()")); + + var comp = (CSharpCompilation)verifier.Compilation; + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + var lambda = exprs.SelectAsArray(e => model.GetSymbolInfo(e).Symbol).Single().GetSymbol(); + Assert.Equal(expectedAreLocalsZeroed, lambda.AreLocalsZeroed); + } + + private static string GetSkipLocalsInit(bool skipLocalsInit) => skipLocalsInit ? "[SkipLocalsInit]" : ""; + [Fact, WorkItem(49434, "https://github.com/dotnet/roslyn/issues/49434")] public void SkipLocalsInit_Module_TopLevelStatements() { @@ -13426,5 +13516,46 @@ public event System.Action E // [Deprecated("don't use this", DeprecationType.Remove, 50331648u)] Diagnostic(ErrorCode.ERR_AttributeNotOnEventAccessor, "Deprecated").WithArguments("Windows.Foundation.Metadata.DeprecatedAttribute", "class, struct, enum, constructor, method, property, indexer, field, event, interface, delegate").WithLocation(7, 10)); } + + [Fact, WorkItem(53418, "https://github.com/dotnet/roslyn/issues/53418")] + public void TestObsoleteIndexerSlice() + { + var code = @" +using System; + +public class C +{ + [Obsolete(""error"", error: true)] + public int Slice(int i, int j) => 0; + + [Obsolete(""error"", error: true)] + public int this[int i] => 0; + + [Obsolete(""error"", error: true)] + public int Count => 0; + + public void M() + { + _ = this[^1]; + _ = this[..]; + } +} +"; + + CreateCompilation(code, targetFramework: TargetFramework.NetCoreApp).VerifyDiagnostics( + // (17,13): error CS0619: 'C.this[int]' is obsolete: 'error' + // _ = this[^1]; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "this[^1]").WithArguments("C.this[int]", "error").WithLocation(17, 13), + // (17,13): error CS0619: 'C.Count' is obsolete: 'error' + // _ = this[^1]; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "this[^1]").WithArguments("C.Count", "error").WithLocation(17, 13), + // (18,13): error CS0619: 'C.Slice(int, int)' is obsolete: 'error' + // _ = this[..]; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "this[..]").WithArguments("C.Slice(int, int)", "error").WithLocation(18, 13), + // (18,13): error CS0619: 'C.Count' is obsolete: 'error' + // _ = this[..]; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "this[..]").WithArguments("C.Count", "error").WithLocation(18, 13) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs index 663817cd8f06a..038c442ed13cd 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs @@ -2070,6 +2070,38 @@ void NamedOptional(int x = 2) VerifyOutputInMain(source, "3 2", "System"); } + [Fact, WorkItem(51518, "https://github.com/dotnet/roslyn/issues/51518")] + public void OptionalParameterCodeGen() + { + var source = @" +public class C +{ + public static void Main() + { + LocalFunc(); + void LocalFunc(int a = 2) { } + } +} +"; + var comp = CreateCompilationWithMscorlib45AndCSharp(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedSignatures: new SignatureDescription[] + { + Signature("C", "Main", ".method public hidebysig static System.Void Main() cil managed"), + Signature("C", "
g__LocalFunc|0_0", ".method [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] assembly hidebysig static System.Void
g__LocalFunc|0_0([opt] System.Int32 a = 2) cil managed") + }); + } + + [Fact, WorkItem(53478, "https://github.com/dotnet/roslyn/issues/53478")] + public void OptionalParameterCodeGen_Reflection() + { + VerifyOutputInMain(@"void TestAction(int i = 5) { } + + var d = (Action)TestAction; + var p2 = d.Method.GetParameters(); + Console.WriteLine(p2[0].HasDefaultValue); + Console.WriteLine(p2[0].DefaultValue);", @"True +5", new[] { "System" }); + } [Fact] [CompilerTrait(CompilerFeature.Dynamic)] diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOverridingAndHiding.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOverridingAndHiding.cs index ce2d5cba3a279..e013c7f76c9a1 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOverridingAndHiding.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOverridingAndHiding.cs @@ -1190,8 +1190,9 @@ static void Main() }; var substitutedSource = subst(substitutedSource0); - var compilation = CreateCompilation(substitutedSource, options: TestOptions.ReleaseExe); + var compilation = CreateCompilation(substitutedSource, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.StandardLatest); string expectedOutput; + Assert.Equal(RuntimeUtilities.IsCoreClrRuntime, compilation.Assembly.RuntimeSupportsCovariantReturnsOfClasses); if (compilation.Assembly.RuntimeSupportsCovariantReturnsOfClasses) { // Correct runtime behavior with no warning diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs index d71899dc52a80..5c099ac55b72b 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs @@ -1358,9 +1358,12 @@ static void Main() // (6,30): error CS0034: Operator '==' is ambiguous on operands of type '' and 'default' // System.Console.Write((null, () => 1) == (default, default)); Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, () => 1) == (default, default)").WithArguments("==", "", "default").WithLocation(6, 30), - // (6,30): error CS0019: Operator '==' cannot be applied to operands of type 'lambda expression' and 'default' + // (6,37): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // System.Console.Write((null, () => 1) == (default, default)); - Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, () => 1) == (default, default)").WithArguments("==", "lambda expression", "default").WithLocation(6, 30), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => 1").WithArguments("inferred delegate type").WithLocation(6, 37), + // (6,37): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, () => 1) == (default, default)); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => 1").WithArguments("inferred delegate type").WithLocation(6, 37), // (7,30): error CS0034: Operator '==' is ambiguous on operands of type '(, lambda expression)' and 'default' // System.Console.Write((null, () => 2) == default); Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, () => 2) == default").WithArguments("==", "(, lambda expression)", "default").WithLocation(7, 30) @@ -1654,66 +1657,94 @@ class C { static void Main() { - System.Console.Write((null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })); + System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); } }"; - var comp = CreateCompilation(source); + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '' and 'lambda expression' - // System.Console.Write((null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })); - Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "", "lambda expression").WithLocation(6, 30), - // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '' and 'method group' - // System.Console.Write((null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })); - Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "", "method group").WithLocation(6, 30), - // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '' and 'lambda expression' - // System.Console.Write((null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })); - Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, () => { }, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "", "lambda expression").WithLocation(6, 30) - ); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - - // check tuple on the left - var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(0); - Assert.Equal("(null, null, null, null)", tuple1.ToString()); - - var tupleType1 = model.GetTypeInfo(tuple1); - Assert.Null(tupleType1.Type); - Assert.Null(tupleType1.ConvertedType); - - // check tuple on the right ... - var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(1); - Assert.Equal("(null, () => { }, Main, (int i) => { int j = 0; return i + j; })", tuple2.ToString()); - - var tupleType2 = model.GetTypeInfo(tuple2); - Assert.Null(tupleType2.Type); - Assert.Null(tupleType2.ConvertedType); - - // ... its first lambda ... - var firstLambda = tuple2.Arguments[1].Expression; - Assert.Null(model.GetTypeInfo(firstLambda).Type); - Assert.Null(model.GetTypeInfo(firstLambda).ConvertedType); - - // ... its method group ... - var methodGroup = tuple2.Arguments[2].Expression; - Assert.Null(model.GetTypeInfo(methodGroup).Type); - Assert.Null(model.GetTypeInfo(methodGroup).ConvertedType); - Assert.Null(model.GetSymbolInfo(methodGroup).Symbol); - Assert.Equal(new[] { "void C.Main()" }, model.GetSymbolInfo(methodGroup).CandidateSymbols.Select(s => s.ToTestDisplayString())); - - // ... its second lambda and the symbols it uses - var secondLambda = tuple2.Arguments[3].Expression; - Assert.Null(model.GetTypeInfo(secondLambda).Type); - Assert.Null(model.GetTypeInfo(secondLambda).ConvertedType); - - var addition = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Last(); - Assert.Equal("i + j", addition.ToString()); - - var i = addition.Left; - Assert.Equal("System.Int32 i", model.GetSymbolInfo(i).Symbol.ToTestDisplayString()); - - var j = addition.Right; - Assert.Equal("System.Int32 j", model.GetSymbolInfo(j).Symbol.ToTestDisplayString()); + // (6,65): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "x => x").WithArguments("inferred delegate type").WithLocation(6, 65), + // (6,65): error CS8917: The delegate type could not be inferred. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(6, 65), + // (6,65): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "x => x").WithArguments("inferred delegate type").WithLocation(6, 65), + // (6,65): error CS8917: The delegate type could not be inferred. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(6, 65), + // (6,73): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Main").WithArguments("inferred delegate type").WithLocation(6, 73), + // (6,73): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Main").WithArguments("inferred delegate type").WithLocation(6, 73), + // (6,79): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int i) => { int j = 0; return i + j; }").WithArguments("inferred delegate type").WithLocation(6, 79), + // (6,79): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int i) => { int j = 0; return i + j; }").WithArguments("inferred delegate type").WithLocation(6, 79)); + verify(comp); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (6,65): error CS8917: The delegate type could not be inferred. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(6, 65), + // (6,65): error CS8917: The delegate type could not be inferred. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(6, 65)); + verify(comp); + + static void verify(CSharpCompilation comp) + { + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + + // check tuple on the left + var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(0); + Assert.Equal("(null, null, null, null)", tuple1.ToString()); + + var tupleType1 = model.GetTypeInfo(tuple1); + Assert.Null(tupleType1.Type); + Assert.Null(tupleType1.ConvertedType); + + // check tuple on the right ... + var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(1); + Assert.Equal("(null, x => x, Main, (int i) => { int j = 0; return i + j; })", tuple2.ToString()); + + var tupleType2 = model.GetTypeInfo(tuple2); + Assert.Null(tupleType2.Type); + Assert.Null(tupleType2.ConvertedType); + + // ... its first lambda ... + var firstLambda = tuple2.Arguments[1].Expression; + Assert.Null(model.GetTypeInfo(firstLambda).Type); + Assert.Equal("System.Delegate", model.GetTypeInfo(firstLambda).ConvertedType.ToTestDisplayString()); + + // ... its method group ... + var methodGroup = tuple2.Arguments[2].Expression; + Assert.Null(model.GetTypeInfo(methodGroup).Type); + Assert.Equal("System.Delegate", model.GetTypeInfo(methodGroup).ConvertedType.ToTestDisplayString()); + Assert.Null(model.GetSymbolInfo(methodGroup).Symbol); + Assert.Equal(new[] { "void C.Main()" }, model.GetSymbolInfo(methodGroup).CandidateSymbols.Select(s => s.ToTestDisplayString())); + + // ... its second lambda and the symbols it uses + var secondLambda = tuple2.Arguments[3].Expression; + Assert.Equal("System.Func", model.GetTypeInfo(secondLambda).Type.ToTestDisplayString()); + Assert.Equal("System.Delegate", model.GetTypeInfo(secondLambda).ConvertedType.ToTestDisplayString()); + + var addition = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Last(); + Assert.Equal("i + j", addition.ToString()); + + var i = addition.Left; + Assert.Equal("System.Int32 i", model.GetSymbolInfo(i).Symbol.ToTestDisplayString()); + + var j = addition.Right; + Assert.Equal("System.Int32 j", model.GetSymbolInfo(j).Symbol.ToTestDisplayString()); + } } [Fact] @@ -1986,9 +2017,9 @@ public void M() // (6,13): error CS0815: Cannot assign (, ) to an implicitly-typed variable // var t = (null, null); Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "t = (null, null)").WithArguments("(, )").WithLocation(6, 13), - // (7,13): error CS0019: Operator '==' cannot be applied to operands of type '' and 'lambda expression' + // (7,22): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // if (null == (() => {}) ) {} - Diagnostic(ErrorCode.ERR_BadBinaryOps, "null == (() => {})").WithArguments("==", "", "lambda expression").WithLocation(7, 13), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => {}").WithArguments("inferred delegate type").WithLocation(7, 22), // (8,13): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'int' // if ("" == 1) {} Diagnostic(ErrorCode.ERR_BadBinaryOps, @""""" == 1").WithArguments("==", "string", "int").WithLocation(8, 13) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs index 9b0cdd1aa69e8..e6f6e36bfa95c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs @@ -1634,10 +1634,10 @@ public static void M(int[,] s) comp.VerifyDiagnostics( // (12,27): error CS0029: Cannot implicitly convert type 'System.Index' to 'int' // Console.WriteLine(s[new Index(1, false), ^1]); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "s[new Index(1, false), ^1]").WithArguments("System.Index", "int").WithLocation(12, 27), + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new Index(1, false)").WithArguments("System.Index", "int").WithLocation(12, 29), // (12,27): error CS0029: Cannot implicitly convert type 'System.Index' to 'int' // Console.WriteLine(s[new Index(1, false), ^1]); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "s[new Index(1, false), ^1]").WithArguments("System.Index", "int").WithLocation(12, 27)); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "^1").WithArguments("System.Index", "int").WithLocation(12, 50)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 16b0a387b8ef1..81c4fecd75385 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -4880,7 +4880,7 @@ public class B 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 }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (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): @@ -4899,7 +4899,7 @@ public class B "); VerifyOperationTreeForNode(compilation, model, switchExpressions[1], @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... > new B() }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (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): @@ -4930,7 +4930,7 @@ public class B "); VerifyOperationTreeForNode(compilation, model, switchExpressions[2], @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: '1 switch { ... ing.Empty }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: '1 switch { ... ing.Empty }') Value: ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') Arms(2): @@ -5012,7 +5012,7 @@ public class B Instance Receiver: null Right: - ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... 1, _ => 2 }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (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): @@ -5040,7 +5040,7 @@ public class B IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: '1 switch { ... > new B() }') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: - ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... > new B() }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (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): @@ -5080,7 +5080,7 @@ public class B 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 }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: '1 switch { ... ing.Empty }') Value: ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') Arms(2): @@ -5163,7 +5163,7 @@ public class B 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 }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: '1 switch { ... 1, _ => 2 }') Value: ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') Arms(2): @@ -5190,7 +5190,7 @@ public class B 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() }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?) (Syntax: '1 switch { ... > new B() }') Value: ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') Arms(2): @@ -5229,7 +5229,7 @@ public class B 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 }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?) (Syntax: '1 switch { ... ing.Empty }') Value: ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') Arms(2): diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs index 7ab3020d503e2..805e7440a5a59 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs @@ -2589,6 +2589,42 @@ int P2 AssertInstrumented(verifier, "C.g__L4|5_0"); } + [Fact] + public void ExcludeFromCodeCoverageAttribute_LambdaAttributes() + { + string source = +@"using System; +using System.Diagnostics.CodeAnalysis; +class Program +{ + static void M1() + { + Action a1 = static () => + { + Func f1 = [ExcludeFromCodeCoverage] static (bool b) => { if (b) return 0; return 1; }; + Func f2 = static (bool b) => { if (b) return 0; return 1; }; + }; + } + static void M2() + { + Action a2 = [ExcludeFromCodeCoverage] static () => + { + Func f3 = [ExcludeFromCodeCoverage] static (bool b) => { if (b) return 0; return 1; }; + Func f4 = static (bool b) => { if (b) return 0; return 1; }; + }; + } +}"; + var verifier = CompileAndVerify(source + InstrumentationHelperSource, options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularPreview); + AssertInstrumented(verifier, "Program.M1"); + AssertInstrumented(verifier, "Program.<>c__DisplayClass0_0.b__0()"); + AssertNotInstrumented(verifier, "Program.<>c.b__0_1(bool)"); + AssertInstrumented(verifier, "Program.<>c__DisplayClass0_0.b__2(bool)"); + AssertInstrumented(verifier, "Program.M2"); + AssertNotInstrumented(verifier, "Program.<>c.b__1_0()"); + AssertNotInstrumented(verifier, "Program.<>c.b__1_1(bool)"); + AssertNotInstrumented(verifier, "Program.<>c.b__1_2(bool)"); + } + [Fact] public void ExcludeFromCodeCoverageAttribute_Type() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/AssemblyReferencesTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/AssemblyReferencesTests.cs index 9fe24387baf54..068ba6382dc68 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/AssemblyReferencesTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/AssemblyReferencesTests.cs @@ -81,7 +81,7 @@ class C c2.GlobalNamespace.GetMember("C").GetMember("Main")) }; - c2.EmitDifference(baseline, edits, mdStream, ilStream, pdbStream, updatedMethods); + c2.EmitDifference(baseline, edits, s => false, mdStream, ilStream, pdbStream); var actualIL = ImmutableArray.Create(ilStream.ToArray()).GetMethodIL(); var expectedIL = @" @@ -152,7 +152,7 @@ class C c2.GlobalNamespace.GetMember("C").GetMember("Main")) }; - c2.EmitDifference(baseline, edits, mdStream, ilStream, pdbStream, updatedMethods); + c2.EmitDifference(baseline, edits, s => false, mdStream, ilStream, pdbStream); var actualIL = ImmutableArray.Create(ilStream.ToArray()).GetMethodIL(); diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs index a43fee1e354d0..003401a3770ab 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs @@ -125,6 +125,141 @@ void F() Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default)); } + [Fact] + public void MethodWithSwitchExpression() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + int F(object o) + { + return o switch + { + int i => new Func(() => i + 1)(), + _ => 0 + }; + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + int F(object o) + { + return o switch + { + int i => new Func(() => i + 2)(), + _ => 0 + }; + } +}"); + var compilation0 = CreateCompilation(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C.<>c__DisplayClass0_0: {5__2, b__0}", + "C: {<>c__DisplayClass0_0}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + var x = Visualize(generation0.OriginalMetadata, md1); + + // Method updates + CheckEncLogDefinitions(reader1, + Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + + [Fact] + public void MethodWithNestedSwitchExpression() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + int F(object o) + { + return o switch + { + int i => new Func(() => i + (int)o + i switch + { + 1 => 1, + _ => new Func(() => (int)o + 1)() + })(), + _ => 0 + }; + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + int F(object o) + { + return o switch + { + int i => new Func(() => i + (int)o + i switch + { + 1 => 1, + _ => new Func(() => (int)o + 2)() + })(), + _ => 0 + }; + } +}"); + var compilation0 = CreateCompilation(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {<>c__DisplayClass0_0, <>c__DisplayClass0_1}", + "C.<>c__DisplayClass0_1: {5__2, CS$<>8__locals2, b__0}", + "C.<>c__DisplayClass0_0: {o, <>9__1, b__1}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates + CheckEncLogDefinitions(reader1, + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + [Fact] public void MethodWithStaticLocalFunction1() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs index cc9e92b89a723..7dae6013bdcab 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs @@ -58,6 +58,8 @@ static IEnumerable G() using var md1 = diff1.GetMetadata(); var reader1 = md1.Reader; + diff1.VerifyUpdatedTypes("0x02000002"); + CheckEncLog(reader1, Row(2, TableIndex.AssemblyRef, EditAndContinueOperation.Default), Row(17, TableIndex.MemberRef, EditAndContinueOperation.Default), @@ -860,6 +862,8 @@ static async Task F() // only methods with sequence points should be listed in UpdatedMethods: diff1.VerifyUpdatedMethods("0x06000004"); + diff1.VerifyUpdatedTypes("0x02000002", "0x02000003"); + using (var md1 = diff1.GetMetadata()) { // Verify that no new TypeDefs, FieldDefs or MethodDefs were added, @@ -3321,6 +3325,8 @@ .locals init (int V_0) ImmutableArray.Create( SemanticEdit.Create(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + diff1.VerifyUpdatedTypes("0x02000003", "0x02000004"); + diff1.VerifySynthesizedMembers( "C: {d__0}", "C.d__0: {<>1__state, <>2__current, <>l__initialThreadId, <>4__this, 5__1, System.IDisposable.Dispose, MoveNext, System.Collections.Generic.IEnumerator.get_Current, System.Collections.IEnumerator.Reset, System.Collections.IEnumerator.get_Current, System.Collections.Generic.IEnumerable.GetEnumerator, System.Collections.IEnumerable.GetEnumerator, System.Collections.Generic.IEnumerator.Current, System.Collections.IEnumerator.Current}", @@ -3333,6 +3339,8 @@ .locals init (int V_0) ImmutableArray.Create( SemanticEdit.Create(SemanticEditKind.Update, f1, f2, GetSyntaxMapFromMarkers(source1, source2), preserveLocalVariables: true))); + diff2.VerifyUpdatedTypes("0x02000003", "0x02000004"); + diff2.VerifySynthesizedMembers( "C: {d__0}", "C.d__0: {<>1__state, <>2__current, <>l__initialThreadId, <>4__this, 5__1, System.IDisposable.Dispose, MoveNext, System.Collections.Generic.IEnumerator.get_Current, System.Collections.IEnumerator.Reset, System.Collections.IEnumerator.get_Current, System.Collections.Generic.IEnumerable.GetEnumerator, System.Collections.IEnumerable.GetEnumerator, System.Collections.Generic.IEnumerator.Current, System.Collections.IEnumerator.Current}", diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs index baa930c9177cf..c0bb0bccd09a7 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs @@ -275,9 +275,9 @@ internal static void SaveImages(string outputDirectory, CompilationVerifier base public static class EditAndContinueTestExtensions { - internal static CSharpCompilation WithSource(this CSharpCompilation compilation, string newSource) + internal static CSharpCompilation WithSource(this CSharpCompilation compilation, CSharpTestSource newSource) { - return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(CSharpTestBase.Parse(newSource)); + return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSource.GetSyntaxTrees(TestOptions.Regular)); } internal static CSharpCompilation WithSource(this CSharpCompilation compilation, SyntaxTree newTree) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 015034f5ef769..0652a0d96bc13 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -324,7 +324,6 @@ static void Main() { } var method1 = compilation1.GetMember("C.F"); - var diff1 = compilation1.EmitDifference( generation0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1))); @@ -864,6 +863,10 @@ public void AddField() var reader1 = md1.Reader; var readers = new[] { reader0, reader1 }; + diff1.VerifyUpdatedTypes("0x02000002"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C"); + CheckNames(readers, reader1.GetTypeDefNames()); CheckNames(readers, reader1.GetFieldDefNames(), "G"); CheckNames(readers, reader1.GetMethodDefNames(), ".ctor"); @@ -1616,6 +1619,10 @@ static object F() var reader1 = md1.Reader; var readers = new[] { reader0, reader1 }; + diff1.VerifyUpdatedTypes("0x02000002", "0x02000003"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "A", "B`1"); + CheckNames(readers, reader1.GetTypeDefNames(), "C`1"); Assert.Equal(1, reader1.GetTableRowCount(TableIndex.NestedClass)); @@ -2047,6 +2054,10 @@ interface J { } var reader1 = md1.Reader; var readers = new[] { reader0, reader1 }; + diff1.VerifyUpdatedTypes("0x02000002"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "I"); + CheckNames(readers, reader1.GetTypeDefNames(), "J"); CheckNames(readers, reader1.GetFieldDefNames(), "X", "Y"); CheckNames(readers, reader1.GetMethodDefNames(), "add_Y", "remove_Y", "M", "N", "get_P", "set_P", "get_Q", "set_Q", "add_E", "remove_E", "add_F", "remove_F", ".cctor"); @@ -2072,6 +2083,10 @@ interface J { } var reader2 = md2.Reader; readers = new[] { reader0, reader1, reader2 }; + diff2.VerifyUpdatedTypes("0x02000002"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "I"); + CheckNames(readers, reader2.GetTypeDefNames()); CheckNames(readers, reader2.GetFieldDefNames(), "X"); CheckNames(readers, reader2.GetMethodDefNames(), "M", "N", "get_P", "set_P", "get_Q", "set_Q", "add_E", "remove_E", "add_F", "remove_F", ".cctor"); @@ -2208,6 +2223,10 @@ [B] static void M2<[A]T>() { } var reader1 = md1.Reader; var readers = new[] { reader0, reader1 }; + diff1.VerifyUpdatedTypes("0x02000004"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C"); + CheckNames(readers, reader1.GetTypeDefNames()); CheckNames(readers, reader1.GetMethodDefNames(), "M2", "get_P2", "add_E2", "remove_E2"); @@ -2353,6 +2372,11 @@ static void M() // Verify delta metadata contains expected rows. using var md1 = diff1.GetMetadata(); var readers = new[] { reader0, md1.Reader }; + + diff1.VerifyUpdatedTypes("0x02000002"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C"); + CheckNames(readers, md1.Reader.GetTypeDefNames()); CheckNames(readers, md1.Reader.GetMethodDefNames(), "M"); CheckEncLog(md1.Reader, @@ -2423,6 +2447,11 @@ void M() using var md1 = diff1.GetMetadata(); var reader1 = md1.Reader; var readers = new[] { reader0, reader1 }; + + diff1.VerifyUpdatedTypes("0x02000003"); + + CheckNames(readers, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C"); + CheckNames(readers, reader1.GetTypeDefNames()); CheckNames(readers, reader1.GetEventDefNames()); CheckNames(readers, reader1.GetFieldDefNames()); @@ -2454,6 +2483,8 @@ static void M() var testData0 = new CompilationTestData(); var bytes0 = compilation0.EmitToArray(testData: testData0); + using var md0 = ModuleMetadata.CreateFromImage(bytes0); + var reader0 = md0.MetadataReader; var generation0 = EmitBaseline.CreateInitialBaseline( ModuleMetadata.CreateFromImage(bytes0), @@ -2466,6 +2497,10 @@ static void M() var md1 = diff1.GetMetadata(); var reader1 = md1.Reader; + diff1.VerifyUpdatedTypes("0x02000002"); + + CheckNames(reader0, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C"); + CheckEncLog(reader1, Row(2, TableIndex.AssemblyRef, EditAndContinueOperation.Default), Row(12, TableIndex.TypeRef, EditAndContinueOperation.Default), @@ -2555,11 +2590,17 @@ class C var compilation1 = compilation0.WithSource(source1); var bytes0 = compilation0.EmitToArray(); var generation0 = EmitBaseline.CreateInitialBaseline(ModuleMetadata.CreateFromImage(bytes0), EmptyLocalsProvider); + using var md0 = ModuleMetadata.CreateFromImage(bytes0); var diff1 = compilation1.EmitDifference( generation0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Insert, null, compilation1.GetMember("C.puts")))); + diff1.VerifyUpdatedTypes("0x02000002"); + + var reader0 = md0.MetadataReader; + CheckNames(reader0, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C"); + using var md1 = diff1.GetMetadata(); var reader1 = md1.Reader; CheckEncLog(reader1, @@ -5965,7 +6006,6 @@ .locals init (<>f__AnonymousType0 V_0) //x diff1.VerifySynthesizedMembers( "<>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}"); - var baselineIL = @" { // Code size 24 (0x18) @@ -6180,7 +6220,7 @@ .locals init ([unchanged] V_0, IL_0021: br.s IL_0023 -IL_0023: ldloc.s V_6 IL_0025: ret -}", methodToken: diff1.UpdatedMethods.Single()); +}", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } /// @@ -7383,7 +7423,6 @@ static string F() var method1F = compilation1.GetMember("C.F"); using MemoryStream mdStream = new MemoryStream(), ilStream = new MemoryStream(), pdbStream = new MemoryStream(); - var updatedMethods = new List(); var isAddedSymbol = new Func(s => false); var badStream = new BrokenStream(); @@ -7396,7 +7435,6 @@ static string F() badStream, ilStream, pdbStream, - updatedMethods, new CompilationTestData(), default); Assert.False(result.Success); @@ -7412,7 +7450,6 @@ static string F() mdStream, badStream, pdbStream, - updatedMethods, new CompilationTestData(), default); Assert.False(result.Success); @@ -7428,7 +7465,6 @@ static string F() mdStream, ilStream, badStream, - updatedMethods, new CompilationTestData(), default); Assert.False(result.Success); @@ -7472,7 +7508,6 @@ static string F() var method1F = compilation1.GetMember("C.F"); using MemoryStream mdStream = new MemoryStream(), ilStream = new MemoryStream(), pdbStream = new MemoryStream(); - var updatedMethods = new List(); var isAddedSymbol = new Func(s => false); var badStream = new BrokenStream(); @@ -7485,7 +7520,6 @@ static string F() mdStream, ilStream, badStream, - updatedMethods, new CompilationTestData(), default); Assert.False(result.Success); @@ -7570,7 +7604,6 @@ static void F() generation0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0F, method1F, syntaxMap: s => null, preserveLocalVariables: true))); - var handle = MetadataTokens.BlobHandle(1); byte[] value0 = reader0.GetBlobBytes(handle); Assert.Equal("20-01-01-08", BitConverter.ToString(value0)); @@ -9383,6 +9416,11 @@ .maxstack 2 ImmutableArray.Create( SemanticEdit.Create(SemanticEditKind.Update, ctor0, ctor1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + diff1.VerifyUpdatedTypes("0x02000002", "0x02000004"); + + var reader0 = md0.MetadataReader; + CheckNames(reader0, reader0.GetUpdatedTypeDefNames(diff1.EmitResult), "C", "<>c__DisplayClass0_0"); + diff1.VerifySynthesizedMembers( "C: {<>c__DisplayClass0_0}", "C.<>c__DisplayClass0_0: {x, <.ctor>b__0}"); @@ -9426,6 +9464,10 @@ .maxstack 2 ImmutableArray.Create( SemanticEdit.Create(SemanticEditKind.Update, ctor1, ctor2, GetSyntaxMapFromMarkers(source1, source0), preserveLocalVariables: true))); + diff2.VerifyUpdatedTypes("0x02000002", "0x02000004"); + + CheckNames(reader0, reader0.GetUpdatedTypeDefNames(diff2.EmitResult), "C", "<>c__DisplayClass0_0"); + diff2.VerifySynthesizedMembers( "C: {<>c__DisplayClass0_0}", "C.<>c__DisplayClass0_0: {x, <.ctor>b__0}"); @@ -10578,5 +10620,160 @@ .maxstack 1 } "); } + + [Fact] + public void Records_AddWellKnownMember() + { + var source0 = +@" +#nullable enable +namespace N +{ + record R(int X) + { + } +} +"; + var source1 = +@" +#nullable enable +namespace N +{ + record R(int X) + { + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } + } +} +"; + + var compilation0 = CreateCompilation(new[] { source0, IsExternalInitTypeDefinition }, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1, IsExternalInitTypeDefinition }); + + var printMembers0 = compilation0.GetMember("N.R.PrintMembers"); + var printMembers1 = compilation1.GetMember("N.R.PrintMembers"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + // Verify full metadata contains expected rows. + var reader0 = md0.MetadataReader; + + CheckNames(reader0, reader0.GetTypeDefNames(), "", "EmbeddedAttribute", "NullableAttribute", "NullableContextAttribute", "IsExternalInit", "R"); + CheckNames(reader0, reader0.GetMethodDefNames(), + /* EmbeddedAttribute */".ctor", + /* NullableAttribute */ ".ctor", + /* NullableContextAttribute */".ctor", + /* IsExternalInit */".ctor", + /* R: */ + ".ctor", + "get_EqualityContract", + "get_X", + "set_X", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "$", + ".ctor", + "Deconstruct"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, printMembers0, printMembers1))); + + diff1.VerifySynthesizedMembers( + ": {Microsoft}", + "Microsoft: {CodeAnalysis}", + "Microsoft.CodeAnalysis: {EmbeddedAttribute}", + "System.Runtime.CompilerServices: {NullableAttribute, NullableContextAttribute}"); + + // Verify delta metadata contains expected rows. + using var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + var readers = new[] { reader0, reader1 }; + + EncValidation.VerifyModuleMvid(1, reader0, reader1); + + CheckNames(readers, reader1.GetTypeDefNames()); + CheckNames(readers, reader1.GetMethodDefNames(), "PrintMembers"); + + CheckEncLog(reader1, + Row(2, TableIndex.AssemblyRef, EditAndContinueOperation.Default), + Row(19, TableIndex.TypeRef, EditAndContinueOperation.Default), + Row(20, TableIndex.TypeRef, EditAndContinueOperation.Default), + Row(21, TableIndex.TypeRef, EditAndContinueOperation.Default), + Row(4, TableIndex.TypeSpec, EditAndContinueOperation.Default), + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(10, TableIndex.MethodDef, EditAndContinueOperation.Default)); // R.PrintMembers + + CheckEncMap(reader1, + Handle(19, TableIndex.TypeRef), + Handle(20, TableIndex.TypeRef), + Handle(21, TableIndex.TypeRef), + Handle(10, TableIndex.MethodDef), + Handle(3, TableIndex.StandAloneSig), + Handle(4, TableIndex.TypeSpec), + Handle(2, TableIndex.AssemblyRef)); + } + + [Fact] + public void Records_RemoveWellKnownMember() + { + var source0 = +@" +namespace N +{ + record R(int X) + { + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } + } +} +"; + var source1 = +@" +namespace N +{ + record R(int X) + { + } +} +"; + + var compilation0 = CreateCompilation(new[] { source0, IsExternalInitTypeDefinition }, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1, IsExternalInitTypeDefinition }); + + var method0 = compilation0.GetMember("N.R.PrintMembers"); + var method1 = compilation1.GetMember("N.R.PrintMembers"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, method0, method1))); + + diff1.VerifySynthesizedMembers( + ": {Microsoft}", + "Microsoft: {CodeAnalysis}", + "Microsoft.CodeAnalysis: {EmbeddedAttribute}", + "System.Runtime.CompilerServices: {NullableAttribute, NullableContextAttribute}"); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs index 257498198dd53..21b020ad0cdb1 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs @@ -594,7 +594,7 @@ .locals init (object V_0, } -IL_0041: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } /// @@ -1296,7 +1296,7 @@ .locals init (double[,,] V_0, IL_006c: ble.s IL_0027 -IL_006e: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -1765,7 +1765,7 @@ .locals init (string[] V_0, //arr IL_002b: conv.i4 IL_002c: blt.s IL_0017 -IL_002e: ret -}", methodToken: diff1.UpdatedMethods.Single()); +}", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -2035,7 +2035,7 @@ .locals init (object V_0, IL_0055: callvirt ""int string.Length.get"" IL_005a: blt.s IL_0044 -IL_005c: ret -}", methodToken: diff2.UpdatedMethods.Single()); +}", methodToken: diff2.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -2426,7 +2426,7 @@ .locals init (string V_0, IL_0036: nop -IL_0037: br.s IL_0039 -IL_0039: ret -}", methodToken: diff1.UpdatedMethods.Single()); +}", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -3270,7 +3270,7 @@ .locals init (object V_0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetEquivalentNodesMap(method1, method0), preserveLocalVariables: true))); diff1.VerifyIL("C.b__0", @" -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); #endif } @@ -3529,7 +3529,7 @@ .locals init (int V_0) ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetEquivalentNodesMap(method1, method0), preserveLocalVariables: true))); diff1.VerifyIL("?", @" -{", methodToken: diff1.UpdatedMethods.Single()); +{", methodToken: diff1.EmitResult.UpdatedMethods.Single()); #endif } @@ -3689,7 +3689,7 @@ .locals init (int V_0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetEquivalentNodesMap(method1, method0), preserveLocalVariables: true))); diff1.VerifyIL("?", @" -{", methodToken: diff1.UpdatedMethods.Single()); +{", methodToken: diff1.EmitResult.UpdatedMethods.Single()); #endif } @@ -3738,7 +3738,7 @@ .locals init (int V_0, //x -IL_0011: ldloc.3 IL_0012: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -3794,7 +3794,7 @@ .locals init (int V_0, //i IL_001f: br.s IL_0021 -IL_0021: ldloc.3 IL_0022: ret -}", methodToken: diff1.UpdatedMethods.Single()); +}", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -3849,7 +3849,7 @@ .locals init (System.ValueTuple> V_0, //x -IL_0031: ldloc.2 IL_0032: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -3901,7 +3901,7 @@ .locals init (int V_0, //x -IL_0010: ldloc.s V_4 IL_0012: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -3957,7 +3957,7 @@ .locals init (int V_0, //i IL_001f: br.s IL_0021 -IL_0021: ldloc.3 IL_0022: ret -}", methodToken: diff1.UpdatedMethods.Single()); +}", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4017,7 +4017,7 @@ .locals init (bool V_0, IL_002b: br.s IL_002d -IL_002d: ldloc.2 IL_002e: ret -}", methodToken: diff1.UpdatedMethods.Single()); +}", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4095,7 +4095,7 @@ .locals init (int V_0, //a -IL_003b: ldloc.s V_5 IL_003d: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4186,7 +4186,7 @@ .locals init (int V_0, //i -IL_0049: ldloc.s V_6 IL_004b: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4251,7 +4251,7 @@ .locals init (C.d__0 V_0) IL_0032: call ""System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get"" IL_0037: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4311,7 +4311,7 @@ .locals init (C.d__0 V_0) IL_0032: call ""System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get"" IL_0037: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4401,7 +4401,7 @@ .locals init (int V_0, //x -IL_0046: ldloc.s V_8 IL_0048: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] @@ -4485,7 +4485,7 @@ .locals init ([unchanged] V_0, IL_004b: blt.s IL_000e -IL_004d: ret } -", methodToken: diff1.UpdatedMethods.Single()); +", methodToken: diff1.EmitResult.UpdatedMethods.Single()); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs index 3d633944d05e1..e3238e95c4e61 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs @@ -1404,5 +1404,153 @@ static void verify(string source1, string source2) Assert.Null(matcher.MapDefinition(f_1.GetCciAdapter())); } } + + [Fact] + public void Record_ImplementSynthesizedMember_ToString() + { + var source0 = @" +public record R +{ +}"; + var source1 = @" +public record R +{ + public override string ToString() => ""R""; +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member = compilation1.GetMember("R.ToString"); + var other = matcher.MapDefinition(member.GetCciAdapter()); + Assert.NotNull(other); + } + + [Fact] + public void Record_ImplementSynthesizedMember_PrintMembers() + { + var source0 = @" +public record R +{ +}"; + var source1 = @" +public record R +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true; +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member0 = compilation0.GetMember("R.PrintMembers"); + var member1 = compilation1.GetMember("R.PrintMembers"); + + Assert.Equal(member0, (MethodSymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol()); + } + + [Fact] + public void Record_RemoveSynthesizedMember_PrintMembers() + { + var source0 = @" +public record R +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true; +}"; + var source1 = @" +public record R +{ +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member0 = compilation0.GetMember("R.PrintMembers"); + var member1 = compilation1.GetMember("R.PrintMembers"); + + Assert.Equal(member0, (MethodSymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol()); + } + + [Fact] + public void Record_ImplementSynthesizedMember_Property() + { + var source0 = @" +public record R(int X);"; + var source1 = @" +public record R(int X) +{ + public int X { get; init; } = this.X; +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member0 = compilation0.GetMember("R.X"); + var member1 = compilation1.GetMember("R.X"); + + Assert.Equal(member0, (PropertySymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol()); + } + + [Fact] + public void Record_ImplementSynthesizedMember_Constructor() + { + var source0 = @" +public record R(int X);"; + var source1 = @" +public record R +{ + public R(int X) + { + this.X = X; + } + + public int X { get; init; } +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var members = compilation1.GetMembers("R..ctor"); + // There are two, one is the copy constructor + Assert.Equal(2, members.Length); + + var member = (SourceConstructorSymbol)members.Single(m => m.ToString() == "R.R(int)"); + var other = matcher.MapDefinition(member.GetCciAdapter()); + Assert.NotNull(other); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs index 8f04b70734a6e..6ba84970145bc 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs @@ -149,33 +149,31 @@ void M2() { } } - [Fact] + [ConditionalFact(typeof(WindowsOrLinuxOnly))] [WorkItem(33909, "https://github.com/dotnet/roslyn/issues/33909")] [WorkItem(34880, "https://github.com/dotnet/roslyn/issues/34880")] + [WorkItem(53361, "https://github.com/dotnet/roslyn/issues/53361")] public void DeeplyNestedGeneric() { int nestingLevel = (ExecutionConditionUtil.Architecture, ExecutionConditionUtil.Configuration) switch { // Legacy baselines are indicated by comments - (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) when ExecutionConditionUtil.IsMacOS => 180, // 100 - (ExecutionArchitecture.x64, ExecutionConfiguration.Release) when ExecutionConditionUtil.IsMacOS => 520, // 100 - _ when ExecutionConditionUtil.IsCoreClrUnix => 1200, // 1200 - _ when ExecutionConditionUtil.IsMonoDesktop => 730, // 730 - (ExecutionArchitecture.x86, ExecutionConfiguration.Debug) => 450, // 270 + (ExecutionArchitecture.x86, ExecutionConfiguration.Debug) => 370, // 270 (ExecutionArchitecture.x86, ExecutionConfiguration.Release) => 1290, // 1290 - (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) => 250, // 170 + (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) => 270, // 170 (ExecutionArchitecture.x64, ExecutionConfiguration.Release) => 730, // 730 _ => throw new Exception($"Unexpected configuration {ExecutionConditionUtil.Architecture} {ExecutionConditionUtil.Configuration}") }; // Un-comment loop below and use above commands to figure out the new limits - // for (int i = nestingLevel; i < int.MaxValue; i = i + 10) - // { - // var start = DateTime.UtcNow; - // Console.Write($"Depth: {i}"); - // runDeeplyNestedGenericTest(i); - // Console.WriteLine($" - {DateTime.UtcNow - start}"); - // } + //Console.WriteLine($"Using architecture: {ExecutionConditionUtil.Architecture}, configuration: {ExecutionConditionUtil.Configuration}"); + //for (int i = nestingLevel; i < int.MaxValue; i = i + 10) + //{ + // var start = DateTime.UtcNow; + // Console.Write($"Depth: {i}"); + // runDeeplyNestedGenericTest(i); + // Console.WriteLine($" - {DateTime.UtcNow - start}"); + //} runDeeplyNestedGenericTest(nestingLevel); diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs index 32e09832f4b52..057f155a7ec51 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs @@ -1207,6 +1207,447 @@ void F() "); } + [Fact] + public void SwitchExpressionWithLambda1() + { + string source = WithWindowsLineBreaks(@" +using System; + +class C +{ + static string M(object o) + { + return o switch + { + int i => new Func(() => $""Number: {i} + {o} == {i + (int)o}"")(), + _ => ""Don't know"" + }; + } +} +"); + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + "); + } + + [Fact] + public void SwitchExpressionWithLambda2() + { + string source = WithWindowsLineBreaks(@" +using System; + +class C +{ + static string M(object o) + { + return o switch + { + int i when new Func(() => $""Number: {i} + {o} == {i + (int)o}"")() != null => $""Definitely a number: {i}"", + int i => new Func(() => $""Number: {i} + {o} == {i + (int)o}"")(), + _ => ""Don't know"" + }; + } +} +"); + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + "); + } + + [Fact] + public void SwitchExpressionWithLambda3() + { + string source = WithWindowsLineBreaks(@" +using System; + +class C +{ + static string M(object o) + { + return new Func(() => o.ToString())() switch + { + ""goo"" => o.ToString(), + _ => ""Don't know"" + }; + } +} +"); + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + +"); + } + + [Fact] + public void SwitchExpressionWithLambda4() + { + string source = WithWindowsLineBreaks(@" +using System; + +class C +{ + static string M(object o) + { + return o switch + { + string s => new Func(() => string.Concat(""s"", s))(), + int i => new Func(() => i.ToString() + i switch + { + 1 => new Func(() => string.Concat(""One"", i))(), + _ => ""Don't know"" + })(), + _ => ""Don't know"" + }; + } +} +"); + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + "); + } + + [Fact] + public void SwitchExpressionWithLambda5() + { + string source = WithWindowsLineBreaks(@" +using System; + +class C +{ + static string M(object o) + { + return o switch + { + string s => new Func(() => string.Concat(o, s))(), + int i => new Func(() => i.ToString() + i switch + { + 1 => new Func(() => string.Concat(""One"", i, o))(), + _ => ""Don't know"" + })(), + _ => ""Don't know"" + }; + } +} +"); + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + "); + } + + [Fact] + public void SwitchExpressionWithLambda6() + { + string source = WithWindowsLineBreaks(@" +using System; + +class C +{ + static string M(object o) + { + return o switch + { + string s => new Func(() => string.Concat(""s"", s))(), + int i => new Func(() => i.ToString() + new Func(() => i + 1)())(), + _ => ""Don't know"" + }; + } +} +"); + + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + "); + } + [Fact] public void UsingStatement1() { @@ -1381,5 +1822,283 @@ .locals init (bool V_0, //result } ", sequencePoints: "C+<>c.b__0_0"); } + + [Fact] + public void WithExpression() + { + var source = MarkedSource(WithWindowsLineBreaks(@" +using System; +record R(int X); + +class Test +{ + public static void M(int a) + { + var x = new R(1); + var y = x with + { + X = new Func(() => a)() + }; + } +} +"), removeTags: true); // We're validating offsets so need to remove tags entirely + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source.Tree, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdbLambdasAndClosures(source); + } + + [Fact] + public void WithExpression_2() + { + var source = MarkedSource(WithWindowsLineBreaks(@" +using System; +record R(int X, int Y); + +class Test +{ + public static void M(int a) + { + var x = new R(1, 2); + var b = 1; + var y = x with + { + X = new Func(() => a)(), + Y = new Func(() => b)() + }; + } +} +"), removeTags: true); // We're validating offsets so need to remove tags entirely + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source.Tree, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdbLambdasAndClosures(source); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/52068")] + public void WithExpression_3() + { + var source = MarkedSource(WithWindowsLineBreaks(@" +using System; +record R(int X, int Y); +record Z(int A, R R); + +class Test +{ + public static void M(int a) + { + var r = new R(1, 2); + var x = new Z(1, new R(2, 3)); + var b = 1; + var y = x with + { + A = new Func(() => a)(), + R = r with + { + X = 4, + Y = new Func(() => b)() + } + }; + } +} +"), removeTags: true); // We're validating offsets so need to remove tags entirely + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source.Tree, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdbLambdasAndClosures(source); + } + + [Fact] + public void Record_1() + { + var source = WithWindowsLineBreaks(@" +using System; +record D(int X) +{ + public int Y { get; set; } = new Func(a => a + X).Invoke(1); +} +"); + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdb(@" + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", format: CodeAnalysis.Emit.DebugInformationFormat.Pdb); + } + + [Fact] + public void Record_2() + { + var source = WithWindowsLineBreaks(@" +using System; +record C(int X) +{ + public C(int x, Func f) + : this(x) + { + } +} + +record D(int X) : C(F(X, out int z), () => z) +{ + static int F(int x, out int p) + { + p = 1; + return x + 1; + } +} +"); + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + compilation.VerifyDiagnostics(); + + compilation.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", format: CodeAnalysis.Emit.DebugInformationFormat.Pdb); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBSourceLinkTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBSourceLinkTests.cs index f81896fe75e3c..d632e2a66b9bb 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBSourceLinkTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBSourceLinkTests.cs @@ -149,13 +149,12 @@ public static void Main() xmlDocumentationStream: null, cancellationToken: default, win32Resources: null, - useRawWin32Resources: false, manifestResources: null, options: EmitOptions.Default.WithDebugInformationFormat(DebugInformationFormat.Pdb), debugEntryPoint: null, sourceLinkStream: new MemoryStream(new byte[] { 1, 2, 3 }), embeddedTexts: null, - pdbOptionsBlobReader: null, + rebuildData: null, testData: new CompilationTestData() { SymWriterFactory = metadataProvider => new SymUnmanagedWriterWithoutSourceLinkSupport(metadataProvider) diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index 4626deb25162b..43bf8dbdcf199 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -183,13 +183,12 @@ public void SymWriterErrors() xmlDocumentationStream: null, cancellationToken: default, win32Resources: null, - useRawWin32Resources: false, manifestResources: null, options: null, debugEntryPoint: null, sourceLinkStream: null, embeddedTexts: null, - pdbOptionsBlobReader: null, + rebuildData: null, testData: new CompilationTestData() { SymWriterFactory = _ => new MockSymUnmanagedWriter() }); result.Diagnostics.Verify( @@ -216,13 +215,12 @@ public void SymWriterErrors2() xmlDocumentationStream: null, cancellationToken: default, win32Resources: null, - useRawWin32Resources: false, manifestResources: null, options: null, debugEntryPoint: null, sourceLinkStream: null, embeddedTexts: null, - pdbOptionsBlobReader: null, + rebuildData: null, testData: new CompilationTestData() { SymWriterFactory = SymWriterTestUtilities.ThrowingFactory }); result.Diagnostics.Verify( @@ -249,13 +247,12 @@ public void SymWriterErrors3() xmlDocumentationStream: null, cancellationToken: default, win32Resources: null, - useRawWin32Resources: false, manifestResources: null, options: null, debugEntryPoint: null, sourceLinkStream: null, embeddedTexts: null, - pdbOptionsBlobReader: null, + rebuildData: null, testData: new CompilationTestData() { SymWriterFactory = SymWriterTestUtilities.ThrowingFactory }); result.Diagnostics.Verify( @@ -282,13 +279,12 @@ public void SymWriterErrors4() xmlDocumentationStream: null, cancellationToken: default, win32Resources: null, - useRawWin32Resources: false, manifestResources: null, options: null, debugEntryPoint: null, sourceLinkStream: null, embeddedTexts: null, - pdbOptionsBlobReader: null, + rebuildData: null, testData: new CompilationTestData() { SymWriterFactory = _ => throw new DllNotFoundException("xxx") }); result.Diagnostics.Verify( @@ -4284,6 +4280,64 @@ .locals init (int V_0, ", sequencePoints: "C+
d__0.MoveNext", source: source); } + [Fact] + public void SwitchExpressionWithPattern() + { + string source = WithWindowsLineBreaks(@" +class C +{ + static string M(object o) + { + return o switch + { + int i => $""Number: {i}"", + _ => ""Don't know"" + }; + } +} +"); + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.M", +@" + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + #endregion #region DoStatement @@ -9625,9 +9679,9 @@ .locals init (C.<>c__DisplayClass0_0 V_0, //CS$<>8__locals0 - + - + @@ -9641,7 +9695,7 @@ .locals init (C.<>c__DisplayClass0_0 V_0, //CS$<>8__locals0 - + @@ -9649,8 +9703,8 @@ .locals init (C.<>c__DisplayClass0_0 V_0, //CS$<>8__locals0 0 - - + + @@ -9731,6 +9785,453 @@ .locals init (C.<>c__DisplayClass0_0 V_0, //CS$<>8__locals0 "); } + [WorkItem(50321, "https://github.com/dotnet/roslyn/issues/50321")] + [ConditionalFact(typeof(CoreClrOnly))] + public void NestedSwitchExpressions_Closures_01() + { + string source = WithWindowsLineBreaks( +@"using System; +class C +{ + static int F(object o) + { + return o switch + { + int i => new Func(() => i + i switch + { + 1 => 2, + _ => 3 + })(), + _ => 4 + }; + } +}"); + var verifier = CompileAndVerify(source, options: TestOptions.DebugDll); + verifier.VerifyTypeIL("C", +@".class private auto ansi beforefieldinit C + extends [netstandard]System.Object +{ + // Nested Types + .class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0' + extends [netstandard]System.Object + { + .custom instance void [netstandard]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field public int32 '5__2' + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20a1 + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [netstandard]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method '<>c__DisplayClass0_0'::.ctor + .method assembly hidebysig + instance int32 'b__0' () cil managed + { + // Method begins at RVA 0x20ac + // Code size 38 (0x26) + .maxstack 2 + .locals init ( + [0] int32, + [1] int32 + ) + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'<>c__DisplayClass0_0'::'5__2' + IL_0006: stloc.0 + IL_0007: ldc.i4.1 + IL_0008: brtrue.s IL_000b + IL_000a: nop + IL_000b: ldarg.0 + IL_000c: ldfld int32 C/'<>c__DisplayClass0_0'::'5__2' + IL_0011: ldc.i4.1 + IL_0012: beq.s IL_0016 + IL_0014: br.s IL_001a + IL_0016: ldc.i4.2 + IL_0017: stloc.1 + IL_0018: br.s IL_001e + IL_001a: ldc.i4.3 + IL_001b: stloc.1 + IL_001c: br.s IL_001e + IL_001e: ldc.i4.1 + IL_001f: brtrue.s IL_0022 + IL_0021: nop + IL_0022: ldloc.0 + IL_0023: ldloc.1 + IL_0024: add + IL_0025: ret + } // end of method '<>c__DisplayClass0_0'::'b__0' + } // end of class <>c__DisplayClass0_0 + // Methods + .method private hidebysig static + int32 F ( + object o + ) cil managed + { + // Method begins at RVA 0x2050 + // Code size 69 (0x45) + .maxstack 2 + .locals init ( + [0] class C/'<>c__DisplayClass0_0', + [1] int32, + [2] int32 + ) + IL_0000: nop + IL_0001: newobj instance void C/'<>c__DisplayClass0_0'::.ctor() + IL_0006: stloc.0 + IL_0007: ldc.i4.1 + IL_0008: brtrue.s IL_000b + IL_000a: nop + IL_000b: ldarg.0 + IL_000c: isinst [netstandard]System.Int32 + IL_0011: brfalse.s IL_0037 + IL_0013: ldloc.0 + IL_0014: ldarg.0 + IL_0015: unbox.any [netstandard]System.Int32 + IL_001a: stfld int32 C/'<>c__DisplayClass0_0'::'5__2' + IL_001f: br.s IL_0021 + IL_0021: br.s IL_0023 + IL_0023: ldloc.0 + IL_0024: ldftn instance int32 C/'<>c__DisplayClass0_0'::'b__0'() + IL_002a: newobj instance void class [netstandard]System.Func`1::.ctor(object, native int) + IL_002f: callvirt instance !0 class [netstandard]System.Func`1::Invoke() + IL_0034: stloc.1 + IL_0035: br.s IL_003b + IL_0037: ldc.i4.4 + IL_0038: stloc.1 + IL_0039: br.s IL_003b + IL_003b: ldc.i4.1 + IL_003c: brtrue.s IL_003f + IL_003e: nop + IL_003f: ldloc.1 + IL_0040: stloc.2 + IL_0041: br.s IL_0043 + IL_0043: ldloc.2 + IL_0044: ret + } // end of method C::F + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20a1 + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [netstandard]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method C::.ctor +} // end of class C +"); + verifier.VerifyPdb( +@" + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(50321, "https://github.com/dotnet/roslyn/issues/50321")] + [ConditionalFact(typeof(CoreClrOnly))] + public void NestedSwitchExpressions_Closures_02() + { + string source = WithWindowsLineBreaks( +@"using System; +class C +{ + static string F(object o) + { + return o switch + { + int i => new Func(() => ""1"" + i switch + { + 1 => new Func(() => ""2"" + i)(), + _ => ""3"" + })(), + _ => ""4"" + }; + } +}"); + var verifier = CompileAndVerify(source, options: TestOptions.DebugDll); + verifier.VerifyTypeIL("C", +@".class private auto ansi beforefieldinit C + extends [netstandard]System.Object +{ + // Nested Types + .class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0' + extends [netstandard]System.Object + { + .custom instance void [netstandard]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field public int32 '5__2' + .field public class [netstandard]System.Func`1 '<>9__1' + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20a5 + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [netstandard]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method '<>c__DisplayClass0_0'::.ctor + .method assembly hidebysig + instance string 'b__0' () cil managed + { + // Method begins at RVA 0x20b0 + // Code size 78 (0x4e) + .maxstack 3 + .locals init ( + [0] string, + [1] class [netstandard]System.Func`1 + ) + IL_0000: ldc.i4.1 + IL_0001: brtrue.s IL_0004 + IL_0003: nop + IL_0004: ldarg.0 + IL_0005: ldfld int32 C/'<>c__DisplayClass0_0'::'5__2' + IL_000a: ldc.i4.1 + IL_000b: beq.s IL_000f + IL_000d: br.s IL_0036 + IL_000f: ldarg.0 + IL_0010: ldfld class [netstandard]System.Func`1 C/'<>c__DisplayClass0_0'::'<>9__1' + IL_0015: dup + IL_0016: brtrue.s IL_002e + IL_0018: pop + IL_0019: ldarg.0 + IL_001a: ldarg.0 + IL_001b: ldftn instance string C/'<>c__DisplayClass0_0'::'b__1'() + IL_0021: newobj instance void class [netstandard]System.Func`1::.ctor(object, native int) + IL_0026: dup + IL_0027: stloc.1 + IL_0028: stfld class [netstandard]System.Func`1 C/'<>c__DisplayClass0_0'::'<>9__1' + IL_002d: ldloc.1 + IL_002e: callvirt instance !0 class [netstandard]System.Func`1::Invoke() + IL_0033: stloc.0 + IL_0034: br.s IL_003e + IL_0036: ldstr ""3"" + IL_003b: stloc.0 + IL_003c: br.s IL_003e + IL_003e: ldc.i4.1 + IL_003f: brtrue.s IL_0042 + IL_0041: nop + IL_0042: ldstr ""1"" + IL_0047: ldloc.0 + IL_0048: call string [netstandard]System.String::Concat(string, string) + IL_004d: ret + } // end of method '<>c__DisplayClass0_0'::'b__0' + .method assembly hidebysig + instance string 'b__1' () cil managed + { + // Method begins at RVA 0x210a + // Code size 22 (0x16) + .maxstack 8 + IL_0000: ldstr ""2"" + IL_0005: ldarg.0 + IL_0006: ldflda int32 C/'<>c__DisplayClass0_0'::'5__2' + IL_000b: call instance string [netstandard]System.Int32::ToString() + IL_0010: call string [netstandard]System.String::Concat(string, string) + IL_0015: ret + } // end of method '<>c__DisplayClass0_0'::'b__1' + } // end of class <>c__DisplayClass0_0 + // Methods + .method private hidebysig static + string F ( + object o + ) cil managed + { + // Method begins at RVA 0x2050 + // Code size 73 (0x49) + .maxstack 2 + .locals init ( + [0] class C/'<>c__DisplayClass0_0', + [1] string, + [2] string + ) + IL_0000: nop + IL_0001: newobj instance void C/'<>c__DisplayClass0_0'::.ctor() + IL_0006: stloc.0 + IL_0007: ldc.i4.1 + IL_0008: brtrue.s IL_000b + IL_000a: nop + IL_000b: ldarg.0 + IL_000c: isinst [netstandard]System.Int32 + IL_0011: brfalse.s IL_0037 + IL_0013: ldloc.0 + IL_0014: ldarg.0 + IL_0015: unbox.any [netstandard]System.Int32 + IL_001a: stfld int32 C/'<>c__DisplayClass0_0'::'5__2' + IL_001f: br.s IL_0021 + IL_0021: br.s IL_0023 + IL_0023: ldloc.0 + IL_0024: ldftn instance string C/'<>c__DisplayClass0_0'::'b__0'() + IL_002a: newobj instance void class [netstandard]System.Func`1::.ctor(object, native int) + IL_002f: callvirt instance !0 class [netstandard]System.Func`1::Invoke() + IL_0034: stloc.1 + IL_0035: br.s IL_003f + IL_0037: ldstr ""4"" + IL_003c: stloc.1 + IL_003d: br.s IL_003f + IL_003f: ldc.i4.1 + IL_0040: brtrue.s IL_0043 + IL_0042: nop + IL_0043: ldloc.1 + IL_0044: stloc.2 + IL_0045: br.s IL_0047 + IL_0047: ldloc.2 + IL_0048: ret + } // end of method C::F + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20a5 + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [netstandard]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method C::.ctor +} // end of class C +"); + verifier.VerifyPdb( +@" + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + [WorkItem(37261, "https://github.com/dotnet/roslyn/issues/37261")] [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] public void SwitchExpression_MethodBody() @@ -12171,10 +12672,9 @@ public void InvalidCharacterInPdbPath() var compilation = CreateCompilation(""); var result = compilation.Emit(outStream, options: new EmitOptions(pdbFilePath: "test\\?.pdb", debugInformationFormat: DebugInformationFormat.Embedded)); - Assert.False(result.Success); - result.Diagnostics.Verify( - // error CS2021: File name 'test\?.pdb' is empty, contains invalid characters, has a drive specification without an absolute path, or is too long - Diagnostic(ErrorCode.FTL_InvalidInputFileName).WithArguments("test\\?.pdb").WithLocation(1, 1)); + // This is fine because EmitOptions just controls what is written into the PE file and it's + // valid for this to be an illegal file name (path map can easily create these). + Assert.True(result.Success); } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArrayElementReferenceExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArrayElementReferenceExpression.cs index f7d114c37d873..a58d2c62ef14f 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArrayElementReferenceExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArrayElementReferenceExpression.cs @@ -448,7 +448,7 @@ public void F(string[] args, C c) string expectedOperationTree = @" IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.String, IsInvalid) (Syntax: 'args[c]') Array reference: - IParameterReferenceOperation: args (OperationKind.ParameterReference, Type: System.String[], IsInvalid) (Syntax: 'args') + IParameterReferenceOperation: args (OperationKind.ParameterReference, Type: System.String[]) (Syntax: 'args') Indices(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'c') Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -458,7 +458,7 @@ public void F(string[] args, C c) var expectedDiagnostics = new DiagnosticDescription[] { // CS0029: Cannot implicitly convert type 'C' to 'int' // var a = /**/args[c]/**/; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "args[c]").WithArguments("C", "int").WithLocation(6, 27) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c").WithArguments("C", "int").WithLocation(6, 32) }; VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); @@ -485,7 +485,7 @@ public static explicit operator int(C c) string expectedOperationTree = @" IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.String, IsInvalid) (Syntax: 'args[c]') Array reference: - IParameterReferenceOperation: args (OperationKind.ParameterReference, Type: System.String[], IsInvalid) (Syntax: 'args') + IParameterReferenceOperation: args (OperationKind.ParameterReference, Type: System.String[]) (Syntax: 'args') Indices(1): IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Int32 C.op_Explicit(C c)) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'c') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Int32 C.op_Explicit(C c)) @@ -495,7 +495,7 @@ public static explicit operator int(C c) var expectedDiagnostics = new DiagnosticDescription[] { // CS0266: Cannot implicitly convert type 'C' to 'int'. An explicit conversion exists (are you missing a cast?) // var a = /**/args[c]/**/; - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "args[c]").WithArguments("C", "int").WithLocation(6, 27) + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c").WithArguments("C", "int").WithLocation(6, 32) }; VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs index e2ff1f73b9fe7..3d165da312198 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs @@ -14,6 +14,69 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public partial class IOperationTests_SwitchExpression : SemanticModelTestBase { + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Patterns)] + [Fact] + public void SwitchExpression_NonExhaustive() + { + //null case is not handled -> not exhaustive + string source = @" +namespace Tests +{ + public class Prog + { + static int EvalPoint((int, int)? point) => /**/point switch + { + (0, int t) => t > 2 ? 3 : 4, + var (_, _) => 2 + }/**/; + + static void Main() + { + EvalPoint(null); + } + } +} +"; + string expectedOperationTree = @"ISwitchExpressionOperation (2 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'point switc ... }') + Value: + IParameterReferenceOperation: point (OperationKind.ParameterReference, Type: (System.Int32, System.Int32)?) (Syntax: 'point') + Arms(2): + ISwitchExpressionArmOperation (1 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '(0, int t) ... > 2 ? 3 : 4') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '(0, int t)') (InputType: (System.Int32, System.Int32)?, NarrowedType: (System.Int32, System.Int32), DeclaredSymbol: null, MatchedType: (System.Int32, System.Int32), DeconstructSymbol: null) + DeconstructionSubpatterns (2): + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '0') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'int t') (InputType: System.Int32, NarrowedType: System.Int32, DeclaredSymbol: System.Int32 t, MatchesNull: False) + PropertySubpatterns (0) + Value: + IConditionalOperation (OperationKind.Conditional, Type: System.Int32) (Syntax: 't > 2 ? 3 : 4') + Condition: + IBinaryOperation (BinaryOperatorKind.GreaterThan) (OperationKind.Binary, Type: System.Boolean) (Syntax: 't > 2') + Left: + ILocalReferenceOperation: t (OperationKind.LocalReference, Type: System.Int32) (Syntax: 't') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + WhenTrue: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3) (Syntax: '3') + WhenFalse: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 4) (Syntax: '4') + Locals: Local_1: System.Int32 t + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: 'var (_, _) => 2') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '(_, _)') (InputType: (System.Int32, System.Int32)?, NarrowedType: (System.Int32, System.Int32), DeclaredSymbol: null, MatchedType: (System.Int32, System.Int32), DeconstructSymbol: null) + DeconstructionSubpatterns (2): + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + PropertySubpatterns (0) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2')"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); + } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Patterns)] [Fact] public void SwitchExpression_Basic() @@ -29,7 +92,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (3 arms) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... 4, _ => 5 }') +ISwitchExpressionOperation (3 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... 4, _ => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(3): @@ -73,7 +136,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (0 arms) (OperationKind.SwitchExpression, Type: System.Object) (Syntax: 'x switch { }') +ISwitchExpressionOperation (0 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Object) (Syntax: 'x switch { }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(0) @@ -101,7 +164,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { => 5 }') +ISwitchExpressionOperation (1 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -137,7 +200,7 @@ void M(object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (3 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... 4, _ => 5 }') +ISwitchExpressionOperation (3 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... 4, _ => 5 }') Value: IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'x') Children(0) @@ -185,7 +248,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Object) (Syntax: 'x switch { ... _ => ""Z"" }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Object) (Syntax: 'x switch { ... _ => ""Z"" }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(2): @@ -227,7 +290,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" - ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: 'x switch { ... _ => ""Z"" }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: 'x switch { ... _ => ""Z"" }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(2): @@ -273,7 +336,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { _ /*=>*/ 5 }') +ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { _ /*=>*/ 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -306,7 +369,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: 'x switch { _ => /*5*/ }') +ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: 'x switch { _ => /*5*/ }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -340,7 +403,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... ound => 5 }') +ISwitchExpressionOperation (1 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... ound => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -379,7 +442,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: 'x switch { ... NotFound }') +ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: 'x switch { ... NotFound }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -413,7 +476,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... 5, 1 => 2 }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... 5, 1 => 2 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(2): @@ -453,7 +516,7 @@ void M(int? x, bool b, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... 2, _ => 5 }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... 2, _ => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(2): @@ -491,7 +554,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... alse => 5 }') +ISwitchExpressionOperation (2 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... alse => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(2): @@ -533,7 +596,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... true => 5 }') +ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: 'x switch { ... true => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(2): @@ -571,7 +634,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... ound => 5 }') +ISwitchExpressionOperation (1 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... ound => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -610,7 +673,7 @@ void M(int? x, object y) } "; string expectedOperationTree = @" -ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... nt z => 5 }') +ISwitchExpressionOperation (1 arms, IsExhaustive: False) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'x switch { ... nt z => 5 }') Value: IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?) (Syntax: 'x') Arms(1): @@ -657,7 +720,7 @@ public void TargetTypedSwitchExpression_ImplicitCast() IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Action, IsImplicit) (Syntax: 'b switch { ... arg => {} }') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: - ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Action) (Syntax: 'b switch { ... arg => {} }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Action) (Syntax: 'b switch { ... arg => {} }') Value: ILocalReferenceOperation: b (OperationKind.LocalReference, Type: System.Boolean) (Syntax: 'b') Arms(2): @@ -714,7 +777,7 @@ public void TargetTypedSwitchExpression_ExplicitCast() IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Action) (Syntax: '(Action {} })') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: - ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Action) (Syntax: 'b switch { ... arg => {} }') + ISwitchExpressionOperation (2 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Action) (Syntax: 'b switch { ... arg => {} }') Value: ILocalReferenceOperation: b (OperationKind.LocalReference, Type: System.Boolean) (Syntax: 'b') Arms(2): diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs index 6ae3b532284a8..40a5b1d08b621 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs @@ -1354,7 +1354,7 @@ Ignored Dimensions(1): IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'M') Arguments(1): IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: a) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'y switch { int z => 42 }') - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') Value: ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'y') Arms(1): @@ -1789,7 +1789,7 @@ Ignored Dimensions(1): IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'M2') Arguments(1): IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'y switch { int z => 42 }') - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') Value: ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'y') Arms(1): @@ -2309,7 +2309,7 @@ void M1() string expectedOperationTree = @" IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'int[y switc ... new int[0]') Ignored Dimensions(1): - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') Value: ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'y') Arms(1): @@ -2371,7 +2371,7 @@ void M1() string expectedOperationTree = @" IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'int[y switc ... new int[0]') Ignored Dimensions(1): - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') Value: ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'y') Arms(1): @@ -2430,7 +2430,7 @@ void M1() string expectedOperationTree = @" IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'int[y switc ... new int[0]') Ignored Dimensions(1): - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') Value: ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'y') Arms(1): @@ -2840,7 +2840,7 @@ void M1() string expectedOperationTree = @" IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'int[y switc ... new int[0]') Ignored Dimensions(1): - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'y switch { int z => 42 }') Value: ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'y') Arms(1): diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_TryCatch.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_TryCatch.cs index 8efd23de19c4e..5564db8b0a01d 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_TryCatch.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_TryCatch.cs @@ -8007,7 +8007,7 @@ Catch clauses(1): Initializer: null Filter: - ISwitchExpressionOperation (3 arms) (OperationKind.SwitchExpression, Type: System.Boolean) (Syntax: 'e.Message s ... }') + ISwitchExpressionOperation (3 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Boolean) (Syntax: 'e.Message s ... }') Value: IPropertyReferenceOperation: System.String System.Exception.Message { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'e.Message') Instance Receiver: diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs index 3503994a388a2..b0a2b42b3cf6c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs @@ -3149,7 +3149,7 @@ async Task M1(TypedReference tr) } }"; CreateCompilationWithMscorlib45(source).VerifyDiagnostics( - // (7,34): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or lambda expressions + // (7,34): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or async lambda expressions // async Task M1(TypedReference tr) Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "tr").WithArguments("System.TypedReference")); } @@ -3170,7 +3170,7 @@ async Task M1() } }"; CreateCompilationWithMscorlib45(source).VerifyDiagnostics( - // (9,9): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or lambda expressions + // (9,9): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or async lambda expressions // TypedReference tr; Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "TypedReference").WithArguments("System.TypedReference"), // (9,24): warning CS0168: The variable 'tr' is declared but never used @@ -3194,7 +3194,7 @@ async Task M1(bool truth) } }"; CreateCompilationWithMscorlib45(source).VerifyDiagnostics( - // (9,9): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or lambda expressions + // (9,9): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or async lambda expressions // var tr = new TypedReference(); Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("System.TypedReference")); } @@ -3216,7 +3216,7 @@ unsafe async public static void F() // (8,31): error CS0209: The type of a local declared in a fixed statement must be a pointer type // fixed (TypedReference tr) { } Diagnostic(ErrorCode.ERR_BadFixedInitType, "tr"), - // (8,16): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or lambda expressions. + // (8,16): error CS4012: Parameters or locals of type 'System.TypedReference' cannot be declared in async methods or async lambda expressions. // fixed (TypedReference tr) { } Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "TypedReference").WithArguments("System.TypedReference"), // (8,31): error CS0210: You must provide an initializer in a fixed or using statement declaration diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs index af01f7b27706b..5b29dc781eef4 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs @@ -244,30 +244,43 @@ static void M(A a) }"; CreateCompilation(source).VerifyDiagnostics( // (11,9): error CS1656: Cannot assign to 'E' because it is a 'method group' + // a.E += a.E; Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "a.E").WithArguments("E", "method group").WithLocation(11, 9), - // (12,13): error CS0019: Operator '!=' cannot be applied to operands of type 'method group' and '' - Diagnostic(ErrorCode.ERR_BadBinaryOps, "a.E != null").WithArguments("!=", "method group", "").WithLocation(12, 13), - // (14,15): error CS1503: Argument 1: cannot convert from 'method group' to 'object' + // (12,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // if (a.E != null) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "a.E").WithArguments("inferred delegate type").WithLocation(12, 13), + // (14,15): error CS1503: Argument 1: cannot convert from 'method group' to 'A' + // M(a.E); Diagnostic(ErrorCode.ERR_BadArgType, "a.E").WithArguments("1", "method group", "A").WithLocation(14, 15), - // (15, 15): error CS0119: 'A.E()' is a 'method', which is not valid in the given context + // (15,15): error CS0119: 'A.E()' is a method, which is not valid in the given context + // a.E.ToString(); Diagnostic(ErrorCode.ERR_BadSKunknown, "E").WithArguments("A.E()", "method").WithLocation(15, 15), // (16,17): error CS0023: Operator '!' cannot be applied to operand of type 'method group' + // o = !a.E; Diagnostic(ErrorCode.ERR_BadUnaryOp, "!a.E").WithArguments("!", "method group").WithLocation(16, 17), // (17,26): error CS0122: 'A.F()' is inaccessible due to its protection level + // o = a.E ?? a.F; Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(17, 26), // (19,11): error CS0122: 'A.F()' is inaccessible due to its protection level + // a.F += a.F; Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(19, 11), // (19,18): error CS0122: 'A.F()' is inaccessible due to its protection level + // a.F += a.F; Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(19, 18), // (20,15): error CS0122: 'A.F()' is inaccessible due to its protection level + // if (a.F != null) Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(20, 15), // (22,17): error CS0122: 'A.F()' is inaccessible due to its protection level + // M(a.F); Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(22, 17), // (23,15): error CS0122: 'A.F()' is inaccessible due to its protection level + // a.F.ToString(); Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(23, 15), // (24,20): error CS0122: 'A.F()' is inaccessible due to its protection level + // o = !a.F; Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(24, 20), // (25,39): error CS0122: 'A.F()' is inaccessible due to its protection level + // o = (o != null) ? a.E : a.F; Diagnostic(ErrorCode.ERR_BadAccess, "F").WithArguments("A.F()").WithLocation(25, 39)); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs new file mode 100644 index 0000000000000..6c601d03f70a6 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -0,0 +1,2086 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class DelegateTypeTests : CSharpTestBase + { + private const string s_utils = +@"using System; +using System.Linq; +static class Utils +{ + internal static string GetDelegateMethodName(this Delegate d) + { + var method = d.Method; + return Concat(GetTypeName(method.DeclaringType), method.Name); + } + internal static string GetDelegateTypeName(this Delegate d) + { + return d.GetType().GetTypeName(); + } + internal static string GetTypeName(this Type type) + { + if (type.IsArray) + { + return GetTypeName(type.GetElementType()) + ""[]""; + } + string typeName = type.Name; + int index = typeName.LastIndexOf('`'); + if (index >= 0) + { + typeName = typeName.Substring(0, index); + } + typeName = Concat(type.Namespace, typeName); + if (!type.IsGenericType) + { + return typeName; + } + return $""{typeName}<{string.Join("", "", type.GetGenericArguments().Select(GetTypeName))}>""; + } + private static string Concat(string container, string name) + { + return string.IsNullOrEmpty(container) ? name : container + ""."" + name; + } +}"; + + [Fact] + public void LanguageVersion() + { + var source = +@"class Program +{ + static void Main() + { + System.Delegate d; + d = Main; + d = () => { }; + d = delegate () { }; + System.Linq.Expressions.Expression e = () => 1; + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (6,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = Main; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Main").WithArguments("inferred delegate type").WithLocation(6, 13), + // (7,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = () => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => { }").WithArguments("inferred delegate type").WithLocation(7, 13), + // (8,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = delegate () { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "delegate () { }").WithArguments("inferred delegate type").WithLocation(8, 13), + // (9,48): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // System.Linq.Expressions.Expression e = () => 1; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => 1").WithArguments("inferred delegate type").WithLocation(9, 48)); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MethodGroupConversions() + { + var source = +@"class Program +{ + static void Main() + { + object o = Main; + System.ICloneable c = Main; + System.Delegate d = Main; + System.MulticastDelegate m = Main; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (5,20): error CS0428: Cannot convert method group 'Main' to non-delegate type 'object'. Did you intend to invoke the method? + // object o = Main; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Main").WithArguments("Main", "object").WithLocation(5, 20), + // (6,31): error CS0428: Cannot convert method group 'Main' to non-delegate type 'ICloneable'. Did you intend to invoke the method? + // System.ICloneable c = Main; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Main").WithArguments("Main", "System.ICloneable").WithLocation(6, 31), + // (8,38): error CS0428: Cannot convert method group 'Main' to non-delegate type 'MulticastDelegate'. Did you intend to invoke the method? + // System.MulticastDelegate m = Main; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Main").WithArguments("Main", "System.MulticastDelegate").WithLocation(8, 38)); + } + + [Fact] + public void LambdaConversions() + { + var source = +@"class Program +{ + static void Main() + { + object o = () => { }; + System.ICloneable c = () => { }; + System.Delegate d = () => { }; + System.MulticastDelegate m = () => { }; + d = x => x; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (5,20): error CS1660: Cannot convert lambda expression to type 'object' because it is not a delegate type + // object o = () => { }; + Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "() => { }").WithArguments("lambda expression", "object").WithLocation(5, 20), + // (6,31): error CS1660: Cannot convert lambda expression to type 'ICloneable' because it is not a delegate type + // System.ICloneable c = () => { }; + Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "() => { }").WithArguments("lambda expression", "System.ICloneable").WithLocation(6, 31), + // (8,38): error CS1660: Cannot convert lambda expression to type 'MulticastDelegate' because it is not a delegate type + // System.MulticastDelegate m = () => { }; + Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "() => { }").WithArguments("lambda expression", "System.MulticastDelegate").WithLocation(8, 38), + // (9,13): error CS8917: The delegate type could not be inferred. + // d = x => x; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(9, 13)); + } + + private static IEnumerable GetMethodGroupData(Func getExpectedDiagnostics) + { + yield return getData("static int F() => 0;", "Program.F", "F", "System.Func"); + yield return getData("static int F() => 0;", "F", "F", "System.Func"); + yield return getData("int F() => 0;", "(new Program()).F", "F", "System.Func"); + yield return getData("static T F() => default;", "Program.F", "F", "System.Func"); + yield return getData("static void F() where T : class { }", "F", "F", "System.Action"); + yield return getData("static void F() where T : struct { }", "F", "F", "System.Action"); + yield return getData("T F() => default;", "(new Program()).F", "F", "System.Func"); + yield return getData("T F() => default;", "(new Program()).F", "F", null); + yield return getData("void F(T t) { }", "(new Program()).F", "F", "System.Action"); + yield return getData("void F(T t) { }", "(new Program()).F", "F", null); + yield return getData("static ref int F() => throw null;", "F", "F", null); + yield return getData("static ref readonly int F() => throw null;", "F", "F", null); + yield return getData("static void F() { }", "F", "F", "System.Action"); + yield return getData("static void F(int x, int y) { }", "F", "F", "System.Action"); + yield return getData("static void F(out int x, int y) { x = 0; }", "F", "F", null); + yield return getData("static void F(int x, ref int y) { }", "F", "F", null); + yield return getData("static void F(int x, in int y) { }", "F", "F", null); + yield return getData("static void F(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) { }", "F", "F", "System.Action"); + yield return getData("static void F(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) { }", "F", "F", null); + yield return getData("static object F(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) => null;", "F", "F", "System.Func"); + yield return getData("static object F(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) => null;", "F", "F", null); + + object?[] getData(string methodDeclaration, string methodGroupExpression, string methodGroupOnly, string? expectedType) => + new object?[] { methodDeclaration, methodGroupExpression, expectedType is null ? getExpectedDiagnostics(methodGroupExpression, methodGroupOnly) : null, expectedType }; + } + + public static IEnumerable GetMethodGroupImplicitConversionData() + { + return GetMethodGroupData((methodGroupExpression, methodGroupOnly) => + { + int offset = methodGroupExpression.Length - methodGroupOnly.Length; + return new[] + { + // (6,29): error CS8917: The delegate type could not be inferred. + // System.Delegate d = F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, methodGroupOnly).WithLocation(6, 29 + offset) + }; + }); + } + + [Theory] + [MemberData(nameof(GetMethodGroupImplicitConversionData))] + public void MethodGroup_ImplicitConversion(string methodDeclaration, string methodGroupExpression, DiagnosticDescription[]? expectedDiagnostics, string? expectedType) + { + var source = +$@"class Program +{{ + {methodDeclaration} + static void Main() + {{ + System.Delegate d = {methodGroupExpression}; + System.Console.Write(d.GetDelegateTypeName()); + }} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedDiagnostics is null) + { + CompileAndVerify(comp, expectedOutput: expectedType); + } + else + { + comp.VerifyDiagnostics(expectedDiagnostics); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + } + + public static IEnumerable GetMethodGroupExplicitConversionData() + { + return GetMethodGroupData((methodGroupExpression, methodGroupOnly) => + { + int offset = methodGroupExpression.Length - methodGroupOnly.Length; + return new[] + { + // (6,20): error CS0030: Cannot convert type 'method' to 'Delegate' + // object o = (System.Delegate)F; + Diagnostic(ErrorCode.ERR_NoExplicitConv, $"(System.Delegate){methodGroupExpression}").WithArguments("method", "System.Delegate").WithLocation(6, 20) + }; + }); + } + + [Theory] + [MemberData(nameof(GetMethodGroupExplicitConversionData))] + public void MethodGroup_ExplicitConversion(string methodDeclaration, string methodGroupExpression, DiagnosticDescription[]? expectedDiagnostics, string? expectedType) + { + var source = +$@"class Program +{{ + {methodDeclaration} + static void Main() + {{ + object o = (System.Delegate){methodGroupExpression}; + System.Console.Write(o.GetType().GetTypeName()); + }} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedDiagnostics is null) + { + CompileAndVerify(comp, expectedOutput: expectedType); + } + else + { + comp.VerifyDiagnostics(expectedDiagnostics); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = ((CastExpressionSyntax)tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value).Expression; + var typeInfo = model.GetTypeInfo(expr); + // https://github.com/dotnet/roslyn/issues/52874: GetTypeInfo() for method group should return inferred delegate type. + Assert.Null(typeInfo.Type); + Assert.Null(typeInfo.ConvertedType); + } + + public static IEnumerable GetLambdaData() + { + yield return getData("x => x", null); + yield return getData("x => { return x; }", null); + yield return getData("x => ref args[0]", null); + yield return getData("(x, y) => { }", null); + yield return getData("() => 1", "System.Func"); + yield return getData("() => ref args[0]", null); + yield return getData("() => { }", "System.Action"); + yield return getData("(int x, int y) => { }", "System.Action"); + yield return getData("(out int x, int y) => { x = 0; }", null); + yield return getData("(int x, ref int y) => { x = 0; }", null); + yield return getData("(int x, in int y) => { x = 0; }", null); + yield return getData("(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) => { }", "System.Action"); + yield return getData("(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) => { }", null); + yield return getData("(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) => _1", "System.Func"); + yield return getData("(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) => _1", null); + yield return getData("static () => 1", "System.Func"); + yield return getData("async () => { await System.Threading.Tasks.Task.Delay(0); }", "System.Func"); + yield return getData("static async () => { await System.Threading.Tasks.Task.Delay(0); return 0; }", "System.Func>"); + yield return getData("() => Main", null); + yield return getData("(int x) => x switch { _ => null }", null); + yield return getData("_ => { }", null); + yield return getData("_ => _", null); + yield return getData("() => throw null", null); + yield return getData("x => throw null", null); + yield return getData("(int x) => throw null", null); + yield return getData("() => { throw null; }", "System.Action"); + yield return getData("(int x) => { throw null; }", "System.Action"); + yield return getData("(string s) => { if (s.Length > 0) return s; return null; }", "System.Func"); + yield return getData("(string s) => { if (s.Length > 0) return default; return s; }", "System.Func"); + yield return getData("(int i) => { if (i > 0) return i; return default; }", "System.Func"); + yield return getData("(int x, short y) => { if (x > 0) return x; return y; }", "System.Func"); + yield return getData("(int x, short y) => { if (x > 0) return y; return x; }", "System.Func"); + + static object?[] getData(string expr, string? expectedType) => + new object?[] { expr, expectedType }; + } + + public static IEnumerable GetAnonymousMethodData() + { + yield return getData("delegate { }", null); + yield return getData("delegate () { return 1; }", "System.Func"); + yield return getData("delegate () { return ref args[0]; }", null); + yield return getData("delegate () { }", "System.Action"); + yield return getData("delegate (int x, int y) { }", "System.Action"); + yield return getData("delegate (out int x, int y) { x = 0; }", null); + yield return getData("delegate (int x, ref int y) { x = 0; }", null); + yield return getData("delegate (int x, in int y) { x = 0; }", null); + yield return getData("delegate (int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) { }", "System.Action"); + yield return getData("delegate (int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) { }", null); + yield return getData("delegate (int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) { return _1; }", "System.Func"); + yield return getData("delegate (int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) { return _1; }", null); + + static object?[] getData(string expr, string? expectedType) => + new object?[] { expr, expectedType }; + } + + [Theory] + [MemberData(nameof(GetLambdaData))] + [MemberData(nameof(GetAnonymousMethodData))] + public void AnonymousFunction_ImplicitConversion(string anonymousFunction, string? expectedType) + { + var source = +$@"class Program +{{ + static void Main(string[] args) + {{ + System.Delegate d = {anonymousFunction}; + System.Console.Write(d.GetDelegateTypeName()); + }} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedType is null) + { + comp.VerifyDiagnostics( + // (5,29): error CS8917: The delegate type could not be inferred. + // System.Delegate d = x => x; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, anonymousFunction).WithLocation(5, 29)); + } + else + { + CompileAndVerify(comp, expectedOutput: expectedType); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(expr); + if (expectedType == null) + { + Assert.Null(typeInfo.Type); + } + else + { + Assert.Equal(expectedType, typeInfo.Type.ToTestDisplayString()); + } + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + } + + [Theory] + [MemberData(nameof(GetLambdaData))] + [MemberData(nameof(GetAnonymousMethodData))] + public void AnonymousFunction_ExplicitConversion(string anonymousFunction, string? expectedType) + { + var source = +$@"class Program +{{ + static void Main(string[] args) + {{ + object o = (System.Delegate)({anonymousFunction}); + System.Console.Write(o.GetType().GetTypeName()); + }} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedType is null) + { + comp.VerifyDiagnostics( + // (5,20): error CS8917: The delegate type could not be inferred. + // object o = (System.Delegate)(x => x); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, $"(System.Delegate)({anonymousFunction})").WithLocation(5, 20)); + } + else + { + CompileAndVerify(comp, expectedOutput: expectedType); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = ((CastExpressionSyntax)tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value).Expression; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(expectedType, typeInfo.ConvertedType?.ToTestDisplayString()); + } + + public static IEnumerable GetExpressionData() + { + yield return getData("x => x", null); + yield return getData("() => 1", "System.Func"); + yield return getData("(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16) => _1", "System.Func"); + yield return getData("(int _1, object _2, int _3, object _4, int _5, object _6, int _7, object _8, int _9, object _10, int _11, object _12, int _13, object _14, int _15, object _16, int _17) => _1", null); + yield return getData("static () => 1", "System.Func"); + + static object?[] getData(string expr, string? expectedType) => + new object?[] { expr, expectedType }; + } + + [Theory] + [MemberData(nameof(GetExpressionData))] + public void Expression_ImplicitConversion(string anonymousFunction, string? expectedType) + { + var source = +$@"class Program +{{ + static void Main(string[] args) + {{ + System.Linq.Expressions.Expression e = {anonymousFunction}; + }} +}}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + if (expectedType is null) + { + comp.VerifyDiagnostics( + // (5,48): error CS8917: The delegate type could not be inferred. + // System.Linq.Expressions.Expression e = x => x; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, anonymousFunction).WithLocation(5, 48)); + } + else + { + comp.VerifyDiagnostics(); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(expr); + if (expectedType == null) + { + Assert.Null(typeInfo.Type); + } + else + { + Assert.Equal($"System.Linq.Expressions.Expression<{expectedType}>", typeInfo.Type.ToTestDisplayString()); + } + Assert.Equal("System.Linq.Expressions.Expression", typeInfo.ConvertedType!.ToTestDisplayString()); + } + + [Theory] + [MemberData(nameof(GetExpressionData))] + public void Expression_ExplicitConversion(string anonymousFunction, string? expectedType) + { + var source = +$@"class Program +{{ + static void Main(string[] args) + {{ + object o = (System.Linq.Expressions.Expression)({anonymousFunction}); + }} +}}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + if (expectedType is null) + { + comp.VerifyDiagnostics( + // (5,20): error CS8917: The delegate type could not be inferred. + // object o = (System.Linq.Expressions.Expression)(x => x); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, $"(System.Linq.Expressions.Expression)({anonymousFunction})").WithLocation(5, 20)); + } + else + { + comp.VerifyDiagnostics(); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = ((CastExpressionSyntax)tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value).Expression; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + if (expectedType is null) + { + Assert.Null(typeInfo.ConvertedType); + } + else + { + Assert.Equal($"System.Linq.Expressions.Expression<{expectedType}>", typeInfo.ConvertedType.ToTestDisplayString()); + } + } + + /// + /// Should bind and report diagnostics from anonymous method body + /// regardless of whether the delegate type can be inferred. + /// + [Fact] + public void AnonymousMethodBodyErrors() + { + var source = +@"using System; +class Program +{ + static void Main() + { + Delegate d0 = x0 => { _ = x0.Length; object y0 = 0; _ = y0.Length; }; + Delegate d1 = (object x1) => { _ = x1.Length; }; + Delegate d2 = (ref object x2) => { _ = x2.Length; }; + Delegate d3 = delegate (object x3) { _ = x3.Length; }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (6,23): error CS8917: The delegate type could not be inferred. + // Delegate d0 = x0 => { _ = x0.Length; object y0 = 0; _ = y0.Length; }; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x0 => { _ = x0.Length; object y0 = 0; _ = y0.Length; }").WithLocation(6, 23), + // (6,68): error CS1061: 'object' does not contain a definition for 'Length' and no accessible extension method 'Length' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // Delegate d0 = x0 => { _ = x0.Length; object y0 = 0; _ = y0.Length; }; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Length").WithArguments("object", "Length").WithLocation(6, 68), + // (7,47): error CS1061: 'object' does not contain a definition for 'Length' and no accessible extension method 'Length' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // Delegate d1 = (object x1) => { _ = x1.Length; }; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Length").WithArguments("object", "Length").WithLocation(7, 47), + // (8,23): error CS8917: The delegate type could not be inferred. + // Delegate d2 = (ref object x2) => { _ = x2.Length; }; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "(ref object x2) => { _ = x2.Length; }").WithLocation(8, 23), + // (8,51): error CS1061: 'object' does not contain a definition for 'Length' and no accessible extension method 'Length' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // Delegate d2 = (ref object x2) => { _ = x2.Length; }; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Length").WithArguments("object", "Length").WithLocation(8, 51), + // (9,53): error CS1061: 'object' does not contain a definition for 'Length' and no accessible extension method 'Length' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // Delegate d3 = delegate (object x3) { _ = x3.Length; }; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Length").WithArguments("object", "Length").WithLocation(9, 53)); + } + + public static IEnumerable GetBaseAndDerivedTypesData() + { + yield return getData("internal void F(object x) { }", "internal static new void F(object x) { }", "F", "F", null, "System.Action"); // instance and static + // https://github.com/dotnet/roslyn/issues/52701: Assert failure: Unexpected value 'LessDerived' of type 'Microsoft.CodeAnalysis.CSharp.MemberResolutionKind' +#if !DEBUG + yield return getData("internal void F(object x) { }", "internal static new void F(object x) { }", "this.F", "F", + new[] + { + // (5,29): error CS0176: Member 'B.F(object)' cannot be accessed with an instance reference; qualify it with a type name instead + // System.Delegate d = this.F; + Diagnostic(ErrorCode.ERR_ObjectProhibited, "this.F").WithArguments("B.F(object)").WithLocation(5, 29) + }); // instance and static +#endif + yield return getData("internal void F(object x) { }", "internal static new void F(object x) { }", "base.F", "F", null, "System.Action"); // instance and static + yield return getData("internal static void F(object x) { }", "internal new void F(object x) { }", "F", "F", null, "System.Action"); // static and instance + yield return getData("internal static void F(object x) { }", "internal new void F(object x) { }", "this.F", "F", null, "System.Action"); // static and instance + yield return getData("internal static void F(object x) { }", "internal new void F(object x) { }", "base.F", "F"); // static and instance + yield return getData("internal void F(object x) { }", "internal static void F() { }", "F", "F"); // instance and static, different number of parameters + yield return getData("internal void F(object x) { }", "internal static void F() { }", "B.F", "F", null, "System.Action"); // instance and static, different number of parameters + yield return getData("internal void F(object x) { }", "internal static void F() { }", "this.F", "F", null, "System.Action"); // instance and static, different number of parameters + yield return getData("internal void F(object x) { }", "internal static void F() { }", "base.F", "F", null, "System.Action"); // instance and static, different number of parameters + yield return getData("internal static void F() { }", "internal void F(object x) { }", "F", "F"); // static and instance, different number of parameters + yield return getData("internal static void F() { }", "internal void F(object x) { }", "B.F", "F", null, "System.Action"); // static and instance, different number of parameters + yield return getData("internal static void F() { }", "internal void F(object x) { }", "this.F", "F", null, "System.Action"); // static and instance, different number of parameters + yield return getData("internal static void F() { }", "internal void F(object x) { }", "base.F", "F"); // static and instance, different number of parameters + yield return getData("internal static void F(object x) { }", "private static void F() { }", "F", "F"); // internal and private + yield return getData("private static void F(object x) { }", "internal static void F() { }", "F", "F", null, "System.Action"); // internal and private + yield return getData("internal abstract void F(object x);", "internal override void F(object x) { }", "F", "F", null, "System.Action"); // override + yield return getData("internal virtual void F(object x) { }", "internal override void F(object x) { }", "F", "F", null, "System.Action"); // override + yield return getData("internal void F(object x) { }", "internal void F(object x) { }", "F", "F", null, "System.Action"); // hiding + yield return getData("internal void F(object x) { }", "internal new void F(object x) { }", "F", "F", null, "System.Action"); // hiding + yield return getData("internal void F(object x) { }", "internal new void F(object y) { }", "F", "F", null, "System.Action"); // different parameter name + yield return getData("internal void F(object x) { }", "internal void F(string x) { }", "F", "F"); // different parameter type + yield return getData("internal void F(object x) { }", "internal void F(object x, object y) { }", "F", "F"); // different number of parameters + yield return getData("internal void F(object x) { }", "internal void F(ref object x) { }", "F", "F"); // different parameter ref kind + yield return getData("internal void F(ref object x) { }", "internal void F(object x) { }", "F", "F"); // different parameter ref kind + yield return getData("internal abstract object F();", "internal override object F() => throw null;", "F", "F", null, "System.Func"); // override + yield return getData("internal virtual object F() => throw null;", "internal override object F() => throw null;", "F", "F", null, "System.Func"); // override + yield return getData("internal object F() => throw null;", "internal object F() => throw null;", "F", "F", null, "System.Func"); // hiding + yield return getData("internal object F() => throw null;", "internal new object F() => throw null;", "F", "F", null, "System.Func"); // hiding + yield return getData("internal string F() => throw null;", "internal new object F() => throw null;", "F", "F"); // different return type + yield return getData("internal object F() => throw null;", "internal new ref object F() => throw null;", "F", "F"); // different return ref kind + yield return getData("internal ref object F() => throw null;", "internal new object F() => throw null;", "F", "F"); // different return ref kind + yield return getData("internal void F(object x) { }", "internal new void F(dynamic x) { }", "F", "F", null, "System.Action"); // object/dynamic + yield return getData("internal dynamic F() => throw null;", "internal new object F() => throw null;", "F", "F", null, "System.Func"); // object/dynamic + yield return getData("internal void F((object, int) x) { }", "internal new void F((object a, int b) x) { }", "F", "F", null, "System.Action>"); // tuple names + yield return getData("internal (object a, int b) F() => throw null;", "internal new (object, int) F() => throw null;", "F", "F", null, "System.Func>"); // tuple names + yield return getData("internal void F(System.IntPtr x) { }", "internal new void F(nint x) { }", "F", "F", null, "System.Action"); // System.IntPtr/nint + yield return getData("internal nint F() => throw null;", "internal new System.IntPtr F() => throw null;", "F", "F", null, "System.Func"); // System.IntPtr/nint + yield return getData("internal void F(object x) { }", +@"#nullable enable +internal new void F(object? x) { } +#nullable disable", "F", "F", null, "System.Action"); // different nullability + yield return getData( + @"#nullable enable +internal object? F() => throw null!; +#nullable disable", "internal new object F() => throw null;", "F", "F", null, "System.Func"); // different nullability + yield return getData("internal void F() { }", "internal void F() { }", "F", "F"); // different arity + yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity + yield return getData("internal void F() { }", "internal void F() { }", "F", "F"); // different arity + yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity + yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity + yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity + yield return getData("internal void F(T t) { }", "internal new void F(U u) { }", "F", "F", null, "System.Action"); // different type parameter names + yield return getData("internal void F(T t) where T : class { }", "internal new void F(T t) { }", "F", "F", null, "System.Action"); // different type parameter constraints + yield return getData("internal void F(T t) { }", "internal new void F(T t) where T : class { }", "F", "F", null, "System.Action"); // different type parameter constraints + yield return getData("internal void F(T t) { }", "internal new void F(T t) where T : class { }", "base.F", "F", null, "System.Action"); // different type parameter constraints + yield return getData("internal void F(T t) where T : class { }", "internal new void F(T t) where T : struct { }", "F", "F", null, "System.Action"); // different type parameter constraints + // https://github.com/dotnet/roslyn/issues/52701: Assert failure: Unexpected value 'LessDerived' of type 'Microsoft.CodeAnalysis.CSharp.MemberResolutionKind' +#if !DEBUG + yield return getData("internal void F(T t) where T : class { }", "internal new void F(T t) where T : struct { }", "F", "F", + new[] + { + // (5,29): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'B.F(T)' + // System.Delegate d = F; + Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "F").WithArguments("B.F(T)", "T", "object").WithLocation(5, 29) + }); // different type parameter constraints +#endif + + static object?[] getData(string methodA, string methodB, string methodGroupExpression, string methodGroupOnly, DiagnosticDescription[]? expectedDiagnostics = null, string? expectedType = null) + { + if (expectedDiagnostics is null && expectedType is null) + { + int offset = methodGroupExpression.Length - methodGroupOnly.Length; + expectedDiagnostics = new[] + { + // (5,29): error CS8917: The delegate type could not be inferred. + // System.Delegate d = F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, methodGroupOnly).WithLocation(5, 29 + offset) + }; + } + return new object?[] { methodA, methodB, methodGroupExpression, expectedDiagnostics, expectedType }; + } + } + + [Theory] + [MemberData(nameof(GetBaseAndDerivedTypesData))] + public void MethodGroup_BaseAndDerivedTypes(string methodA, string methodB, string methodGroupExpression, DiagnosticDescription[]? expectedDiagnostics, string? expectedType) + { + var source = +$@"partial class B +{{ + void M() + {{ + System.Delegate d = {methodGroupExpression}; + System.Console.Write(d.GetDelegateTypeName()); + }} + static void Main() + {{ + new B().M(); + }} +}} +abstract class A +{{ + {methodA} +}} +partial class B : A +{{ + {methodB} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedDiagnostics is null) + { + CompileAndVerify(comp, expectedOutput: expectedType); + } + else + { + comp.VerifyDiagnostics(expectedDiagnostics); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + } + + public static IEnumerable GetExtensionMethodsSameScopeData() + { + yield return getData("internal static void F(this object x) { }", "internal static void F(this string x) { }", "string.Empty.F", "F", null, "B.F", "System.Action"); // different parameter type + yield return getData("internal static void F(this object x) { }", "internal static void F(this string x) { }", "this.F", "F", null, "A.F", "System.Action"); // different parameter type + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x, object y) { }", "this.F", "F"); // different number of parameters + yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, ref object y) { }", "this.F", "F"); // different parameter ref kind + yield return getData("internal static void F(this object x, ref object y) { }", "internal static void F(this object x, object y) { }", "this.F", "F"); // different parameter ref kind + yield return getData("internal static object F(this object x) => throw null;", "internal static ref object F(this object x) => throw null;", "this.F", "F"); // different return ref kind + yield return getData("internal static ref object F(this object x) => throw null;", "internal static object F(this object x) => throw null;", "this.F", "F"); // different return ref kind + yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F"); // different arity + yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F", null, "B.F", "System.Action"); // different arity + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F"); // different arity + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F", null, "A.F", "System.Action"); // different arity + yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) { }", "this.F", "F", + new[] + { + // (5,29): error CS0121: The call is ambiguous between the following methods or properties: 'A.F(T)' and 'B.F(T)' + // System.Delegate d = this.F; + Diagnostic(ErrorCode.ERR_AmbigCall, "this.F").WithArguments("A.F(T)", "B.F(T)").WithLocation(5, 29) + }); // different type parameter constraints + yield return getData("internal static void F(this T t) { }", "internal static void F(this T t) where T : class { }", "this.F", "F", + new[] + { + // (5,29): error CS0121: The call is ambiguous between the following methods or properties: 'A.F(T)' and 'B.F(T)' + // System.Delegate d = this.F; + Diagnostic(ErrorCode.ERR_AmbigCall, "this.F").WithArguments("A.F(T)", "B.F(T)").WithLocation(5, 29) + }); // different type parameter constraints + yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) where T : struct { }", "this.F", "F"); // different type parameter constraints + + static object?[] getData(string methodA, string methodB, string methodGroupExpression, string methodGroupOnly, DiagnosticDescription[]? expectedDiagnostics = null, string? expectedMethod = null, string? expectedType = null) + { + if (expectedDiagnostics is null && expectedType is null) + { + int offset = methodGroupExpression.Length - methodGroupOnly.Length; + expectedDiagnostics = new[] + { + // (5,29): error CS8917: The delegate type could not be inferred. + // System.Delegate d = F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, methodGroupOnly).WithLocation(5, 29 + offset) + }; + } + return new object?[] { methodA, methodB, methodGroupExpression, expectedDiagnostics, expectedMethod, expectedType }; + } + } + + [Theory] + [MemberData(nameof(GetExtensionMethodsSameScopeData))] + public void MethodGroup_ExtensionMethodsSameScope(string methodA, string methodB, string methodGroupExpression, DiagnosticDescription[]? expectedDiagnostics, string? expectedMethod, string? expectedType) + { + var source = +$@"class Program +{{ + void M() + {{ + System.Delegate d = {methodGroupExpression}; + System.Console.Write(""{{0}}: {{1}}"", d.GetDelegateMethodName(), d.GetDelegateTypeName()); + }} + static void Main() + {{ + new Program().M(); + }} +}} +static class A +{{ + {methodA} +}} +static class B +{{ + {methodB} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedDiagnostics is null) + { + CompileAndVerify(comp, expectedOutput: $"{expectedMethod}: {expectedType}"); + } + else + { + comp.VerifyDiagnostics(expectedDiagnostics); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + + var symbolInfo = model.GetSymbolInfo(expr); + // https://github.com/dotnet/roslyn/issues/52870: GetSymbolInfo() should return resolved method from method group. + Assert.Null(symbolInfo.Symbol); + } + + public static IEnumerable GetExtensionMethodsDifferentScopeData() + { + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F", null, "A.F", "System.Action"); // hiding + yield return getData("internal static void F(this object x) { }", "internal static void F(this object y) { }", "this.F", "F", null, "A.F", "System.Action"); // different parameter name + yield return getData("internal static void F(this object x) { }", "internal static void F(this string x) { }", "string.Empty.F", "F", null, "A.F", "System.Action"); // different parameter type + yield return getData("internal static void F(this object x) { }", "internal static void F(this string x) { }", "this.F", "F", null, "A.F", "System.Action"); // different parameter type + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x, object y) { }", "this.F", "F"); // different number of parameters + yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, ref object y) { }", "this.F", "F"); // different parameter ref kind + yield return getData("internal static void F(this object x, ref object y) { }", "internal static void F(this object x, object y) { }", "this.F", "F"); // different parameter ref kind + yield return getData("internal static object F(this object x) => throw null;", "internal static ref object F(this object x) => throw null;", "this.F", "F"); // different return ref kind + yield return getData("internal static ref object F(this object x) => throw null;", "internal static object F(this object x) => throw null;", "this.F", "F"); // different return ref kind + yield return getData("internal static void F(this object x, System.IntPtr y) { }", "internal static void F(this object x, nint y) { }", "this.F", "F", null, "A.F", "System.Action"); // System.IntPtr/nint + yield return getData("internal static nint F(this object x) => throw null;", "internal static System.IntPtr F(this object x) => throw null;", "this.F", "F", null, "A.F", "System.Func"); // System.IntPtr/nint + yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F"); // different arity + yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F", null, "N.B.F", "System.Action"); // different arity + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F"); // different arity + yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F", null, "A.F", "System.Action"); // different arity + yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) { }", "this.F", "F", null, "A.F", "System.Action"); // different type parameter constraints + yield return getData("internal static void F(this T t) { }", "internal static void F(this T t) where T : class { }", "this.F", "F", null, "A.F", "System.Action"); // different type parameter constraints + yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) where T : struct { }", "this.F", "F"); // different type parameter constraints + + static object?[] getData(string methodA, string methodB, string methodGroupExpression, string methodGroupOnly, DiagnosticDescription[]? expectedDiagnostics = null, string? expectedMethod = null, string? expectedType = null) + { + if (expectedDiagnostics is null && expectedType is null) + { + int offset = methodGroupExpression.Length - methodGroupOnly.Length; + expectedDiagnostics = new[] + { + // (6,29): error CS8917: The delegate type could not be inferred. + // System.Delegate d = F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, methodGroupOnly).WithLocation(6, 29 + offset) + }; + } + return new object?[] { methodA, methodB, methodGroupExpression, expectedDiagnostics, expectedMethod, expectedType }; + } + } + + [Theory] + [MemberData(nameof(GetExtensionMethodsDifferentScopeData))] + public void MethodGroup_ExtensionMethodsDifferentScope(string methodA, string methodB, string methodGroupExpression, DiagnosticDescription[]? expectedDiagnostics, string? expectedMethod, string? expectedType) + { + var source = +$@"using N; +class Program +{{ + void M() + {{ + System.Delegate d = {methodGroupExpression}; + System.Console.Write(""{{0}}: {{1}}"", d.GetDelegateMethodName(), d.GetDelegateTypeName()); + }} + static void Main() + {{ + new Program().M(); + }} +}} +static class A +{{ + {methodA} +}} +namespace N +{{ + static class B + {{ + {methodB} + }} +}}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + if (expectedDiagnostics is null) + { + CompileAndVerify(comp, expectedOutput: $"{expectedMethod}: {expectedType}"); + } + else + { + comp.VerifyDiagnostics(expectedDiagnostics); + } + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + + var symbolInfo = model.GetSymbolInfo(expr); + // https://github.com/dotnet/roslyn/issues/52870: GetSymbolInfo() should return resolved method from method group. + Assert.Null(symbolInfo.Symbol); + } + + [Fact] + public void InstanceMethods_01() + { + var source = +@"using System; +class Program +{ + object F1() => null; + void F2(object x, int y) { } + void F() + { + Delegate d1 = F1; + Delegate d2 = this.F2; + Console.WriteLine(""{0}, {1}"", d1.GetDelegateTypeName(), d2.GetDelegateTypeName()); + } + static void Main() + { + new Program().F(); + } +}"; + CompileAndVerify(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, expectedOutput: "System.Func, System.Action"); + } + + [Fact] + public void InstanceMethods_02() + { + var source = +@"using System; +class A +{ + protected virtual void F() { Console.WriteLine(nameof(A)); } +} +class B : A +{ + protected override void F() { Console.WriteLine(nameof(B)); } + static void Invoke(Delegate d) { d.DynamicInvoke(); } + void M() + { + Invoke(F); + Invoke(this.F); + Invoke(base.F); + } + static void Main() + { + new B().M(); + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: +@"B +B +A"); + } + + [Fact] + public void InstanceMethods_03() + { + var source = +@"using System; +class A +{ + protected void F() { Console.WriteLine(nameof(A)); } +} +class B : A +{ + protected new void F() { Console.WriteLine(nameof(B)); } + static void Invoke(Delegate d) { d.DynamicInvoke(); } + void M() + { + Invoke(F); + Invoke(this.F); + Invoke(base.F); + } + static void Main() + { + new B().M(); + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: +@"B +B +A"); + } + + [Fact] + public void InstanceMethods_04() + { + var source = +@"class Program +{ + T F() => default; + static void Main() + { + var p = new Program(); + System.Delegate d = p.F; + object o = (System.Delegate)p.F; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (7,31): error CS8917: The delegate type could not be inferred. + // System.Delegate d = p.F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F").WithLocation(7, 31), + // (8,20): error CS0030: Cannot convert type 'method' to 'Delegate' + // object o = (System.Delegate)p.F; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(System.Delegate)p.F").WithArguments("method", "System.Delegate").WithLocation(8, 20)); + } + + [Fact] + public void MethodGroup_Inaccessible() + { + var source = +@"using System; +class A +{ + private static void F() { } + internal static void F(object o) { } +} +class B +{ + static void Main() + { + Delegate d = A.F; + Console.WriteLine(d.GetDelegateTypeName()); + } +}"; + CompileAndVerify(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, expectedOutput: "System.Action"); + } + + [Fact] + public void MethodGroup_IncorrectArity() + { + var source = +@"class Program +{ + static void F0(object o) { } + static void F0(object o) { } + static void F1(object o) { } + static void F1(object o) { } + static void F2(object o) { } + static void F2(object o) { } + static void Main() + { + System.Delegate d; + d = F0; + d = F1; + d = F2; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (12,13): error CS0308: The non-generic method 'Program.F0(object)' cannot be used with type arguments + // d = F0; + Diagnostic(ErrorCode.ERR_HasNoTypeVars, "F0").WithArguments("Program.F0(object)", "method").WithLocation(12, 13), + // (13,13): error CS0308: The non-generic method 'Program.F1(object)' cannot be used with type arguments + // d = F1; + Diagnostic(ErrorCode.ERR_HasNoTypeVars, "F1").WithArguments("Program.F1(object)", "method").WithLocation(13, 13), + // (14,13): error CS8917: The delegate type could not be inferred. + // d = F2; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F2").WithLocation(14, 13)); + } + + [Fact] + public void ExtensionMethods_01() + { + var source = +@"static class E +{ + internal static void F1(this object x, int y) { } + internal static void F2(this object x) { } +} +class Program +{ + void F2(int x) { } + static void Main() + { + System.Delegate d; + var p = new Program(); + d = p.F1; + d = p.F2; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (14,15): error CS8917: The delegate type could not be inferred. + // d = p.F2; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F2").WithLocation(14, 15)); + } + + [Fact] + public void ExtensionMethods_02() + { + var source = +@"using System; +static class E +{ + internal static void F(this System.Type x, int y) { } + internal static void F(this string x) { } +} +class Program +{ + static void Main() + { + Delegate d1 = typeof(Program).F; + Delegate d2 = """".F; + Console.WriteLine(""{0}, {1}"", d1.GetDelegateTypeName(), d2.GetDelegateTypeName()); + } +}"; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "System.Action, System.Action"); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().Select(d => d.Initializer!.Value).ToArray(); + Assert.Equal(2, exprs.Length); + + foreach (var expr in exprs) + { + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + } + } + + [Fact] + public void ExtensionMethods_03() + { + var source = +@"using N; +namespace N +{ + static class E1 + { + internal static void F1(this object x, int y) { } + internal static void F2(this object x, int y) { } + internal static void F2(this object x) { } + internal static void F3(this object x) { } + } +} +static class E2 +{ + internal static void F1(this object x) { } +} +class Program +{ + static void Main() + { + System.Delegate d; + var p = new Program(); + d = p.F1; + d = p.F2; + d = p.F3; + d = E1.F1; + d = E2.F1; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (22,15): error CS8917: The delegate type could not be inferred. + // d = p.F1; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F1").WithLocation(22, 15), + // (23,15): error CS8917: The delegate type could not be inferred. + // d = p.F2; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F2").WithLocation(23, 15)); + } + + [Fact] + public void ExtensionMethods_04() + { + var source = +@"static class E +{ + internal static void F1(this object x, int y) { } +} +static class Program +{ + static void F2(this object x) { } + static void Main() + { + System.Delegate d; + d = E.F1; + d = F2; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ExtensionMethods_05() + { + var source = +@"using System; +static class E +{ + internal static void F(this A a) { } +} +class A +{ +} +class B : A +{ + static void Invoke(Delegate d) { } + void M() + { + Invoke(F); + Invoke(this.F); + Invoke(base.F); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (14,16): error CS0103: The name 'F' does not exist in the current context + // Invoke(F); + Diagnostic(ErrorCode.ERR_NameNotInContext, "F").WithArguments("F").WithLocation(14, 16), + // (16,21): error CS0117: 'A' does not contain a definition for 'F' + // Invoke(base.F); + Diagnostic(ErrorCode.ERR_NoSuchMember, "F").WithArguments("A", "F").WithLocation(16, 21)); + } + + [Fact] + public void ExtensionMethods_06() + { + var source = +@"static class E +{ + internal static void F1(this object x, T y) { } + internal static void F2(this T t) { } +} +class Program +{ + static void F(T t) where T : class + { + System.Delegate d; + d = t.F1; + d = t.F2; + d = t.F1; + d = t.F1; + d = t.F2; + d = t.F2; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (11,15): error CS8917: The delegate type could not be inferred. + // d = t.F1; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F1").WithLocation(11, 15), + // (12,15): error CS8917: The delegate type could not be inferred. + // d = t.F2; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F2").WithLocation(12, 15)); + } + + /// + /// Method group with dynamic receiver does not use method group conversion. + /// + [Fact] + public void DynamicReceiver() + { + var source = +@"using System; +class Program +{ + void F() { } + static void Main() + { + dynamic d = new Program(); + object obj; + try + { + obj = d.F; + } + catch (Exception e) + { + obj = e; + } + Console.WriteLine(obj.GetType().FullName); + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, references: new[] { CSharpRef }, expectedOutput: "Microsoft.CSharp.RuntimeBinder.RuntimeBinderException"); + } + + // System.Func<> and System.Action<> cannot be used as the delegate type + // when the parameters or return type are not valid type arguments. + [Fact] + public void InvalidTypeArguments() + { + var source = +@"unsafe class Program +{ + static int* F() => throw null; + static void Main() + { + System.Delegate d; + d = F; + d = (int x, int* y) => { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeReleaseExe); + comp.VerifyDiagnostics( + // (7,13): error CS8917: The delegate type could not be inferred. + // d = F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F").WithLocation(7, 13), + // (8,13): error CS8917: The delegate type could not be inferred. + // d = (int x, int* y) => { }; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "(int x, int* y) => { }").WithLocation(8, 13)); + } + + [Fact] + public void GenericDelegateType() + { + var source = +@"using System; +class Program +{ + static void Main() + { + Delegate d = F(); + Console.WriteLine(d.GetDelegateTypeName()); + } + unsafe static Delegate F() + { + return (T t, int* p) => { }; + } +}"; + // When we synthesize delegate types, and infer a synthesized + // delegate type, run the program to report the actual delegate type. + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeReleaseExe); + comp.VerifyDiagnostics( + // (11,16): error CS8917: The delegate type could not be inferred. + // return (T t, int* p) => { }; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "(T t, int* p) => { }").WithLocation(11, 16)); + } + + /// + /// Custom modifiers should not affect delegate signature. + /// + [Fact] + public void CustomModifiers_01() + { + var sourceA = +@".class public A +{ + .method public static void F1(object modopt(int32) x) { ldnull throw } + .method public static object modopt(int32) F2() { ldnull throw } +}"; + var refA = CompileIL(sourceA); + + var sourceB = +@"using System; +class B +{ + static void Report(Delegate d) + { + Console.WriteLine(d.GetDelegateTypeName()); + } + static void Main() + { + Report(A.F1); + Report(A.F2); + } +}"; + var comp = CreateCompilation(new[] { sourceB, s_utils }, new[] { refA }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: +@"System.Action +System.Func"); + } + + /// + /// Custom modifiers should not affect delegate signature. + /// + [Fact] + public void CustomModifiers_02() + { + var sourceA = +@".class public A +{ + .method public static void F1(object modreq(int32) x) { ldnull throw } + .method public static object modreq(int32) F2() { ldnull throw } +}"; + var refA = CompileIL(sourceA); + + var sourceB = +@"using System; +class B +{ + static void Report(Delegate d) + { + Console.WriteLine(d.GetDelegateTypeName()); + } + static void Main() + { + Report(A.F1); + Report(A.F2); + } +}"; + var comp = CreateCompilation(new[] { sourceB, s_utils }, new[] { refA }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics( + // (10,16): error CS1503: Argument 1: cannot convert from 'method group' to 'Delegate' + // Report(A.F1); + Diagnostic(ErrorCode.ERR_BadArgType, "A.F1").WithArguments("1", "method group", "System.Delegate").WithLocation(10, 16), + // (11,16): error CS1503: Argument 1: cannot convert from 'method group' to 'Delegate' + // Report(A.F2); + Diagnostic(ErrorCode.ERR_BadArgType, "A.F2").WithArguments("1", "method group", "System.Delegate").WithLocation(11, 16)); + } + + [Fact] + public void UnmanagedCallersOnlyAttribute_01() + { + var source = +@"using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +class Program +{ + static void Main() + { + Delegate d = F; + } + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + static void F() { } +}"; + var comp = CreateCompilation(new[] { source, UnmanagedCallersOnlyAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (8,22): error CS8902: 'Program.F()' is attributed with 'UnmanagedCallersOnly' and cannot be converted to a delegate type. Obtain a function pointer to this method. + // Delegate d = F; + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeConvertedToDelegate, "F").WithArguments("Program.F()").WithLocation(8, 22)); + } + + [Fact] + public void UnmanagedCallersOnlyAttribute_02() + { + var source = +@"using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +class Program +{ + static void Main() + { + Delegate d = new S().F; + } +} +struct S +{ +} +static class E1 +{ + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + public static void F(this S s) { } +} +static class E2 +{ + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + public static void F(this S s) { } +}"; + var comp = CreateCompilation(new[] { source, UnmanagedCallersOnlyAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (8,22): error CS0121: The call is ambiguous between the following methods or properties: 'E1.F(S)' and 'E2.F(S)' + // Delegate d = new S().F; + Diagnostic(ErrorCode.ERR_AmbigCall, "new S().F").WithArguments("E1.F(S)", "E2.F(S)").WithLocation(8, 22)); + } + + [Fact] + public void SystemActionAndFunc_Missing() + { + var sourceA = +@".assembly mscorlib +{ + .ver 0:0:0:0 +} +.class public System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.ValueType extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.String extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Void extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Boolean extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Int32 extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Delegate extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +}"; + var refA = CompileIL(sourceA, prependDefaultHeader: false, autoInherit: false); + + var sourceB = +@"class Program +{ + static void Main() + { + System.Delegate d; + d = Main; + d = () => 1; + } +}"; + + var comp = CreateEmptyCompilation(sourceB, new[] { refA }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (6,13): error CS0518: Predefined type 'System.Action' is not defined or imported + // d = Main; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "Main").WithArguments("System.Action").WithLocation(6, 13), + // (6,13): error CS0518: Predefined type 'System.Action' is not defined or imported + // d = Main; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "Main").WithArguments("System.Action").WithLocation(6, 13), + // (6,13): error CS8917: The delegate type could not be inferred. + // d = Main; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "Main").WithLocation(6, 13), + // (7,13): error CS0518: Predefined type 'System.Func`1' is not defined or imported + // d = () => 1; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "() => 1").WithArguments("System.Func`1").WithLocation(7, 13)); + } + + [Fact] + public void SystemActionAndFunc_UseSiteErrors() + { + var sourceA = +@".assembly mscorlib +{ + .ver 0:0:0:0 +} +.class public System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.ValueType extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.String extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.Type extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Void extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Boolean extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Int32 extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Delegate extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Attribute extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Runtime.CompilerServices.RequiredAttributeAttribute extends System.Attribute +{ + .method public hidebysig specialname rtspecialname instance void .ctor(class System.Type t) cil managed { ret } +} +.class public abstract System.MulticastDelegate extends System.Delegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Action`1 extends System.MulticastDelegate +{ + .custom instance void System.Runtime.CompilerServices.RequiredAttributeAttribute::.ctor(class System.Type) = ( 01 00 FF 00 00 ) + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method public hidebysig instance void Invoke(!T t) { ret } +} +.class public sealed System.Func`1 extends System.MulticastDelegate +{ + .custom instance void System.Runtime.CompilerServices.RequiredAttributeAttribute::.ctor(class System.Type) = ( 01 00 FF 00 00 ) + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method public hidebysig instance !T Invoke() { ldnull throw } +}"; + var refA = CompileIL(sourceA, prependDefaultHeader: false, autoInherit: false); + + var sourceB = +@"class Program +{ + static void F(object o) + { + } + static void Main() + { + System.Delegate d; + d = F; + d = () => 1; + } +}"; + + var comp = CreateEmptyCompilation(sourceB, new[] { refA }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (9,13): error CS0648: 'Action' is a type not supported by the language + // d = F; + Diagnostic(ErrorCode.ERR_BogusType, "F").WithArguments("System.Action").WithLocation(9, 13), + // (9,13): error CS0648: 'Action' is a type not supported by the language + // d = F; + Diagnostic(ErrorCode.ERR_BogusType, "F").WithArguments("System.Action").WithLocation(9, 13), + // (10,13): error CS0648: 'Func' is a type not supported by the language + // d = () => 1; + Diagnostic(ErrorCode.ERR_BogusType, "() => 1").WithArguments("System.Func").WithLocation(10, 13)); + } + + [Fact] + public void SystemLinqExpressionsExpression_Missing() + { + var sourceA = +@".assembly mscorlib +{ + .ver 0:0:0:0 +} +.class public System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.ValueType extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.String extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.Type extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Void extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Boolean extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Int32 extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Delegate extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.MulticastDelegate extends System.Delegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Func`1 extends System.MulticastDelegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method public hidebysig instance !T Invoke() { ldnull throw } +} +.class public abstract System.Linq.Expressions.Expression extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +}"; + var refA = CompileIL(sourceA, prependDefaultHeader: false, autoInherit: false); + + var sourceB = +@"class Program +{ + static void Main() + { + System.Linq.Expressions.Expression e = () => 1; + } +}"; + + var comp = CreateEmptyCompilation(sourceB, new[] { refA }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (5,48): error CS0518: Predefined type 'System.Linq.Expressions.Expression`1' is not defined or imported + // System.Linq.Expressions.Expression e = () => 1; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "() => 1").WithArguments("System.Linq.Expressions.Expression`1").WithLocation(5, 48)); + } + + [Fact] + public void SystemLinqExpressionsExpression_UseSiteErrors() + { + var sourceA = +@".assembly mscorlib +{ + .ver 0:0:0:0 +} +.class public System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.ValueType extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.String extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public System.Type extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Void extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Boolean extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Int32 extends System.ValueType +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Delegate extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Attribute extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Runtime.CompilerServices.RequiredAttributeAttribute extends System.Attribute +{ + .method public hidebysig specialname rtspecialname instance void .ctor(class System.Type t) cil managed { ret } +} +.class public abstract System.MulticastDelegate extends System.Delegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Func`1 extends System.MulticastDelegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method public hidebysig instance !T Invoke() { ldnull throw } +} +.class public abstract System.Linq.Expressions.Expression extends System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public abstract System.Linq.Expressions.LambdaExpression extends System.Linq.Expressions.Expression +{ + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +} +.class public sealed System.Linq.Expressions.Expression`1 extends System.Linq.Expressions.LambdaExpression +{ + .custom instance void System.Runtime.CompilerServices.RequiredAttributeAttribute::.ctor(class System.Type) = ( 01 00 FF 00 00 ) + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } +}"; + var refA = CompileIL(sourceA, prependDefaultHeader: false, autoInherit: false); + + var sourceB = +@"class Program +{ + static void Main() + { + System.Linq.Expressions.Expression e = () => 1; + } +}"; + + var comp = CreateEmptyCompilation(sourceB, new[] { refA }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (5,48): error CS0648: 'Expression' is a type not supported by the language + // System.Linq.Expressions.Expression e = () => 1; + Diagnostic(ErrorCode.ERR_BogusType, "() => 1").WithArguments("System.Linq.Expressions.Expression").WithLocation(5, 48)); + } + + [WorkItem(4674, "https://github.com/dotnet/csharplang/issues/4674")] + [Fact] + public void OverloadResolution_01() + { + var source = +@"using System; + +class Program +{ + static void M(T t) { Console.WriteLine(""M(T t)""); } + static void M(Action a) { Console.WriteLine(""M(Action a)""); } + + static void F(object o) { } + + static void Main() + { + M(F); // C#9: M(Action) + } +}"; + + var expectedOutput = @"M(Action a)"; + CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: expectedOutput); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput); + } + + [WorkItem(4674, "https://github.com/dotnet/csharplang/issues/4674")] + [Fact] + public void OverloadResolution_02() + { + var source = +@"using System; +class Program +{ + static void Main() + { + var c = new C(); + c.M(Main); // C#9: E.M(object x, Action y) + c.M(() => { }); // C#9: E.M(object x, Action y) + } +} +class C +{ + public void M(object y) { Console.WriteLine(""C.M(object y)""); } +} +static class E +{ + public static void M(this object x, Action y) { Console.WriteLine(""E.M(object x, Action y)""); } +}"; + + var expectedOutput = +@"E.M(object x, Action y) +E.M(object x, Action y) +"; + CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: expectedOutput); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput); + } + + [WorkItem(4674, "https://github.com/dotnet/csharplang/issues/4674")] + [Fact] + public void OverloadResolution_03() + { + var source = +@"using System; +class Program +{ + static void Main() + { + var c = new C(); + c.M(Main); // C#9: E.M(object x, Action y) + c.M(() => { }); // C#9: E.M(object x, Action y) + } +} +class C +{ + public void M(Delegate d) { Console.WriteLine(""C.M""); } +} +static class E +{ + public static void M(this object o, Action a) { Console.WriteLine(""E.M""); } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (7,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // c.M(Main); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Main").WithArguments("inferred delegate type").WithLocation(7, 13), + // (8,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // c.M(() => { }); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => { }").WithArguments("inferred delegate type").WithLocation(8, 13)); + + // Breaking change from C#9 which binds to E.M. + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: +@"C.M +C.M +"); + } + + [WorkItem(4674, "https://github.com/dotnet/csharplang/issues/4674")] + [Fact] + public void OverloadResolution_04() + { + var source = +@"using System; +using System.Linq.Expressions; +class Program +{ + static void Main() + { + var c = new C(); + c.M(() => 1); + } +} +class C +{ + public void M(Expression e) { Console.WriteLine(""C.M""); } +} +static class E +{ + public static void M(this object o, Func a) { Console.WriteLine(""E.M""); } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (8,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // c.M(() => 1); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => 1").WithArguments("inferred delegate type").WithLocation(8, 13)); + + // Breaking change from C#9 which binds to E.M. + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: @"C.M"); + } + + [Fact] + public void OverloadResolution_05() + { + var source = +@"using System; +class Program +{ + static void Report(string name) { Console.WriteLine(name); } + static void FA(Delegate d) { Report(""FA(Delegate)""); } + static void FA(Action d) { Report(""FA(Action)""); } + static void FB(Delegate d) { Report(""FB(Delegate)""); } + static void FB(Func d) { Report(""FB(Func)""); } + static void F1() { } + static int F2() => 0; + static void Main() + { + FA(F1); + FA(F2); + FB(F1); + FB(F2); + FA(() => { }); + FA(() => 0); + FB(() => { }); + FB(() => 0); + FA(delegate () { }); + FA(delegate () { return 0; }); + FB(delegate () { }); + FB(delegate () { return 0; }); + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (14,12): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // FA(F2); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "F2").WithArguments("inferred delegate type").WithLocation(14, 12), + // (15,12): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // FB(F1); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "F1").WithArguments("inferred delegate type").WithLocation(15, 12), + // (18,12): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // FA(() => 0); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => 0").WithArguments("inferred delegate type").WithLocation(18, 12), + // (19,12): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // FB(() => { }); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => { }").WithArguments("inferred delegate type").WithLocation(19, 12), + // (22,12): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // FA(delegate () { return 0; }); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "delegate () { return 0; }").WithArguments("inferred delegate type").WithLocation(22, 12), + // (23,12): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // FB(delegate () { }); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "delegate () { }").WithArguments("inferred delegate type").WithLocation(23, 12)); + + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: +@"FA(Action) +FA(Delegate) +FB(Delegate) +FB(Func) +FA(Action) +FA(Delegate) +FB(Delegate) +FB(Func) +FA(Action) +FA(Delegate) +FB(Delegate) +FB(Func) +"); + } + + [Fact] + public void OverloadResolution_06() + { + var source = +@"using System; +using System.Linq.Expressions; +class Program +{ + static void Report(string name, Expression e) { Console.WriteLine(""{0}: {1}"", name, e); } + static void F(Expression e) { Report(""F(Expression)"", e); } + static void F(Expression> e) { Report(""F(Expression>)"", e); } + static void Main() + { + F(() => 0); + F(() => string.Empty); + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (11,11): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // F(() => string.Empty); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "() => string.Empty").WithArguments("inferred delegate type").WithLocation(11, 11)); + + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: +@"F(Expression>): () => 0 +F(Expression): () => String.Empty +"); + } + + [Fact] + public void OverloadResolution_07() + { + var source = +@"using System; +using System.Linq.Expressions; +class Program +{ + static void F(Expression e) { } + static void F(Expression> e) { } + static void Main() + { + F(delegate () { return 0; }); + F(delegate () { return string.Empty; }); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (9,11): error CS1946: An anonymous method expression cannot be converted to an expression tree + // F(delegate () { return 0; }); + Diagnostic(ErrorCode.ERR_AnonymousMethodToExpressionTree, "delegate () { return 0; }").WithLocation(9, 11), + // (10,11): error CS1946: An anonymous method expression cannot be converted to an expression tree + // F(delegate () { return string.Empty; }); + Diagnostic(ErrorCode.ERR_AnonymousMethodToExpressionTree, "delegate () { return string.Empty; }").WithLocation(10, 11)); + } + + [Fact] + public void ImplicitlyTypedVariables() + { + var source = +@"class Program +{ + static void Main() + { + var d1 = Main; + var d2 = () => { }; + var d3 = delegate () { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (5,13): error CS0815: Cannot assign method group to an implicitly-typed variable + // var d1 = Main; + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "d1 = Main").WithArguments("method group").WithLocation(5, 13), + // (6,13): error CS0815: Cannot assign lambda expression to an implicitly-typed variable + // var d2 = () => { }; + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "d2 = () => { }").WithArguments("lambda expression").WithLocation(6, 13), + // (7,13): error CS0815: Cannot assign anonymous method to an implicitly-typed variable + // var d3 = delegate () { }; + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "d3 = delegate () { }").WithArguments("anonymous method").WithLocation(7, 13)); + } + + /// + /// Ensure the conversion group containing the implicit + /// conversion is handled correctly in NullableWalker. + /// + [Fact] + public void NullableAnalysis_01() + { + var source = +@"#nullable enable +class Program +{ + static void Main() + { + System.Delegate d; + d = Main; + d = () => { }; + d = delegate () { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + /// + /// Ensure the conversion group containing the explicit + /// conversion is handled correctly in NullableWalker. + /// + [Fact] + public void NullableAnalysis_02() + { + var source = +@"#nullable enable +class Program +{ + static void Main() + { + object o; + o = (System.Delegate)Main; + o = (System.Delegate)(() => { }); + o = (System.Delegate)(delegate () { }); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + [Fact] + public void TaskRunArgument() + { + var source = +@"using System.Threading.Tasks; +class Program +{ + static async Task F() + { + await Task.Run(() => { }); + } +}"; + var verifier = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + var method = (MethodSymbol)verifier.TestData.GetMethodsByName()["Program.<>c.b__0_0()"].Method; + Assert.Equal("void Program.<>c.b__0_0()", method.ToTestDisplayString()); + verifier.VerifyIL("Program.<>c.b__0_0()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/GlobalUsingDirectiveTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/GlobalUsingDirectiveTests.cs new file mode 100644 index 0000000000000..ed1ed989e9023 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/GlobalUsingDirectiveTests.cs @@ -0,0 +1,4788 @@ +// 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.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.CSharp.UnitTests; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.Semantics +{ + public class GlobalUsingDirectiveTests : CompilingTestBase + { + [Fact] + public void MixingUsings_01() + { + var source = @" +#pragma warning disable CS8019 // Unnecessary using directive. + +global using ns1; +using ns2; +global using ns3; +using ns4; + +namespace ns1 {} +namespace ns2 {} +namespace ns3 {} +namespace ns4 {} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (6,1): error CS9002: A global using directive must precede all non-global using directives. + // global using ns3; + Diagnostic(ErrorCode.ERR_GlobalUsingOutOfOrder, "global").WithLocation(6, 1) + ); + } + + [Fact] + public void MixingUsings_02() + { + var source = @" +#pragma warning disable CS8019 // Unnecessary using directive. + +global using ns1; +global using ns3; + +namespace ns1 {} +namespace ns3 {} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(); + } + + [Fact] + public void MixingUsings_03() + { + var source = @" +#pragma warning disable CS8019 // Unnecessary using directive. + +global using ns1; +global using ns3; + +using ns4; +using ns2; + +namespace ns1 {} +namespace ns2 {} +namespace ns3 {} +namespace ns4 {} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(); + } + + [Fact] + public void InNamespace_01() + { + var source = @" +#pragma warning disable CS8019 // Unnecessary using directive. + +namespace ns +{ + global using ns1; + using ns2; + global using ns3; + using ns4; + + namespace ns1 {} + namespace ns2 {} + namespace ns3 {} + namespace ns4 {} +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (6,5): error CS9001: A global using directive cannot be used in a namespace declaration. + // global using ns1; + Diagnostic(ErrorCode.ERR_GlobalUsingInNamespace, "global").WithLocation(6, 5) + ); + } + + [Fact] + public void InNamespace_02() + { + var source = @" +#pragma warning disable CS8019 // Unnecessary using directive. + +namespace ns.ns.ns +{ + global using ns1; + using ns2; + global using ns3; + using ns4; + + namespace ns1 {} + namespace ns2 {} + namespace ns3 {} + namespace ns4 {} +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (6,5): error CS9001: A global using directive cannot be used in a namespace declaration. + // global using ns1; + Diagnostic(ErrorCode.ERR_GlobalUsingInNamespace, "global").WithLocation(6, 5) + ); + } + + [Fact] + public void ExternAliasScope_01() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 {} +} + +public class C5 +{ + public class C6 {} +} + +namespace NS7 +{ + public class C8 {} +} +"; + + var comp1 = CreateCompilation(source1); + var comp1Ref = comp1.ToMetadataReference().WithAliases(new[] { "alias1" }); + + var source2 = @" +extern alias alias1; + +global using A = alias1::C1; +global using B = alias1::NS3; +global using static alias1::C1; +global using alias1::NS3; + +class Program +{ + static void Main() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new B.C4()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new alias1::C1()); + System.Console.WriteLine(new alias1::NS3.C4()); + } +} +"; + var comp2 = CreateCompilation(source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + CompileAndVerify(comp2, expectedOutput: @" +C1 +C1+C2 +NS3.C4 +NS3.C4 +C1 +NS3.C4 +").VerifyDiagnostics(); + + var source3 = @" +extern alias alias1; + +global using A = alias1::C1; +global using B = alias1::NS3; +global using static alias1::C1; +global using alias1::NS3; + +using C = alias1::C5; +using D = alias1::NS7; +using static alias1::C5; +using alias1::NS7; + +class Program +{ + static void Main() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new B.C4()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new alias1::C1()); + System.Console.WriteLine(new alias1::NS3.C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C6()); + System.Console.WriteLine(new D.C8()); + System.Console.WriteLine(new C8()); + } +} +"; + var comp3 = CreateCompilation(source3, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + CompileAndVerify(comp3, expectedOutput: @" +C1 +C1+C2 +NS3.C4 +NS3.C4 +C1 +NS3.C4 +C5 +C5+C6 +NS7.C8 +NS7.C8 +").VerifyDiagnostics(); + } + + [Fact] + public void ExternAliasScope_02() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 {} +} + +public class C5 +{ + public class C6 {} +} + +namespace NS7 +{ + public class C8 {} +} +"; + + var comp1 = CreateCompilation(source1); + var comp1Ref = comp1.ToMetadataReference().WithAliases(new[] { "alias1" }); + + var source2 = @" +extern alias alias1; + +global using A = alias1::C1; +global using B = alias1::NS3; +global using static alias1::C1; +global using alias1::NS3; + +Program.Test(); +System.Console.WriteLine(new alias1::C1()); +System.Console.WriteLine(new alias1::NS3.C4()); + +class Program +{ + public static void Test() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new B.C4()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new alias1::C1()); + System.Console.WriteLine(new alias1::NS3.C4()); + } +} +"; + var comp2 = CreateCompilation(source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + CompileAndVerify(comp2, expectedOutput: @" +C1 +C1+C2 +NS3.C4 +NS3.C4 +C1 +NS3.C4 +C1 +NS3.C4 +").VerifyDiagnostics(); + + var source3 = @" +extern alias alias1; + +global using A = alias1::C1; +global using B = alias1::NS3; +global using static alias1::C1; +global using alias1::NS3; + +using C = alias1::C5; +using D = alias1::NS7; +using static alias1::C5; +using alias1::NS7; + +Program.Test(); +System.Console.WriteLine(new alias1::C1()); +System.Console.WriteLine(new alias1::NS3.C4()); + +class Program +{ + public static void Test() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new B.C4()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new alias1::C1()); + System.Console.WriteLine(new alias1::NS3.C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C6()); + System.Console.WriteLine(new D.C8()); + System.Console.WriteLine(new C8()); + } +} +"; + var comp3 = CreateCompilation(source3, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + CompileAndVerify(comp3, expectedOutput: @" +C1 +C1+C2 +NS3.C4 +NS3.C4 +C1 +NS3.C4 +C5 +C5+C6 +NS7.C8 +NS7.C8 +C1 +NS3.C4 +").VerifyDiagnostics(); + } + + [Fact] + public void ExternAliasScope_03() + { + var source1 = @" +public class C1 +{ +} + +namespace NS3 +{ + public class C4 {} +} +"; + + var comp1 = CreateCompilation(source1); + var comp1Ref = comp1.ToMetadataReference().WithAliases(new[] { "alias1" }); + + var source2 = @" +extern alias alias1; +"; + var source3 = @" +global using A = alias1::C1; +global using B = alias1::NS3; + +class Program +{ + static void Main() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + } +} +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + var expected = new[] + { + // (2,1): hidden CS8020: Unused extern alias. + // extern alias alias1; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias alias1;").WithLocation(2, 1), + // (2,18): error CS0432: Alias 'alias1' not found + // global using A = alias1::C1; + Diagnostic(ErrorCode.ERR_AliasNotFound, "alias1").WithArguments("alias1").WithLocation(2, 18), + // (3,18): error CS0432: Alias 'alias1' not found + // global using B = alias1::NS3; + Diagnostic(ErrorCode.ERR_AliasNotFound, "alias1").WithArguments("alias1").WithLocation(3, 18) + }; + + comp2.VerifyDiagnostics(expected); + + var comp3 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + comp2.VerifyDiagnostics(expected); + } + + [Fact] + public void GlobalUsingAliasScope_01() + { + var ext = @" +public class Extern +{ +} +"; + var extComp = CreateCompilation(ext); + var extCompRef = extComp.ToMetadataReference().WithAliases(new[] { "ext" }); + + var extAlias = @" +extern alias ext; +"; + + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var globalUsings1 = @" +global using A = C1; +"; + + var globalUsings2 = @" +global using B = NS3; +"; + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + } +} +"; + + test(source2, expectedOutput: @" +C1 +NS3.C4 +"); + + var source4 = @" +namespace NS +{ + class Program + { + static void Main() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + } + } +} +"; + + test(source4, expectedOutput: @" +C1 +NS3.C4 +"); + + var source5 = @" +namespace NS +{ + using C = A.C2; + using D = B::NS7; + using static B::C4; + using B.NS7; + + class Program + { + static void Main() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C5()); + System.Console.WriteLine(new D.C8()); + System.Console.WriteLine(new C8()); + } + } +} +"; + + test(source5, expectedOutput: @" +C1 +NS3.C4 +C1+C2 +NS3.C4+C5 +NS3.NS7.C8 +NS3.NS7.C8 +"); + + void test(string source, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { extAlias + source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, extAlias + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + } + } + + [Fact] + public void GlobalUsingAliasScope_02() + { + var ext = @" +public class Extern +{ +} +"; + var extComp = CreateCompilation(ext); + var extCompRef = extComp.ToMetadataReference().WithAliases(new[] { "ext" }); + + var extAlias = @" +extern alias ext; +"; + + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var globalUsings1 = @" +global using A = C1; +"; + + var globalUsings2 = @" +global using B = NS3; +"; + + var source2 = @" +Program.Test(); +System.Console.WriteLine(new A()); +System.Console.WriteLine(new B.C4()); + +class Program +{ + public static void Test() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + } +} +"; + + test(source2, expectedOutput: @" +C1 +NS3.C4 +C1 +NS3.C4 +"); + + var source3 = @" +N.Program.Test(); +System.Console.WriteLine(new A()); +System.Console.WriteLine(new B.C4()); + +namespace N +{ + using C = A.C2; + using D = B::NS7; + using static B::C4; + using B.NS7; + + class Program + { + public static void Test() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C5()); + System.Console.WriteLine(new D.C8()); + System.Console.WriteLine(new C8()); + } + } +} +"; + + test(source3, expectedOutput: @" +C1 +NS3.C4 +C1+C2 +NS3.C4+C5 +NS3.NS7.C8 +NS3.NS7.C8 +C1 +NS3.C4 +"); + + void test(string source, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { extAlias + source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, extAlias + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + } + } + + [Fact] + public void GlobalUsingAliasScope_03() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var globalUsings1 = @" +global using A = C1; +"; + + var globalUsings2 = @" +global using B = NS3; +"; + + var source2 = @" +Program.Test(); +System.Console.WriteLine(new A()); +System.Console.WriteLine(new B.C4()); +"; + + test(source2, + @" +class Program +{ + public static void Test() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + } +} +", + expectedOutput: @" +C1 +NS3.C4 +C1 +NS3.C4 +"); + + var source3 = @" +N.Program.Test(); +System.Console.WriteLine(new A()); +System.Console.WriteLine(new B.C4()); +"; + + test(source3, + @" +namespace N +{ + using C = A.C2; + using D = B.NS7; + using static B.C4; + using B::NS7; + + class Program + { + public static void Test() + { + System.Console.WriteLine(new A()); + System.Console.WriteLine(new B.C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C5()); + System.Console.WriteLine(new D.C8()); + System.Console.WriteLine(new C8()); + } + } +} +", + expectedOutput: @" +C1 +NS3.C4 +C1+C2 +NS3.C4+C5 +NS3.NS7.C8 +NS3.NS7.C8 +C1 +NS3.C4 +"); + + void test(string source, string program, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + } + + [Fact] + public void GlobalUsingAliasScope_04() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var globalUsings1 = @" +global using A = C1; +global using B = NS3; +"; + + var globalUsings2 = @" +#line 1000 +global using C = A.C2; +#line 2000 +global using D = B.NS7; +#line 3000 +global using static B.C4; +#line 4000 +global using B.NS7; +"; + + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2, source1 }, parseOptions: TestOptions.RegularPreview); + + var expected = new DiagnosticDescription[] + { + // (1000,18): error CS0246: The type or namespace name 'A' could not be found (are you missing a using directive or an assembly reference?) + // global using C = A.C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A").WithArguments("A").WithLocation(1000, 18), + // (2000,18): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // global using D = B.NS7; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(2000, 18), + // (3000,21): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // global using static B.C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(3000, 21), + // (4000,14): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // global using B.NS7; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(4000, 14) + }; + + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings2, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings2 + globalUsings1, source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + } + + [Fact] + public void GlobalUsingAliasScope_05() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var globalUsings1 = @" +global using A = C1; +global using B = NS3; +"; + + var usings2 = @" +#line 1000 +using C = A.C2; +#line 2000 +using D = B.NS7; +#line 3000 +using static B.C4; +#line 4000 +using B.NS7; +"; + + var comp = CreateCompilation(new[] { globalUsings1 + usings2, source1 }, parseOptions: TestOptions.RegularPreview); + + var expected = new DiagnosticDescription[] + { + // (1000,11): error CS0246: The type or namespace name 'A' could not be found (are you missing a using directive or an assembly reference?) + // using C = A.C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A").WithArguments("A").WithLocation(1000, 11), + // (2000,11): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // using D = B.NS7; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(2000, 11), + // (3000,14): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // using static B.C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(3000, 14), + // (4000,7): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // using B.NS7; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(4000, 7) + }; + + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { usings2, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings1 + source1, usings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + } + + [Fact] + public void GlobalUsingAliasScope_06() + { + var source1 = @" +global using A = C1; + +Program1.Test(); +NS1.Program2.Test(); +NS2.Program3.Test(); + +class Program1 +{ + public static void Test() + { + System.Console.WriteLine(new A()); + } +} + +namespace NS1 +{ + using A = C2; + + class Program2 + { + public static void Test() + { + System.Console.WriteLine(new A()); + } + } +} + +namespace NS2 +{ + using NS3; + + class Program3 + { + public static void Test() + { + System.Console.WriteLine(new A()); + } + } + + namespace NS3 + { + class A {} + } +} + +public class C1 {} +public class C2 {} +"; + + var comp = CreateCompilation(source1, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: @" +C1 +C2 +NS2.NS3.A +").VerifyDiagnostics(); + } + + [Fact] + public void UsingAliasScope_01() + { + var source1 = @" +global using C = A.C2; +global using D = B::NS7; +global using static B::C4; +global using B.NS7; + +using A = C1; +using B = NS3; + +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var comp = CreateCompilation(source1, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (2,18): error CS0246: The type or namespace name 'A' could not be found (are you missing a using directive or an assembly reference?) + // global using C = A.C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A").WithArguments("A").WithLocation(2, 18), + // (3,18): error CS0432: Alias 'B' not found + // global using D = B::NS7; + Diagnostic(ErrorCode.ERR_AliasNotFound, "B").WithArguments("B").WithLocation(3, 18), + // (4,21): error CS0432: Alias 'B' not found + // global using static B::C4; + Diagnostic(ErrorCode.ERR_AliasNotFound, "B").WithArguments("B").WithLocation(4, 21), + // (5,14): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // global using B.NS7; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(5, 14) + ); + } + + [Fact] + public void UsingAliasScope_02() + { + var source1 = @" +using A = C1; +using B = NS3; + +global using C = A.C2; +global using D = B::NS7; +global using static B::C4; +global using B.NS7; + +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } + + namespace NS7 + { + public class C8 {} + } +} +"; + + var comp = CreateCompilation(source1, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (5,1): error CS9002: A global using directive must precede all non-global using directives. + // global using C = A.C2; + Diagnostic(ErrorCode.ERR_GlobalUsingOutOfOrder, "global").WithLocation(5, 1), + // (5,18): error CS0246: The type or namespace name 'A' could not be found (are you missing a using directive or an assembly reference?) + // global using C = A.C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A").WithArguments("A").WithLocation(5, 18), + // (6,18): error CS0432: Alias 'B' not found + // global using D = B::NS7; + Diagnostic(ErrorCode.ERR_AliasNotFound, "B").WithArguments("B").WithLocation(6, 18), + // (7,21): error CS0432: Alias 'B' not found + // global using static B::C4; + Diagnostic(ErrorCode.ERR_AliasNotFound, "B").WithArguments("B").WithLocation(7, 21), + // (8,14): error CS0246: The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?) + // global using B.NS7; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "B").WithArguments("B").WithLocation(8, 14) + ); + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_01() + { + var ext = @" +public class Extern +{ +} +"; + var extComp = CreateCompilation(ext); + var extCompRef = extComp.ToMetadataReference().WithAliases(new[] { "ext" }); + + var extAlias = @" +extern alias ext; +"; + + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + + var globalUsings1 = @" +global using static C1; +"; + + var globalUsings2 = @" +global using NS3; +"; + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + } +} +"; + + test(source2, expectedOutput: @" +C1+C2 +NS3.C4 +"); + + var source4 = @" +namespace NS +{ + class Program + { + static void Main() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + } + } +} +"; + + test(source4, expectedOutput: @" +C1+C2 +NS3.C4 +"); + + var source5 = @" +namespace NS +{ + using C = C2; + using static C4; + + class Program + { + static void Main() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C5()); + } + } +} +"; + + test(source5, expectedOutput: @" +C1+C2 +NS3.C4 +C1+C2 +NS3.C4+C5 +"); + + void test(string source, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { extAlias + source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, extAlias + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + } + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_02() + { + var ext = @" +public class Extern +{ +} +"; + var extComp = CreateCompilation(ext); + var extCompRef = extComp.ToMetadataReference().WithAliases(new[] { "ext" }); + + var extAlias = @" +extern alias ext; +"; + + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + + var globalUsings1 = @" +global using static C1; +"; + + var globalUsings2 = @" +global using NS3; +"; + + var source2 = @" +Program.Test(); +System.Console.WriteLine(new C2()); +System.Console.WriteLine(new C4()); + +class Program +{ + public static void Test() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + } +} +"; + + test(source2, expectedOutput: @" +C1+C2 +NS3.C4 +C1+C2 +NS3.C4 +"); + + var source3 = @" +N.Program.Test(); +System.Console.WriteLine(new C2()); +System.Console.WriteLine(new C4()); + +namespace N +{ + using C = C2; + using static C4; + + class Program + { + public static void Test() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C5()); + } + } +} +"; + + test(source3, expectedOutput: @" +C1+C2 +NS3.C4 +C1+C2 +NS3.C4+C5 +C1+C2 +NS3.C4 +"); + + void test(string source, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { extAlias + source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, extAlias + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { extCompRef }); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics( + // (2,1): hidden CS8020: Unused extern alias. + // extern alias ext; + Diagnostic(ErrorCode.HDN_UnusedExternAlias, "extern alias ext;").WithLocation(2, 1) + ); + } + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_03() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + + var globalUsings1 = @" +global using static C1; +"; + + var globalUsings2 = @" +global using NS3; +"; + + var source2 = @" +Program.Test(); +System.Console.WriteLine(new C2()); +System.Console.WriteLine(new C4()); +"; + + test(source2, + @" +class Program +{ + public static void Test() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + } +} +", + expectedOutput: @" +C1+C2 +NS3.C4 +C1+C2 +NS3.C4 +"); + + var source3 = @" +N.Program.Test(); +System.Console.WriteLine(new C2()); +System.Console.WriteLine(new C4()); +"; + + test(source3, + @" +namespace N +{ + using C = C2; + using static C4; + + class Program + { + public static void Test() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + + System.Console.WriteLine(new C()); + System.Console.WriteLine(new C5()); + } + } +} +", + expectedOutput: @" +C1+C2 +NS3.C4 +C1+C2 +NS3.C4+C5 +C1+C2 +NS3.C4 +"); + + void test(string source, string program, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source, program }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_04() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + + var globalUsings1 = @" +global using static C1; +global using NS3; +"; + + var globalUsings2 = @" +global using C = C2; +global using static C4; +"; + + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2, source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (5,18): error CS0246: The type or namespace name 'C2' could not be found (are you missing a using directive or an assembly reference?) + // global using C = C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C2").WithArguments("C2").WithLocation(5, 18), + // (6,21): error CS0246: The type or namespace name 'C4' could not be found (are you missing a using directive or an assembly reference?) + // global using static C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C4").WithArguments("C4").WithLocation(6, 21) + ); + + var expected = new DiagnosticDescription[] + { + // (2,18): error CS0246: The type or namespace name 'C2' could not be found (are you missing a using directive or an assembly reference?) + // global using C = C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C2").WithArguments("C2").WithLocation(2, 18), + // (3,21): error CS0246: The type or namespace name 'C4' could not be found (are you missing a using directive or an assembly reference?) + // global using static C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C4").WithArguments("C4").WithLocation(3, 21) + }; + + comp = CreateCompilation(new[] { globalUsings2, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings2 + globalUsings1, source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_05() + { + var source1 = @" +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + + var globalUsings1 = @" +global using static C1; +global using NS3; +"; + + var usings2 = @" +using C = C2; +using static C4; +"; + + var comp = CreateCompilation(new[] { globalUsings1 + usings2, source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (5,11): error CS0246: The type or namespace name 'C2' could not be found (are you missing a using directive or an assembly reference?) + // using C = C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C2").WithArguments("C2").WithLocation(5, 11), + // (6,14): error CS0246: The type or namespace name 'C4' could not be found (are you missing a using directive or an assembly reference?) + // using static C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C4").WithArguments("C4").WithLocation(6, 14) + ); + + var expected = new DiagnosticDescription[] + { + // (2,11): error CS0246: The type or namespace name 'C2' could not be found (are you missing a using directive or an assembly reference?) + // using C = C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C2").WithArguments("C2").WithLocation(2, 11), + // (3,14): error CS0246: The type or namespace name 'C4' could not be found (are you missing a using directive or an assembly reference?) + // using static C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C4").WithArguments("C4").WithLocation(3, 14) + }; + + comp = CreateCompilation(new[] { usings2, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp = CreateCompilation(new[] { globalUsings1 + source1, usings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_06() + { + var source1 = @" +global using static C1; +global using NS4; + +Program1.Test(); +NS1.Program2.Test(); +NS2.Program3.Test(); + +class Program1 +{ + public static void Test() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C3()); + } +} + +namespace NS1 +{ + using static C4; + using NS5; + + class Program2 + { + public static void Test() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C3()); + } + } +} + +namespace NS2 +{ + using C2 = NS3.A; + + class Program3 + { + public static void Test() + { + System.Console.WriteLine(new C2()); + } + } + + namespace NS3 + { + class A {} + } +} + +public class C1 +{ + public class C2 {} +} + +namespace NS4 +{ + class C3 {} +} + +public class C4 +{ + public class C2 {} +} + +namespace NS5 +{ + class C3 {} +} +"; + + var comp = CreateCompilation(source1, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: @" +C1+C2 +NS4.C3 +C4+C2 +NS5.C3 +NS2.NS3.A +").VerifyDiagnostics(); + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_07() + { + var source1 = @" +namespace NS0 +{ + public static class C1 + { + public static void M2(this int x) => System.Console.WriteLine(""NS0.C1.M2 {0}"", x); + } +} + +namespace NS3 +{ + public static class C4 + { + public static void M5(this int x) => System.Console.WriteLine(""NS3.C4.M5 {0}"", x); + } +} + +namespace NS6 +{ + public static class C7 + { + public static void M8(this int x) => System.Console.WriteLine(""NS6.C7.M8 {0}"", x); + } +} + +namespace NS9 +{ + public static class C10 + { + public static void M11(this int x) => System.Console.WriteLine(""NS9.C10.M11 {0}"", x); + } +} +"; + + var globalUsings1 = @" +global using static NS0.C1; +"; + + var globalUsings2 = @" +global using NS3; +"; + + var source2 = @" +class Program +{ + static void Main() + { + 1.M2(); + 2.M5(); + } +} +"; + + test(source2, expectedOutput: @" +NS0.C1.M2 1 +NS3.C4.M5 2 +"); + + var source3 = @" +using NS6; +using static NS9.C10; + +class Program +{ + static void Main() + { + 3.M2(); + 4.M5(); + + 5.M8(); + 6.M11(); + } +} +"; + + test(source3, expectedOutput: @" +NS0.C1.M2 3 +NS3.C4.M5 4 +NS6.C7.M8 5 +NS9.C10.M11 6 +"); + + var source4 = @" +namespace NS +{ + class Program + { + static void Main() + { + 1.M2(); + 2.M5(); + } + } +} +"; + + test(source4, expectedOutput: @" +NS0.C1.M2 1 +NS3.C4.M5 2 +"); + + void test(string source, string expectedOutput) + { + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source, source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings2 + source, globalUsings1 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + source1, globalUsings2 + source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 + source1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source1, source }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + } + + [Fact] + public void GlobalUsingNamespaceOrTypeScope_08() + { + var source1 = @" +namespace NS0 +{ + public static class C1 + { + public static void M2(this int x) => System.Console.WriteLine(""NS0.C1.M2 {0}"", x); + } +} + +namespace NS3 +{ + public static class C4 + { + public static void M5(this int x) => System.Console.WriteLine(""NS3.C4.M5 {0}"", x); + } +} + +namespace NS6 +{ + public static class C7 + { + public static void M5(this int x) => System.Console.WriteLine(""NS6.C7.M5 {0}"", x); + } +} + +namespace NS9 +{ + public static class C10 + { + public static void M2(this int x) => System.Console.WriteLine(""NS9.C10.M2 {0}"", x); + } +} +"; + + var source2 = @" +global using static NS0.C1; +global using NS3; + +Program1.Test(); +NS12.Program2.Test(); + +class Program1 +{ + public static void Test() + { + 1.M2(); + 2.M5(); + } +} + +namespace NS12 +{ + using NS6; + using static NS9.C10; + + class Program2 + { + public static void Test() + { + 3.M2(); + 4.M5(); + } + } +} +"; + + var comp = CreateCompilation(new[] { source1, source2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: @" +NS0.C1.M2 1 +NS3.C4.M5 2 +NS9.C10.M2 3 +NS6.C7.M5 4 +").VerifyDiagnostics(); + } + + [Fact] + public void UsingNamespaceOrTypeScope_01() + { + var source1 = @" +global using C = C2; +global using static C4; + +using static C1; +using NS3; + +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + var comp = CreateCompilation(source1, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (2,18): error CS0246: The type or namespace name 'C2' could not be found (are you missing a using directive or an assembly reference?) + // global using C = C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C2").WithArguments("C2").WithLocation(2, 18), + // (3,21): error CS0246: The type or namespace name 'C4' could not be found (are you missing a using directive or an assembly reference?) + // global using static C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C4").WithArguments("C4").WithLocation(3, 21) + ); + } + + [Fact] + public void UsingNamespaceOrTypeScope_02() + { + var source1 = @" +using static C1; +using NS3; + +global using C = C2; +global using static C4; + +public class C1 +{ + public class C2 {} +} + +namespace NS3 +{ + public class C4 + { + public class C5 + { + } + } +} +"; + var comp = CreateCompilation(source1, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (5,1): error CS9002: A global using directive must precede all non-global using directives. + // global using C = C2; + Diagnostic(ErrorCode.ERR_GlobalUsingOutOfOrder, "global").WithLocation(5, 1), + // (5,18): error CS0246: The type or namespace name 'C2' could not be found (are you missing a using directive or an assembly reference?) + // global using C = C2; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C2").WithArguments("C2").WithLocation(5, 18), + // (6,21): error CS0246: The type or namespace name 'C4' could not be found (are you missing a using directive or an assembly reference?) + // global using static C4; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C4").WithArguments("C4").WithLocation(6, 21) + ); + } + + [Fact] + public void GlobalUsingMerge_01() + { + var source1 = @" +public class C1 +{ + public class C2 {}; +} + +namespace NS3 +{ + public class C4 {} +} + +namespace NS6 +{ + public class C7 {} +} + +namespace NS9 +{ + public class C10 {} +} +"; + var source2 = @" +global using static C1; +"; + var source3 = @" +global using C4 = NS3.C4; +"; + var source4 = @" +global using NS6; +"; + var source5 = @" +global using C10 = NS9.C10; +"; + var source6 = @" +class Program1 +{ + public static void Main() + { + System.Console.WriteLine(new C2()); + System.Console.WriteLine(new C4()); + System.Console.WriteLine(new C7()); + System.Console.WriteLine(new C10()); + } +} +"; + + var comp = CreateCompilation(new[] { source2, source1, source3, source6, source4, "", source5 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: @" +C1+C2 +NS3.C4 +NS6.C7 +NS9.C10 +").VerifyDiagnostics(); + } + + [Fact] + public void AliasConfictWithExternAlias_01() + { + var source1 = @" +public class C1 +{ +} +"; + + var comp1 = CreateCompilation(source1); + var comp1Ref = comp1.ToMetadataReference().WithAliases(new[] { "alias1", "alias2" }); + + var source2 = @" +#line 1000 +extern alias alias1; +#line 2000 +extern alias alias2; +"; + var source3 = @" +#line 3000 +global using alias1 = C2; +"; + var source4 = @" +#line 4000 +global using alias1 = C3; +"; + var source5 = @" +#line 5000 +using alias1 = C4; +#line 6000 +using alias2 = C5; +"; + var source6 = @" +class C2 {} +class C3 {} +class C4 {} +class C5 {} +"; + var comp2 = CreateCompilation(new[] { source2 + source3, source5, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp2.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (3000,1): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "global using alias1 = C2;").WithArguments("alias1").WithLocation(3000, 1) + ); + + var comp3 = CreateCompilation(new[] { source2 + source3 + source4, source5, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp3.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (3000,1): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "global using alias1 = C2;").WithArguments("alias1").WithLocation(3000, 1), + // (4000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(4000, 14) + ); + + var comp4 = CreateCompilation(new[] { source2 + source3 + source5, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp4.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (3000,1): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "global using alias1 = C2;").WithArguments("alias1").WithLocation(3000, 1), + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (6000,1): error CS1537: The using alias 'alias2' appeared previously in this namespace + // using alias2 = C5; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "using alias2 = C5;").WithArguments("alias2").WithLocation(6000, 1) + ); + + var comp5 = CreateCompilation(new[] { source2 + source3 + source4 + source5, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + + var expected1 = new[] + { + // (3000,1): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "global using alias1 = C2;").WithArguments("alias1").WithLocation(3000, 1), + // (4000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(4000, 14), + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (6000,1): error CS1537: The using alias 'alias2' appeared previously in this namespace + // using alias2 = C5; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "using alias2 = C5;").WithArguments("alias2").WithLocation(6000, 1) + }; + + comp5.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify(expected1); + + var comp6 = CreateCompilation(new[] { source2, source3, source5, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp6.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (1000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // extern alias alias1; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 14) + ); + + var comp7 = CreateCompilation(new[] { source2, source3, source4, source5, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp7.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (4000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(4000, 14), + // (1000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // extern alias alias1; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 14) + ); + + var comp8 = CreateCompilation(new[] { source2 + source3 + source5, source4, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp8.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify(expected1); + + var comp9 = CreateCompilation(new[] { source2 + source5, source3, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp9.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (1000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // extern alias alias1; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 14), + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (6000,1): error CS1537: The using alias 'alias2' appeared previously in this namespace + // using alias2 = C5; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "using alias2 = C5;").WithArguments("alias2").WithLocation(6000, 1) + ); + + var comp10 = CreateCompilation(new[] { source2 + source5, source3, source4, source6 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp10.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify( + // (4000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(4000, 14), + // (1000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // extern alias alias1; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 14), + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7), + // (6000,1): error CS1537: The using alias 'alias2' appeared previously in this namespace + // using alias2 = C5; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "using alias2 = C5;").WithArguments("alias2").WithLocation(6000, 1) + ); + } + + [Fact] + public void AliasConfictWithExternAlias_02() + { + var source1 = @" +public class C1 +{ +} +"; + + var comp1 = CreateCompilation(source1); + var comp1Ref = comp1.ToMetadataReference().WithAliases(new[] { "alias1" }); + + var source2 = @" +#line 1000 +extern alias alias1; +"; + var source3 = @" +#line 2000 +global using alias1 = C2; + +class C2 {} +"; + var comp2 = CreateCompilation(new[] { source1, source2, source3 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + + var expected = new[] + { + // (1000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // extern alias alias1; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 14) + }; + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify(expected); + + comp2 = CreateCompilation(new[] { source1, source3, source2 }, parseOptions: TestOptions.RegularPreview, references: new[] { comp1Ref }); + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDeclarationDiagnostics().Where(d => d.Code is not ((int)ErrorCode.HDN_UnusedUsingDirective or (int)ErrorCode.HDN_UnusedExternAlias)).Verify(expected); + } + + [Fact] + public void AliasConfictWithGlobalAlias_01() + { + var source3 = @" +#line 1000 +global using alias1 = C2; +"; + var source4 = @" +#line 2000 +global using alias1 = C3; +"; + var source5 = @" +#line 3000 +using alias1 = C4; +"; + var source6 = @" +class C2 {} +class C3 {} +class C4 {} +"; + var comp2 = CreateCompilation(new[] { source3, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (3000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(3000, 7) + }; + + comp2.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp3 = CreateCompilation(new[] { source3 + source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected2 = new[] + { + // (2000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(2000, 14), + // (3000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(3000, 7) + }; + + comp3.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp4 = CreateCompilation(new[] { source3 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp4.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp5 = CreateCompilation(new[] { source3 + source4 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp5.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp6 = CreateCompilation(new[] { source3, source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp6.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp7 = CreateCompilation(new[] { source3 + source5, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp7.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp8 = CreateCompilation(new[] { source5, source3, source6 }, parseOptions: TestOptions.RegularPreview); + comp8.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp9 = CreateCompilation(new[] { source5, source3, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp9.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + } + + [Fact] + public void AliasConfictWithGlobalAlias_02() + { + var source2 = @" +#line 1000 +global using alias1 = C3; +"; + var source3 = @" +#line 2000 +global using alias1 = C2; + +class C2 {} +class C3 {} +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (2000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(2000, 14) + ); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (1000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 14) + ); + } + + [Fact] + public void AliasConfictWithGlobalAlias_03() + { + var source2 = @" +global using alias1 = C3; +"; + var source3 = @" +#line 1000 +using alias1 = C2; +#line default + +class C2 {} +class C3 {} +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + + var expected = new[] + { + // (1000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(1000, 7) + }; + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp2 = CreateCompilation(source2 + source3, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + } + + [Fact] + public void TypeConfictWithGlobalAlias_01() + { + var source3 = @" +#line 1000 +global using C2 = C2; +"; + var source4 = @" +#line 2000 +global using C4 = C4; +"; + var source5 = @" +#line 3000 +using C3 = C3; +#line 4000 +using C = C4; +#line 5000 +using static C4; + +class Test +{ + void M() + { +#line 6000 + _ = new C2(); +#line 7000 + _ = new C3(); +#line 8000 + _ = new C4(); + } +} +"; + var source6 = @" +class C2 {} +class C3 {} +class C4 {} +"; + var comp2 = CreateCompilation(new[] { source3, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (6000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'C2' + // _ = new C2(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "C2").WithArguments("C2", "").WithLocation(6000, 17), + // (7000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'C3' + // _ = new C3(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "C3").WithArguments("C3", "").WithLocation(7000, 17) + }; + + comp2.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp3 = CreateCompilation(new[] { source3 + source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected2 = new[] + { + // (6000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'C2' + // _ = new C2(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "C2").WithArguments("C2", "").WithLocation(6000, 17), + // (7000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'C3' + // _ = new C3(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "C3").WithArguments("C3", "").WithLocation(7000, 17), + // (8000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'C4' + // _ = new C4(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "C4").WithArguments("C4", "").WithLocation(8000, 17) + }; + + comp3.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp4 = CreateCompilation(new[] { source3 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp4.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp5 = CreateCompilation(new[] { source3 + source4 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp5.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp6 = CreateCompilation(new[] { source3, source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp6.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp7 = CreateCompilation(new[] { source3 + source5, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp7.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp8 = CreateCompilation(new[] { source5, source3, source6 }, parseOptions: TestOptions.RegularPreview); + comp8.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp9 = CreateCompilation(new[] { source5, source3, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp9.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + } + + [Fact] + public void NamespaceConfictWithGlobalAlias_01() + { + var source3 = @" +#line 1000 +global using NS2 = NS2; +"; + var source4 = @" +#line 2000 +global using NS4 = NS4; +"; + var source5 = @" +#line 3000 +using NS3 = NS3; +#line 4000 +using C = NS4; +#line 5000 +using NS4; + +class Test +{ + void M() + { +#line 6000 + _ = new NS2.C2(); +#line 7000 + _ = new NS3.C3(); +#line 8000 + _ = new NS4.C4(); + } +} +"; + var source6 = @" +namespace NS2 +{ + class C2 {} +} +namespace NS3 +{ + class C3 {} +} +namespace NS4 +{ + class C4 {} +} +"; + var comp2 = CreateCompilation(new[] { source3, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (6000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'NS2' + // _ = new NS2.C2(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "NS2").WithArguments("NS2", "").WithLocation(6000, 17), + // (7000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'NS3' + // _ = new NS3.C3(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "NS3").WithArguments("NS3", "").WithLocation(7000, 17) + }; + + comp2.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp3 = CreateCompilation(new[] { source3 + source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected2 = new[] + { + // (6000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'NS2' + // _ = new NS2.C2(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "NS2").WithArguments("NS2", "").WithLocation(6000, 17), + // (7000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'NS3' + // _ = new NS3.C3(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "NS3").WithArguments("NS3", "").WithLocation(7000, 17), + // (8000,17): error CS0576: Namespace '' contains a definition conflicting with alias 'NS4' + // _ = new NS4.C4(); + Diagnostic(ErrorCode.ERR_ConflictAliasAndMember, "NS4").WithArguments("NS4", "").WithLocation(8000, 17) + }; + + comp3.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp4 = CreateCompilation(new[] { source3 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp4.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp5 = CreateCompilation(new[] { source3 + source4 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp5.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp6 = CreateCompilation(new[] { source3, source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp6.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp7 = CreateCompilation(new[] { source3 + source5, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp7.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp8 = CreateCompilation(new[] { source5, source3, source6 }, parseOptions: TestOptions.RegularPreview); + comp8.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp9 = CreateCompilation(new[] { source5, source3, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp9.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + } + + [Fact] + public void UsingConfictWithGlobalUsing_01() + { + var source3 = @" +#line 1000 +global using static C2; +"; + var source4 = @" +#line 2000 +global using static C2; +"; + var source5 = @" +#line 3000 +using static C2; +"; + var source6 = @" +class C2 {} +"; + var comp2 = CreateCompilation(new[] { source3, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (3000,14): warning CS0105: The using directive for 'C2' appeared previously in this namespace + // using static C2; + Diagnostic(ErrorCode.WRN_DuplicateUsing, "C2").WithArguments("C2").WithLocation(3000, 14) + }; + + comp2.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp3 = CreateCompilation(new[] { source3 + source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + + var expected2 = new[] + { + // (2000,21): warning CS0105: The using directive for 'C2' appeared previously in this namespace + // global using static C2; + Diagnostic(ErrorCode.WRN_DuplicateUsing, "C2").WithArguments("C2").WithLocation(2000, 21), + // (3000,14): warning CS0105: The using directive for 'C2' appeared previously in this namespace + // using static C2; + Diagnostic(ErrorCode.WRN_DuplicateUsing, "C2").WithArguments("C2").WithLocation(3000, 14) + }; + + comp3.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp4 = CreateCompilation(new[] { source3 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp4.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp5 = CreateCompilation(new[] { source3 + source4 + source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp5.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp6 = CreateCompilation(new[] { source3, source4, source5, source6 }, parseOptions: TestOptions.RegularPreview); + comp6.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp7 = CreateCompilation(new[] { source3 + source5, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp7.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + var comp8 = CreateCompilation(new[] { source5, source3, source6 }, parseOptions: TestOptions.RegularPreview); + comp8.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var comp9 = CreateCompilation(new[] { source5, source3, source4, source6 }, parseOptions: TestOptions.RegularPreview); + comp9.GetDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + } + + [Fact] + public void UsingConfictWithGlobalUsing_02() + { + var source2 = @" +#line 1000 +global using static C2; +"; + var source3 = @" +#line 2000 +global using static C2; + +class C2 {} +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (2000,21): warning CS0105: The using directive for 'C2' appeared previously in this namespace + // global using static C2; + Diagnostic(ErrorCode.WRN_DuplicateUsing, "C2").WithArguments("C2").WithLocation(2000, 21) + ); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (1000,21): warning CS0105: The using directive for 'C2' appeared previously in this namespace + // global using static C2; + Diagnostic(ErrorCode.WRN_DuplicateUsing, "C2").WithArguments("C2").WithLocation(1000, 21) + ); + } + + [Fact] + public void UsingConfictWithGlobalUsing_03() + { + var source2 = @" +global using static C2; +"; + var source3 = @" +#line 1000 +using static C2; +#line default + +class C2 {} +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + + var expected = new[] + { + // (1000,14): warning CS0105: The using directive for 'C2' appeared previously in this namespace + // using static C2; + Diagnostic(ErrorCode.WRN_DuplicateUsing, "C2").WithArguments("C2").WithLocation(1000, 14) + }; + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + + comp2 = CreateCompilation(source2 + source3, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDeclarationDiagnostics().Where(d => d.Code != (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected); + } + + [Fact] + public void UnusedGlobalAlias_01() + { + var source2 = @" +#line 1000 +global using alias1 = C2; +#line 2000 +global using alias2 = C3; +"; + var source3 = @" +#line 3000 +global using alias3 = C4; +#line 3500 +global using alias5 = C4; +#line 4000 +using alias4 = C2; +#line 5000 +using alias6 = C2; + +class C1 +{ + void M() + { + new alias2(); + new alias5(); + new alias6(); + } +} + +class C2 {} +class C3 {} +class C4 {} +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using alias1 = C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias1 = C2;").WithLocation(1000, 1), + // (3000,1): hidden CS8019: Unnecessary using directive. + // global using alias3 = C4; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias3 = C4;").WithLocation(3000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using alias4 = C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias4 = C2;").WithLocation(4000, 1) + }; + + comp2.VerifyDiagnostics(expected1); + + var expected2 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using alias1 = C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias1 = C2;").WithLocation(1000, 1) + }; + + var expected3 = new[] + { + // (3000,1): hidden CS8019: Unnecessary using directive. + // global using alias3 = C4; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias3 = C4;").WithLocation(3000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using alias4 = C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias4 = C2;").WithLocation(4000, 1) + }; + + comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected2); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected3); + + comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected3); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected2); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected3); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected2); + + comp2 = CreateCompilation(source2 + source3, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected1); + } + + [Fact] + public void UnusedGlobalStaticUsing_01() + { + var source2 = @" +#line 1000 +global using static C2; +#line 2000 +global using static C3; +"; + var source3 = @" +#line 3000 +global using static C4; +#line 3500 +global using static C6; +#line 4000 +using static C5; +#line 5000 +using static C7; + +class C1 +{ + void M() + { + new C33(); + new C66(); + new C77(); + } +} + +class C2 {} +class C3 { public class C33 {} } +class C4 {} +class C5 {} +class C6 { public class C66 {} } +class C7 { public class C77 {} } +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using static C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C2;").WithLocation(1000, 1), + // (3000,1): hidden CS8019: Unnecessary using directive. + // global using static C4; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C4;").WithLocation(3000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using static C5; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C5;").WithLocation(4000, 1) + }; + + comp2.VerifyDiagnostics(expected1); + + var expected2 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using static C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C2;").WithLocation(1000, 1) + }; + + var expected3 = new[] + { + // (3000,1): hidden CS8019: Unnecessary using directive. + // global using static C4; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C4;").WithLocation(3000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using static C5; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C5;").WithLocation(4000, 1) + }; + + comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected2); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected3); + + comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected3); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected2); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected3); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected2); + + comp2 = CreateCompilation(source2 + source3, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected1); + } + + [Fact] + public void UnusedGlobalUsingNamespace_01() + { + var source2 = @" +#line 1000 +global using C2; +#line 2000 +global using C3; +"; + var source3 = @" +#line 3000 +global using C4; +#line 3500 +global using C6; +#line 4000 +using C5; +#line 5000 +using C7; + +class C1 +{ + void M() + { + new C33(); + new C66(); + new C77(); + } +} + +namespace C2 {} +namespace C3 { class C33 {} } +namespace C4 {} +namespace C5 {} +namespace C6 { class C66 {} } +namespace C7 { class C77 {} } +"; + var comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using C2;").WithLocation(1000, 1), + // (3000,1): hidden CS8019: Unnecessary using directive. + // global using C4; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using C4;").WithLocation(3000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using C5; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using C5;").WithLocation(4000, 1) + }; + + comp2.VerifyDiagnostics(expected1); + + var expected2 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using C2;").WithLocation(1000, 1) + }; + + var expected3 = new[] + { + // (3000,1): hidden CS8019: Unnecessary using directive. + // global using C4; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using C4;").WithLocation(3000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using C5; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using C5;").WithLocation(4000, 1) + }; + + comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected2); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected3); + + comp2 = CreateCompilation(new[] { source2, source3 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected3); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected2); + + comp2 = CreateCompilation(new[] { source3, source2 }, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected3); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected2); + + comp2 = CreateCompilation(source2 + source3, parseOptions: TestOptions.RegularPreview); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected1); + } + + [Fact] + public void UnusedGlobalUsingNamespace_02() + { + var source2 = @" +#line 1000 +global using N2; +#line 2000 +global using N3; +#line 3000 +global using N10; + +class C2 +{ + void M() + { + new C1010(); +#line 10000 + new C10000(); + } +} + +#line 40000 +partial class C0 : C0000 +{ +} +"; + var source3 = @" +#line 4000 +using N5; +#line 5000 +using N7; + +class C3 +{ + void M() + { + new C33(); + new C77(); +#line 20000 + new C20000(); + } +} + +namespace N2 {} +namespace N3 { class C33 {} } +namespace N5 {} +namespace N7 { class C77 {} } +namespace N8 {} +namespace N9 { class C99 {} } +namespace N10 { class C1010 {} } + +#line 50000 +partial class C0 : C0000 +{ +} +"; + var source4 = @" +#line 6000 +using N8; +#line 7000 +using N9; + +class C4 +{ + void M() + { + new C99(); +#line 30000 + new C30000(); + } +} + +#line 60000 +partial class C0 : C0000 +{ +} +"; + var comp2 = CreateCompilation(new[] { source2, source3, source4 }, parseOptions: TestOptions.RegularPreview); + + Assert.Empty(comp2.UsageOfUsingsRecordedInTrees); + + comp2.VerifyDiagnostics( + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using N2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using N2;").WithLocation(1000, 1), + // (4000,1): hidden CS8019: Unnecessary using directive. + // using N5; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N5;").WithLocation(4000, 1), + // (6000,1): hidden CS8019: Unnecessary using directive. + // using N8; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N8;").WithLocation(6000, 1), + // (10000,13): error CS0246: The type or namespace name 'C10000' could not be found (are you missing a using directive or an assembly reference?) + // new C10000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C10000").WithArguments("C10000").WithLocation(10000, 13), + // (20000,13): error CS0246: The type or namespace name 'C20000' could not be found (are you missing a using directive or an assembly reference?) + // new C20000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C20000").WithArguments("C20000").WithLocation(20000, 13), + // (30000,13): error CS0246: The type or namespace name 'C30000' could not be found (are you missing a using directive or an assembly reference?) + // new C30000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C30000").WithArguments("C30000").WithLocation(30000, 13), + // (40000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(40000, 20), + // (50000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(50000, 20), + // (60000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(60000, 20) + ); + + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + var expected0 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using N2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using N2;").WithLocation(1000, 1), + // (10000,13): error CS0246: The type or namespace name 'C10000' could not be found (are you missing a using directive or an assembly reference?) + // new C10000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C10000").WithArguments("C10000").WithLocation(10000, 13), + // (40000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(40000, 20) + }; + + var expected1 = new[] + { + // (4000,1): hidden CS8019: Unnecessary using directive. + // using N5; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N5;").WithLocation(4000, 1), + // (20000,13): error CS0246: The type or namespace name 'C20000' could not be found (are you missing a using directive or an assembly reference?) + // new C20000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C20000").WithArguments("C20000").WithLocation(20000, 13), + // (50000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(50000, 20) + }; + + var expected2 = new[] + { + // (6000,1): hidden CS8019: Unnecessary using directive. + // using N8; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N8;").WithLocation(6000, 1), + // (30000,13): error CS0246: The type or namespace name 'C30000' could not be found (are you missing a using directive or an assembly reference?) + // new C30000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C30000").WithArguments("C30000").WithLocation(30000, 13), + // (60000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(60000, 20) + }; + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected1); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDiagnostics().Verify(expected2); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected0); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2 = CreateCompilation(new[] { source2, source3, source4 }, parseOptions: TestOptions.RegularPreview); + + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics(TextSpan.FromBounds(20, comp2.SyntaxTrees[0].Length - 1)).Verify( + // (10000,13): error CS0246: The type or namespace name 'C10000' could not be found (are you missing a using directive or an assembly reference?) + // new C10000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C10000").WithArguments("C10000").WithLocation(10000, 13), + // (40000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(40000, 20) + ); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics(TextSpan.FromBounds(20, comp2.SyntaxTrees[1].Length - 1)).Verify( + // (20000,13): error CS0246: The type or namespace name 'C20000' could not be found (are you missing a using directive or an assembly reference?) + // new C20000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C20000").WithArguments("C20000").WithLocation(20000, 13), + // (50000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(50000, 20) + ); + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDiagnostics(TextSpan.FromBounds(20, comp2.SyntaxTrees[2].Length - 1)).Verify( + // (30000,13): error CS0246: The type or namespace name 'C30000' could not be found (are you missing a using directive or an assembly reference?) + // new C30000(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C30000").WithArguments("C30000").WithLocation(30000, 13), + // (60000,20): error CS0246: The type or namespace name 'C0000' could not be found (are you missing a using directive or an assembly reference?) + // partial class C0 : C0000 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "C0000").WithArguments("C0000").WithLocation(60000, 20) + ); + + Assert.Empty(comp2.UsageOfUsingsRecordedInTrees); + + comp2 = CreateCompilation(new[] { source2, source3, source4 }, parseOptions: TestOptions.RegularPreview); + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected1); + AssertEx.SetEqual(new[] { comp2.SyntaxTrees[1] }, comp2.UsageOfUsingsRecordedInTrees); + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected1); + AssertEx.SetEqual(new[] { comp2.SyntaxTrees[1] }, comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDiagnostics().Verify(expected2); + AssertEx.SetEqual(new[] { comp2.SyntaxTrees[1], comp2.SyntaxTrees[2] }, comp2.UsageOfUsingsRecordedInTrees); + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDiagnostics().Verify(expected2); + AssertEx.SetEqual(new[] { comp2.SyntaxTrees[1], comp2.SyntaxTrees[2] }, comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected0); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected0); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2 = CreateCompilation(new[] { source2, source3, source4 }, parseOptions: TestOptions.RegularPreview); + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected1); + AssertEx.SetEqual(new[] { comp2.SyntaxTrees[1] }, comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected0); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDiagnostics().Verify(expected2); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2 = CreateCompilation(new[] { source2, source3, source4 }, parseOptions: TestOptions.RegularPreview); + + comp2.GetSemanticModel(comp2.SyntaxTrees[0]).GetDiagnostics().Verify(expected0); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[1]).GetDiagnostics().Verify(expected1); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + + comp2.GetSemanticModel(comp2.SyntaxTrees[2]).GetDiagnostics().Verify(expected2); + Assert.Null(comp2.UsageOfUsingsRecordedInTrees); + } + + [Fact] + public void QuickAttributeChecker_01() + { + var origLib_cs = @"public class C : System.Attribute { }"; + + var alias1 = @" +global using alias1 = System.Runtime.CompilerServices.TypeForwardedToAttribute; +"; + var alias2 = @" +using alias2 = System; +"; + var alias3 = @" +global using alias2 = System; +"; + var alias4 = @" +using alias1 = System.Runtime.CompilerServices.TypeForwardedToAttribute; +"; + var newLib_cs = @" +[assembly: RefersToLib] // to bind this, we'll need to find type C in 'lib' +[assembly: alias1(typeof(C))] // but C is forwarded via alias +"; + + var reference_cs = +@" +public class RefersToLibAttribute : C +{ + public RefersToLibAttribute() { } +} +"; + + var origLibComp = CreateCompilation(origLib_cs, assemblyName: "lib"); + origLibComp.VerifyDiagnostics(); + + var newComp = CreateCompilation(origLib_cs, assemblyName: "new"); + newComp.VerifyDiagnostics(); + + var compWithReferenceToLib = CreateCompilation(reference_cs, references: new[] { origLibComp.EmitToImageReference() }); + compWithReferenceToLib.VerifyDiagnostics(); + + MetadataReference newCompImageRef = newComp.EmitToImageReference(); + var newLibComp = CreateCompilation(alias1 + newLib_cs, + references: new[] { compWithReferenceToLib.EmitToImageReference(), newCompImageRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp.VerifyDiagnostics(); + + CompilationReference newCompRef = newComp.ToMetadataReference(); + var newLibComp2 = CreateCompilation(alias1 + newLib_cs, + references: new[] { compWithReferenceToLib.ToMetadataReference(), newCompRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp2.VerifyDiagnostics(); + + newLibComp = CreateCompilation(new[] { alias1, newLib_cs }, + references: new[] { compWithReferenceToLib.EmitToImageReference(), newCompImageRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp.VerifyDiagnostics(); + + newLibComp2 = CreateCompilation(new[] { alias1, newLib_cs }, + references: new[] { compWithReferenceToLib.ToMetadataReference(), newCompRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp2.VerifyDiagnostics(); + + newLibComp = CreateCompilation(alias1 + alias2 + newLib_cs, + references: new[] { compWithReferenceToLib.EmitToImageReference(), newCompImageRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp.VerifyDiagnostics( + // (4,1): hidden CS8019: Unnecessary using directive. + // using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias2 = System;").WithLocation(4, 1) + ); + + newLibComp2 = CreateCompilation(alias1 + alias2 + newLib_cs, + references: new[] { compWithReferenceToLib.ToMetadataReference(), newCompRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp2.VerifyDiagnostics( + // (4,1): hidden CS8019: Unnecessary using directive. + // using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias2 = System;").WithLocation(4, 1) + ); + + newLibComp = CreateCompilation(new[] { alias1, alias2 + newLib_cs }, + references: new[] { compWithReferenceToLib.EmitToImageReference(), newCompImageRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias2 = System;").WithLocation(2, 1) + ); + + newLibComp2 = CreateCompilation(new[] { alias1, alias2 + newLib_cs }, + references: new[] { compWithReferenceToLib.ToMetadataReference(), newCompRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp2.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias2 = System;").WithLocation(2, 1) + ); + + newLibComp = CreateCompilation(alias3 + alias4 + newLib_cs, + references: new[] { compWithReferenceToLib.EmitToImageReference(), newCompImageRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // global using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias2 = System;").WithLocation(2, 1) + ); + + newLibComp2 = CreateCompilation(alias3 + alias4 + newLib_cs, + references: new[] { compWithReferenceToLib.ToMetadataReference(), newCompRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp2.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // global using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias2 = System;").WithLocation(2, 1) + ); + + newLibComp = CreateCompilation(new[] { alias3, alias4 + newLib_cs }, + references: new[] { compWithReferenceToLib.EmitToImageReference(), newCompImageRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // global using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias2 = System;").WithLocation(2, 1) + ); + + newLibComp2 = CreateCompilation(new[] { alias3, alias4 + newLib_cs }, + references: new[] { compWithReferenceToLib.ToMetadataReference(), newCompRef }, assemblyName: "lib", parseOptions: TestOptions.RegularPreview); + newLibComp2.VerifyDiagnostics( + // (2,1): hidden CS8019: Unnecessary using directive. + // global using alias2 = System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using alias2 = System;").WithLocation(2, 1) + ); + } + + [Fact] + public void ImportsInPdb_01() + { + var ext = @" +public class Extern +{ +} +"; + var extComp = CreateCompilation(ext, assemblyName: "Extern"); + var extCompRef = extComp.ToMetadataReference().WithAliases(new[] { "ext" }); + + var externAlias = @" +extern alias ext; +"; + var globalUsings1 = @" +global using alias1 = C1; +"; + var globalUsings2 = @" +global using alias2 = C1; +"; + var usings = @" +using alias3 = C1; +"; + + var filler = @" + +"; + + var source = @" +class C1 +{ + static void Main() + { + } +} +"; + + var expected1 = @" + + + + + + + + + + + + + + + + + + + + + + + + +"; + var comp = CreateCompilation(externAlias + globalUsings1 + globalUsings2 + usings + source, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + comp = CreateCompilation(new[] { externAlias + globalUsings1 + filler + usings + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + comp = CreateCompilation(new[] { externAlias + usings + filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + comp = CreateCompilation(new[] { externAlias + usings + filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + var expected2 = @" + + + + + + + + + + + + + + + + + + + + + + + +"; + comp = CreateCompilation(externAlias + globalUsings1 + globalUsings2 + source, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + comp = CreateCompilation(new[] { externAlias + globalUsings1 + filler + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + comp = CreateCompilation(new[] { externAlias + filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + comp = CreateCompilation(new[] { externAlias + filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + var expected3 = @" + + + + + + + + + + + + + + + + + + + + + + +"; + comp = CreateCompilation(globalUsings1 + globalUsings2 + usings + source, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + comp = CreateCompilation(new[] { globalUsings1 + filler + usings + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + comp = CreateCompilation(new[] { usings + filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + comp = CreateCompilation(new[] { usings + filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + var expected4 = @" + + + + + + + + + + + + + + + + + + + + + +"; + comp = CreateCompilation(globalUsings1 + globalUsings2 + source, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(new[] { globalUsings1 + filler + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(new[] { filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(new[] { filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", @" + + + + + + + + + + + + + + + + + +"); + } + + [Fact] + public void ImportsInPdb_02() + { + var ext = @" +public class Extern +{ +} +"; + var extComp = CreateCompilation(ext, assemblyName: "Extern"); + var extCompRef = extComp.ToMetadataReference().WithAliases(new[] { "ext" }); + + var externAlias = @" +extern alias ext; +"; + var globalUsings1 = @" +global using static C1; +"; + var globalUsings2 = @" +global using NS; +"; + var usings = @" +using static NS.C2; +"; + + var filler = @" + +"; + + var source = @" +class C1 +{ + static void Main() + { + } +} + +namespace NS +{ + class C2 {} +} +"; + + var expected1 = @" + + + + + + + + + + + + + + + + + + + + + + + + +"; + var comp = CreateCompilation(externAlias + globalUsings1 + globalUsings2 + usings + source, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + comp = CreateCompilation(new[] { externAlias + globalUsings1 + filler + usings + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + comp = CreateCompilation(new[] { externAlias + usings + filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + comp = CreateCompilation(new[] { externAlias + usings + filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected1); + + var expected2 = @" + + + + + + + + + + + + + + + + + + + + + + + +"; + comp = CreateCompilation(externAlias + globalUsings1 + globalUsings2 + source, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + comp = CreateCompilation(new[] { externAlias + globalUsings1 + filler + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + comp = CreateCompilation(new[] { externAlias + filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + comp = CreateCompilation(new[] { externAlias + filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview, references: new[] { extCompRef }); + comp.VerifyPdb("C1.Main", expected2); + + var expected3 = @" + + + + + + + + + + + + + + + + + + + + + + +"; + comp = CreateCompilation(globalUsings1 + globalUsings2 + usings + source, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + comp = CreateCompilation(new[] { globalUsings1 + filler + usings + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + comp = CreateCompilation(new[] { usings + filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + comp = CreateCompilation(new[] { usings + filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected3); + + var expected4 = @" + + + + + + + + + + + + + + + + + + + + + +"; + comp = CreateCompilation(globalUsings1 + globalUsings2 + source, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(new[] { globalUsings1 + filler + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(new[] { filler + filler + source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + + comp = CreateCompilation(new[] { filler + filler + source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.VerifyPdb("C1.Main", expected4); + } + + [Fact] + public void GetDeclaredSymbol_01() + { + var externAlias = @" +extern alias alias1; +extern alias alias1; +"; + var globalUsings1 = @" +global using alias1 = C1; +"; + var globalUsings2 = @" +global using alias1 = C2; +"; + var usings = @" +using alias1 = C3; +using alias1 = C4; +"; + + var source = @" +class C1 {} +class C2 {} +class C3 {} +class C4 {} +"; + + var comp = CreateCompilation(new[] { externAlias + globalUsings1 + globalUsings2 + usings, source }, parseOptions: TestOptions.RegularPreview); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + var aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(4, aliases.Length); + + var ext1 = model.GetDeclaredSymbol(ext[0]); + var ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C3;", aliases[2].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[2]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[3].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[3]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { externAlias + globalUsings1 + usings, globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(3, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C3;", aliases[1].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[2].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[2]).Target.ToTestDisplayString()); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(1, aliases.Length); + + Assert.Equal("global using alias1 = C2;", aliases[0].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { externAlias + usings, globalUsings1 + globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + Assert.Equal("using alias1 = C3;", aliases[0].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[1].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + // ------------------------------------- + + comp = CreateCompilation(new[] { externAlias + globalUsings1 + globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { externAlias + globalUsings1, globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(1, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(1, aliases.Length); + + Assert.Equal("global using alias1 = C2;", aliases[0].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { externAlias, globalUsings1 + globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + // ------------------------------------- + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + usings, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(4, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C3;", aliases[2].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[2]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[3].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[3]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { globalUsings1 + usings, globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(3, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C3;", aliases[1].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[2].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[2]).Target.ToTestDisplayString()); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(1, aliases.Length); + + Assert.Equal("global using alias1 = C2;", aliases[0].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { usings, globalUsings1 + globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + Assert.Equal("using alias1 = C3;", aliases[0].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[1].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + // ------------------------------------- + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("global using alias1 = C2;", aliases[1].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + comp = CreateCompilation(new[] { globalUsings1, globalUsings2, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(1, aliases.Length); + + Assert.Equal("global using alias1 = C1;", aliases[0].ToString()); + Assert.Equal("C1", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + tree = comp.SyntaxTrees[1]; + model = comp.GetSemanticModel(tree); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(1, aliases.Length); + + Assert.Equal("global using alias1 = C2;", aliases[0].ToString()); + Assert.Equal("C2", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + // ------------------------------------- + + comp = CreateCompilation(new[] { externAlias + usings, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + Assert.Equal("using alias1 = C3;", aliases[0].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[1].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + + // ------------------------------------- + + comp = CreateCompilation(new[] { externAlias, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, aliases.Length); + + ext1 = model.GetDeclaredSymbol(ext[0]); + ext2 = model.GetDeclaredSymbol(ext[1]); + Assert.NotNull(ext1); + Assert.NotNull(ext2); + Assert.NotEqual(ext1, ext2); + + // ------------------------------------- + + comp = CreateCompilation(new[] { usings, source }, parseOptions: TestOptions.RegularPreview); + tree = comp.SyntaxTrees[0]; + model = comp.GetSemanticModel(tree); + ext = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(0, ext.Length); + aliases = tree.GetRoot().DescendantNodes().OfType().ToArray(); + Assert.Equal(2, aliases.Length); + + Assert.Equal("using alias1 = C3;", aliases[0].ToString()); + Assert.Equal("C3", model.GetDeclaredSymbol(aliases[0]).Target.ToTestDisplayString()); + + Assert.Equal("using alias1 = C4;", aliases[1].ToString()); + Assert.Equal("C4", model.GetDeclaredSymbol(aliases[1]).Target.ToTestDisplayString()); + } + + [Fact] + public void LookupAmbiguityInAliases_01() + { + var externAlias1 = @" +#line 1000 +extern alias alias1; +"; + var externAlias2 = @" +#line 2000 +extern alias alias1; +"; + var globalUsings1 = @" +#line 3000 +global using alias1 = C1; +"; + var globalUsings2 = @" +#line 4000 +global using alias1 = C2; +"; + var usings1 = @" +#line 5000 +using alias1 = C3; +"; + var usings2 = @" +#line 6000 +using alias1 = C4; +"; + + var source = @" +using NS; + +class Test +{ + void M() + { +#line 7000 + new alias1(); + } +} + +class C1 {} +class C2 {} +class C3 {} +class C4 {} + +namespace NS +{ + class alias1 {} +} +"; + + var comp = CreateCompilation(new[] { externAlias1 + globalUsings1 + source }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (7000,13): error CS0104: 'alias1' is an ambiguous reference between 'C1' and '' + // new alias1(); + Diagnostic(ErrorCode.ERR_AmbigContext, "alias1").WithArguments("alias1", "C1", "").WithLocation(7000, 13) + }; + + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.ERR_DuplicateAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected1); + + comp = CreateCompilation(new[] { externAlias1 + source, globalUsings1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.ERR_DuplicateAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected1); + + comp = CreateCompilation(new[] { externAlias1 + usings1 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.ERR_DuplicateAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify( + // (7000,13): error CS0104: 'alias1' is an ambiguous reference between 'C3' and '' + // new alias1(); + Diagnostic(ErrorCode.ERR_AmbigContext, "alias1").WithArguments("alias1", "C3", "").WithLocation(7000, 13) + ); + + var expected2 = new[] + { + // (5000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C3; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(5000, 7) + }; + + comp = CreateCompilation(new[] { globalUsings1 + usings1 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected2); + var tree = comp.SyntaxTrees[0]; + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = comp.GetSemanticModel(tree); + Assert.Equal("C1", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C1", model.GetAliasInfo(node.Type).ToTestDisplayString()); + + comp = CreateCompilation(new[] { usings1 + source, globalUsings1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected2); + tree = comp.SyntaxTrees[0]; + node = tree.GetRoot().DescendantNodes().OfType().Single(); + model = comp.GetSemanticModel(tree); + Assert.Equal("C1", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C1", model.GetAliasInfo(node.Type).ToTestDisplayString()); + + comp = CreateCompilation(new[] { externAlias1 + globalUsings1 + usings1 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.ERR_DuplicateAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected1); + + comp = CreateCompilation(new[] { externAlias1 + usings1 + source, globalUsings1 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.ERR_DuplicateAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected1); + + comp = CreateCompilation(new[] { externAlias1 + externAlias2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.ERR_DuplicateAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify( + // (7000,13): error CS0104: 'alias1' is an ambiguous reference between '' and '' + // new alias1(); + Diagnostic(ErrorCode.ERR_AmbigContext, "alias1").WithArguments("alias1", "", "").WithLocation(7000, 13) + ); + + var expected3 = new[] + { + // (4000,14): error CS1537: The using alias 'alias1' appeared previously in this namespace + // global using alias1 = C2; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(4000, 14) + }; + + comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected3); + tree = comp.SyntaxTrees[0]; + node = tree.GetRoot().DescendantNodes().OfType().Single(); + model = comp.GetSemanticModel(tree); + Assert.Equal("C1", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C1", model.GetAliasInfo(node.Type).ToTestDisplayString()); + + comp = CreateCompilation(new[] { globalUsings1 + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected3); + tree = comp.SyntaxTrees[0]; + node = tree.GetRoot().DescendantNodes().OfType().Single(); + model = comp.GetSemanticModel(tree); + Assert.Equal("C1", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C1", model.GetAliasInfo(node.Type).ToTestDisplayString()); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected3); + tree = comp.SyntaxTrees[0]; + node = tree.GetRoot().DescendantNodes().OfType().Single(); + model = comp.GetSemanticModel(tree); + Assert.Equal("C1", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C1", model.GetAliasInfo(node.Type).ToTestDisplayString()); + + comp = CreateCompilation(new[] { source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify(expected3); + tree = comp.SyntaxTrees[0]; + node = tree.GetRoot().DescendantNodes().OfType().Single(); + model = comp.GetSemanticModel(tree); + Assert.Equal("C1", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C1", model.GetAliasInfo(node.Type).ToTestDisplayString()); + + comp = CreateCompilation(new[] { usings1 + usings2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not ((int)ErrorCode.ERR_BadExternAlias or (int)ErrorCode.HDN_UnusedUsingDirective)).Verify( + // (6000,7): error CS1537: The using alias 'alias1' appeared previously in this namespace + // using alias1 = C4; + Diagnostic(ErrorCode.ERR_DuplicateAlias, "alias1").WithArguments("alias1").WithLocation(6000, 7) + ); + tree = comp.SyntaxTrees[0]; + node = tree.GetRoot().DescendantNodes().OfType().Single(); + model = comp.GetSemanticModel(tree); + Assert.Equal("C3", model.GetTypeInfo(node).Type.ToTestDisplayString()); + Assert.Equal("alias1=C3", model.GetAliasInfo(node.Type).ToTestDisplayString()); + } + + [Fact] + public void LookupAmbiguityInUsedNamespacesOrTypes_01() + { + var globalUsings1 = @" +global using NS1; +"; + var globalUsings2 = @" +global using static C2; +"; + var usings1 = @" +using NS3; +"; + var usings2 = @" +using static C4; +"; + + var source = @" +class Test +{ + void M() + { +#line 7000 + new C5(); + } +} + +class C2 +{ + public class C5 {} +} +class C4 +{ + public class C5 {} +} + +namespace NS1 +{ + public class C5 {} +} +namespace NS3 +{ + public class C5 {} +} +"; + + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (7000,13): error CS0104: 'C5' is an ambiguous reference between 'C2.C5' and 'NS1.C5' + // new C5(); + Diagnostic(ErrorCode.ERR_AmbigContext, "C5").WithArguments("C5", "C2.C5", "NS1.C5").WithLocation(7000, 13) + }; + + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { globalUsings1 + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { globalUsings1 + usings2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (7000,13): error CS0104: 'C5' is an ambiguous reference between 'C4.C5' and 'NS1.C5' + // new C5(); + Diagnostic(ErrorCode.ERR_AmbigContext, "C5").WithArguments("C5", "C4.C5", "NS1.C5").WithLocation(7000, 13) + ); + + comp = CreateCompilation(new[] { usings1 + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (7000,13): error CS0104: 'C5' is an ambiguous reference between 'C2.C5' and 'NS3.C5' + // new C5(); + Diagnostic(ErrorCode.ERR_AmbigContext, "C5").WithArguments("C5", "C2.C5", "NS3.C5").WithLocation(7000, 13) + ); + + comp = CreateCompilation(new[] { usings1 + usings2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (7000,13): error CS0104: 'C5' is an ambiguous reference between 'C4.C5' and 'NS3.C5' + // new C5(); + Diagnostic(ErrorCode.ERR_AmbigContext, "C5").WithArguments("C5", "C4.C5", "NS3.C5").WithLocation(7000, 13) + ); + } + + [Fact] + public void LookupAmbiguityInUsedNamespacesOrTypes_02() + { + var globalUsings1 = @" +global using NS1; +"; + var globalUsings2 = @" +global using static NS.C2; +"; + var usings1 = @" +using NS3; +"; + var usings2 = @" +using static NS.C4; +"; + + var source = @" +class Test +{ + void M() + { +#line 7000 + 1.M5(); + } +} + +namespace NS +{ + static class C2 + { + public static void M5(this int x) {} + } + static class C4 + { + public static void M5(this int x) {} + } +} + +namespace NS1 +{ + public static class C5 + { + public static void M5(this int x) {} + } +} +namespace NS3 +{ + public static class C5 + { + public static void M5(this int x) {} + } +} +"; + + var comp = CreateCompilation(new[] { globalUsings1 + globalUsings2 + source }, parseOptions: TestOptions.RegularPreview); + + var expected1 = new[] + { + // (7000,11): error CS0121: The call is ambiguous between the following methods or properties: 'NS1.C5.M5(int)' and 'NS.C2.M5(int)' + // 1.M5(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M5").WithArguments("NS1.C5.M5(int)", "NS.C2.M5(int)").WithLocation(7000, 11) + }; + + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { globalUsings1 + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { source, globalUsings1 + globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { source, globalUsings1, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + comp = CreateCompilation(new[] { globalUsings1 + usings2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (7000,11): error CS0121: The call is ambiguous between the following methods or properties: 'NS1.C5.M5(int)' and 'NS.C4.M5(int)' + // 1.M5(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M5").WithArguments("NS1.C5.M5(int)", "NS.C4.M5(int)").WithLocation(7000, 11) + ); + + comp = CreateCompilation(new[] { usings1 + source, globalUsings2 }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (7000,11): error CS0121: The call is ambiguous between the following methods or properties: 'NS.C2.M5(int)' and 'NS3.C5.M5(int)' + // 1.M5(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M5").WithArguments("NS.C2.M5(int)", "NS3.C5.M5(int)").WithLocation(7000, 11) + ); + + comp = CreateCompilation(new[] { usings1 + usings2 + source }, parseOptions: TestOptions.RegularPreview); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (7000,11): error CS0121: The call is ambiguous between the following methods or properties: 'NS3.C5.M5(int)' and 'NS.C4.M5(int)' + // 1.M5(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M5").WithArguments("NS3.C5.M5(int)", "NS.C4.M5(int)").WithLocation(7000, 11) + ); + } + + [Fact] + public void AliasHasPriority_01() + { + var source1 = @" +public class C1 +{ +} +"; + + var comp1 = CreateCompilation(source1); + var comp1Ref = comp1.ToMetadataReference().WithAliases(new[] { "A" }); + + var externAlias = @" +extern alias A; +"; + var globalUsing1 = @" +#line 1000 +global using NS; +"; + var globalUsing2 = @" +#line 2000 +global using static C2; +"; + var using1 = @" +#line 3000 +using NS; +"; + var using2 = @" +#line 4000 +using static C2; +"; + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(new A.C1()); + } +} + +namespace NS +{ + public class A + { + public class C1 {} + } +} + +class C2 +{ + public class A + { + public class C1 {} + } +} +"; + { + var comp2 = CreateCompilation(externAlias + globalUsing1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + var expected1 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using NS; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using NS;").WithLocation(1000, 1) + }; + + CompileAndVerify(comp2, expectedOutput: @"C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { externAlias + source2, globalUsing1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(globalUsing1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"NS.A+C1").VerifyDiagnostics(); + } + { + var comp2 = CreateCompilation(externAlias + globalUsing2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + + var expected1 = new[] + { + // (2000,1): hidden CS8019: Unnecessary using directive. + // global using static C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C2;").WithLocation(2000, 1) + }; + + CompileAndVerify(comp2, expectedOutput: @"C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { externAlias + source2, globalUsing2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(globalUsing2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"C2+A+C1").VerifyDiagnostics(); + } + { + var comp2 = CreateCompilation(externAlias + using1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"C1").VerifyDiagnostics( + // (3000,1): hidden CS8019: Unnecessary using directive. + // using NS; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using NS;").WithLocation(3000, 1) + ); + + comp2 = CreateCompilation(using1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"NS.A+C1").VerifyDiagnostics(); + } + { + var comp2 = CreateCompilation(externAlias + using2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"C1").VerifyDiagnostics( + // (4000,1): hidden CS8019: Unnecessary using directive. + // using static C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C2;").WithLocation(4000, 1) + ); + + comp2 = CreateCompilation(using2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe, references: new[] { comp1Ref }); + CompileAndVerify(comp2, expectedOutput: @"C2+A+C1").VerifyDiagnostics(); + } + } + + [Fact] + public void AliasHasPriority_02() + { + var globalAlias = @" +global using A = NS2; +"; + var regularAlias = @" +using A = NS2; +"; + var globalUsing1 = @" +#line 1000 +global using NS; +"; + var globalUsing2 = @" +#line 2000 +global using static C2; +"; + var using1 = @" +#line 3000 +using NS; +"; + var using2 = @" +#line 4000 +using static C2; +"; + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(new A.C1()); + } +} + +namespace NS +{ + public class A + { + public class C1 {} + } +} + +class C2 +{ + public class A + { + public class C1 {} + } +} + +namespace NS2 +{ + public class C1 {} +} +"; + { + var comp2 = CreateCompilation(globalAlias + globalUsing1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + + var expected1 = new[] + { + // (1000,1): hidden CS8019: Unnecessary using directive. + // global using NS; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using NS;").WithLocation(1000, 1) + }; + + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { globalAlias + source2, globalUsing1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { source2, globalAlias + globalUsing1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { source2, globalAlias, globalUsing1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { source2, globalUsing1, globalAlias }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(globalUsing1 + regularAlias + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { regularAlias + source2, globalUsing1 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(globalUsing1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS.A+C1").VerifyDiagnostics(); + } + { + var comp2 = CreateCompilation(globalAlias + globalUsing2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + + var expected1 = new[] + { + // (2000,1): hidden CS8019: Unnecessary using directive. + // global using static C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "global using static C2;").WithLocation(2000, 1) + }; + + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { globalAlias + source2, globalUsing2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { source2, globalAlias + globalUsing2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { source2, globalAlias, globalUsing2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { source2, globalUsing2, globalAlias }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(globalUsing2 + regularAlias + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { regularAlias + source2, globalUsing2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(globalUsing2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"C2+A+C1").VerifyDiagnostics(); + } + { + var comp2 = CreateCompilation(globalAlias + using1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + + var expected1 = new[] + { + // (3000,1): hidden CS8019: Unnecessary using directive. + // using NS; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using NS;").WithLocation(3000, 1) + }; + + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { globalAlias, using1 + source2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(regularAlias + using1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(using1 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS.A+C1").VerifyDiagnostics(); + } + { + var comp2 = CreateCompilation(globalAlias + using2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + + var expected1 = new[] + { + // (4000,1): hidden CS8019: Unnecessary using directive. + // using static C2; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C2;").WithLocation(4000, 1) + }; + + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(new[] { globalAlias, using2 + source2 }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(regularAlias + using2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"NS2.C1").VerifyDiagnostics(expected1); + + comp2 = CreateCompilation(using2 + source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @"C2+A+C1").VerifyDiagnostics(); + } + } + + [Fact] + public void ErrorRecoveryInNamespace_01() + { + var source = @" +namespace ns +{ + global using NS2; + global using static C4; + global using A2 = C5; + + using NS1; + using static C2; + using A1 = C3; + + class Test1 + { + void M() + { +#line 1000 + new NS1C1(); + M2(); + A1.M3(); + + new NS2C2(); + M4(); + A2.M5(); + } + } +} + +namespace ns +{ + class Test2 + { + void M() + { +#line 2000 + new NS1C1(); + M2(); + A1.M3(); + + new NS2C2(); + M4(); + A2.M5(); + } + } +} + +namespace NS1 +{ + class NS1C1 {} +} + +namespace NS2 +{ + class NS2C2 {} +} + +class C2 +{ + public static void M2() {} +} + +class C3 +{ + public static void M3() {} +} + +class C4 +{ + public static void M4() {} +} + +class C5 +{ + public static void M5() {} +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,5): error CS9001: A global using directive cannot be used in a namespace declaration. + // global using NS2; + Diagnostic(ErrorCode.ERR_GlobalUsingInNamespace, "global").WithLocation(4, 5), + // (2000,17): error CS0246: The type or namespace name 'NS1C1' could not be found (are you missing a using directive or an assembly reference?) + // new NS1C1(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "NS1C1").WithArguments("NS1C1").WithLocation(2000, 17), + // (2001,13): error CS0103: The name 'M2' does not exist in the current context + // M2(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "M2").WithArguments("M2").WithLocation(2001, 13), + // (2002,13): error CS0103: The name 'A1' does not exist in the current context + // A1.M3(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "A1").WithArguments("A1").WithLocation(2002, 13), + // (2004,17): error CS0246: The type or namespace name 'NS2C2' could not be found (are you missing a using directive or an assembly reference?) + // new NS2C2(); + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "NS2C2").WithArguments("NS2C2").WithLocation(2004, 17), + // (2005,13): error CS0103: The name 'M4' does not exist in the current context + // M4(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "M4").WithArguments("M4").WithLocation(2005, 13), + // (2006,13): error CS0103: The name 'A2' does not exist in the current context + // A2.M5(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "A2").WithArguments("A2").WithLocation(2006, 13) + ); + } + + [Fact] + public void InvalidUsingTarget_01() + { + var globalUsing1 = @" +#line 1000 +global using A1 = C; +"; + var globalUsing2 = @" +#line 2000 +global using static C; +"; + var regularUsings = @" +#line 3000 +using A2 = C; +using static C; +"; + var source = @" +class C where T : class {} +"; + var expected1 = new[] + { + // (1000,14): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // global using A1 = C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "A1").WithArguments("C", "T", "int").WithLocation(1000, 14), + // (2000,21): error CS0452: The type 'byte' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // global using static C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "C").WithArguments("C", "T", "byte").WithLocation(2000, 21), + // (3000,7): error CS0452: The type 'long' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // using A2 = C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "A2").WithArguments("C", "T", "long").WithLocation(3000, 7), + // (3001,14): error CS0452: The type 'short' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // using static C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "C").WithArguments("C", "T", "short").WithLocation(3001, 14) + }; + + CreateCompilation(new[] { globalUsing1 + globalUsing2 + regularUsings + source }, parseOptions: TestOptions.RegularPreview). + GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + CreateCompilation(new[] { globalUsing1 + regularUsings + source, globalUsing2 }, parseOptions: TestOptions.RegularPreview). + GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + CreateCompilation(new[] { regularUsings + source, globalUsing1, globalUsing2 }, parseOptions: TestOptions.RegularPreview). + GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected1); + + var expected2 = new[] + { + // (1000,14): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // global using A1 = C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "A1").WithArguments("C", "T", "int").WithLocation(1000, 14), + // (2000,21): error CS0452: The type 'byte' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // global using static C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "C").WithArguments("C", "T", "byte").WithLocation(2000, 21), + }; + + CreateCompilation(new[] { globalUsing1 + globalUsing2 + source }, parseOptions: TestOptions.RegularPreview). + GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + CreateCompilation(new[] { globalUsing1 + source, globalUsing2 }, parseOptions: TestOptions.RegularPreview). + GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify(expected2); + + CreateCompilation(new[] { regularUsings + source }, parseOptions: TestOptions.RegularPreview). + GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.HDN_UnusedUsingDirective).Verify( + // (3000,7): error CS0452: The type 'long' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // using A2 = C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "A2").WithArguments("C", "T", "long").WithLocation(3000, 7), + // (3001,14): error CS0452: The type 'short' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C' + // using static C; + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "C").WithArguments("C", "T", "short").WithLocation(3001, 14) + ); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ImportsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ImportsTests.cs index 9e61e28f4477b..f437cd21dff54 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ImportsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ImportsTests.cs @@ -110,24 +110,22 @@ public void ConcatCollidingExternAliases() }); var tree = comp.SyntaxTrees.Single(); - var binder = comp.GetBinderFactory(tree).GetImportsBinder((CSharpSyntaxNode)tree.GetRoot(), inUsing: false); - var scratchImports = binder.GetImports(basesBeingResolved: null); + var binder = comp.GetBinderFactory(tree).GetInNamespaceBinder((CSharpSyntaxNode)tree.GetRoot()); + var scratchImports = binder.ImportChain.Imports; var scratchExternAliases = scratchImports.ExternAliases; Assert.Equal(2, scratchExternAliases.Length); var externAlias1 = scratchExternAliases[0]; var externAlias2 = new AliasAndExternAliasDirective( - AliasSymbol.CreateCustomDebugInfoAlias(scratchExternAliases[1].Alias.Target, externAlias1.ExternAliasDirective.Identifier, binder), - externAlias1.ExternAliasDirective); + AliasSymbol.CreateCustomDebugInfoAlias(scratchExternAliases[1].Alias.Target, externAlias1.ExternAliasDirective.Identifier, binder.ContainingMemberOrLambda, isExtern: true), + externAlias1.ExternAliasDirective, skipInLookup: false); - var imports1 = Imports.FromCustomDebugInfo( - comp, + var imports1 = Imports.Create( ImmutableDictionary.Empty, ImmutableArray.Empty, ImmutableArray.Create(externAlias1)); - var imports2 = Imports.FromCustomDebugInfo( - comp, + var imports2 = Imports.Create( ImmutableDictionary.Empty, ImmutableArray.Empty, ImmutableArray.Create(externAlias2)); @@ -149,8 +147,8 @@ private static Imports[] GetImports(params string[] sources) comp.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); var factories = trees.Select(tree => comp.GetBinderFactory(tree)); - var binders = factories.Select(factory => factory.GetImportsBinder((CSharpSyntaxNode)factory.SyntaxTree.GetRoot(), inUsing: false)); - var imports = binders.Select(binder => binder.GetImports(basesBeingResolved: null)); + var binders = factories.Select(factory => factory.GetInNamespaceBinder((CSharpSyntaxNode)factory.SyntaxTree.GetRoot())); + var imports = binders.Select(binder => binder.ImportChain.Imports); Assert.DoesNotContain(Imports.Empty, imports); return imports.ToArray(); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs index 79a04499ac4d5..53ecf36fa7afb 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs @@ -695,7 +695,7 @@ public void M(int[] arr) { comp.VerifyDiagnostics( // (11,17): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray' // var x = arr[0..2]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr[0..2]").WithArguments("System.Runtime.CompilerServices.RuntimeHelpers", "GetSubArray").WithLocation(11, 17)); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "0..2").WithArguments("System.Runtime.CompilerServices.RuntimeHelpers", "GetSubArray").WithLocation(11, 21)); } [Fact] @@ -720,7 +720,7 @@ public Index(int value, bool fromEnd) { } comp.VerifyDiagnostics( // (6,17): error CS0656: Missing compiler required member 'System.Index.GetOffset' // var x = arr[^2]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr[^2]").WithArguments("System.Index", "GetOffset").WithLocation(6, 17)); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "^2").WithArguments("System.Index", "GetOffset").WithLocation(6, 21)); comp = CreateCompilation(source + @" namespace System diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs index 0c28203a20a49..46544f147fa73 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs @@ -4937,7 +4937,8 @@ public class Derived : Base public override void Method(short s, int i) { } } "; - CSharpCompilation comp = CreateCompilation(text); + CSharpCompilation comp = CreateCompilation(text, targetFramework: TargetFramework.StandardLatest); + Assert.Equal(RuntimeUtilities.IsCoreClrRuntime, comp.Assembly.RuntimeSupportsCovariantReturnsOfClasses); if (comp.Assembly.RuntimeSupportsDefaultInterfaceImplementation) { comp.VerifyDiagnostics( @@ -4995,7 +4996,8 @@ class Derived : Base public override void Method(int @in, ref int @ref) { } } "; - var compilation = CreateCompilation(text); + var compilation = CreateCompilation(text, targetFramework: TargetFramework.StandardLatest); + Assert.Equal(RuntimeUtilities.IsCoreClrRuntime, compilation.Assembly.RuntimeSupportsCovariantReturnsOfClasses); if (compilation.Assembly.RuntimeSupportsCovariantReturnsOfClasses) { // We no longer report a runtime ambiguous override because the compiler diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaDiscardParametersTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaDiscardParametersTests.cs index 2d82d693ff6c9..76a4850e08ab8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaDiscardParametersTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaDiscardParametersTests.cs @@ -4,12 +4,11 @@ #nullable disable -using System.Diagnostics; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests @@ -351,6 +350,52 @@ public static void Main() ); } + [Fact] + public void DiscardParameters_WithAttribute() + { + var source = +@"using System; +class AAttribute : Attribute { } +class C +{ + static void Main() + { + Action a; + a = ([A] _, y) => { }; + a = (object x, [A] object _) => { }; + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (8,14): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = ([A] _, y) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[A]").WithArguments("lambda attributes").WithLocation(8, 14), + // (9,24): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = (object x, [A] object _) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[A]").WithArguments("lambda attributes").WithLocation(9, 24)); + verifyAttributes(comp); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + verifyAttributes(comp); + + static void verifyAttributes(CSharpCompilation comp) + { + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType(); + var lambdas = exprs.Select(e => (IMethodSymbol)model.GetSymbolInfo(e).Symbol).ToArray(); + Assert.Equal(2, lambdas.Length); + Assert.Equal(new[] { "AAttribute" }, getParameterAttributes(lambdas[0].Parameters[0])); + Assert.Equal(new string[0], getParameterAttributes(lambdas[0].Parameters[1])); + Assert.Equal(new string[0], getParameterAttributes(lambdas[1].Parameters[0])); + Assert.Equal(new[] { "AAttribute" }, getParameterAttributes(lambdas[1].Parameters[1])); + } + + static ImmutableArray getParameterAttributes(IParameterSymbol parameter) => parameter.GetAttributes().SelectAsArray(a => a.ToString()); + } + [Fact] public void DiscardParameters_NotInScope() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs index b8b457c0e01bb..c73417b967423 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs @@ -5,6 +5,8 @@ #nullable disable using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text; @@ -592,11 +594,13 @@ static void Main() "; var csProject = CreateCompilation(csSource); - - var emitResult = csProject.Emit(Stream.Null); - Assert.False(emitResult.Success); - Assert.True(emitResult.Diagnostics.Any()); - // TODO: check error code + csProject.VerifyEmitDiagnostics( + // (8,22): warning CS0219: The variable 'message' is assigned but its value is never used + // const string message = "The parameter is obsolete"; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "message").WithArguments("message").WithLocation(8, 22), + // (9,35): error CS7014: Attributes are not valid in this context. + // Action a = delegate ([ObsoleteAttribute(message)] int x) { }; + Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[ObsoleteAttribute(message)]").WithLocation(9, 35)); } [WorkItem(540263, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540263")] @@ -654,10 +658,12 @@ static void Main(string[] args) }"; CreateCompilation(csSource).VerifyDiagnostics( - // (5,37): error CS1660: Cannot convert lambda expression to type 'string' because it is not a delegate type - Diagnostic(ErrorCode.ERR_AnonMethToNonDel, @"() => x").WithArguments("lambda expression", "string"), - // (8,55): error CS0103: The name 'nulF' does not exist in the current context - Diagnostic(ErrorCode.ERR_NameNotInContext, @"nulF").WithArguments("nulF")); + // (5,37): error CS1660: Cannot convert lambda expression to type 'string' because it is not a delegate type + // public Program(string x) : this(() => x) { } + Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "() => x").WithArguments("lambda expression", "string").WithLocation(5, 37), + // (8,55): error CS0103: The name 'nulF' does not exist in the current context + // ((Action)(f => Console.WriteLine(f)))(nulF); + Diagnostic(ErrorCode.ERR_NameNotInContext, "nulF").WithArguments("nulF").WithLocation(8, 55)); } [WorkItem(541725, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/541725")] @@ -3480,5 +3486,852 @@ void F() comp = CreateCompilation(source); comp.VerifyDiagnostics(); } + + [Fact] + public void LambdaAttributes_01() + { + var sourceA = +@"using System; +class A : Attribute { } +class B : Attribute { } +partial class Program +{ + static Delegate D1() => (Action)([A] () => { }); + static Delegate D2(int x) => (Func)((int y, [A][B] int z) => x); + static Delegate D3() => (Action)(([A]_, y) => { }); + Delegate D4() => (Func)([return: A][B] () => GetHashCode()); +}"; + var sourceB = +@"using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +partial class Program +{ + static string GetAttributeString(object a) + { + return a.GetType().FullName; + } + static void Report(Delegate d) + { + var m = d.Method; + var forMethod = ToString(""method"", m.GetCustomAttributes(inherit: false)); + var forReturn = ToString(""return"", m.ReturnTypeCustomAttributes.GetCustomAttributes(inherit: false)); + var forParameters = ToString(""parameter"", m.GetParameters().SelectMany(p => p.GetCustomAttributes(inherit: false))); + Console.WriteLine(""{0}:{1}{2}{3}"", m.Name, forMethod, forReturn, forParameters); + } + static string ToString(string target, IEnumerable attributes) + { + var builder = new StringBuilder(); + foreach (var attribute in attributes) + builder.Append($"" [{target}: {attribute}]""); + return builder.ToString(); + } + static void Main() + { + Report(D1()); + Report(D2(0)); + Report(D3()); + Report(new Program().D4()); + } +}"; + + var comp = CreateCompilation(new[] { sourceA, sourceB }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType(); + var pairs = exprs.Select(e => (e, model.GetSymbolInfo(e).Symbol)).ToArray(); + var expectedAttributes = new[] + { + "[A] () => { }: [method: A]", + "(int y, [A][B] int z) => x: [parameter: A] [parameter: B]", + "([A]_, y) => { }: [parameter: A]", + "[return: A][B] () => GetHashCode(): [method: B] [return: A]", + }; + AssertEx.Equal(expectedAttributes, pairs.Select(p => getAttributesInternal(p.Item1, p.Item2))); + AssertEx.Equal(expectedAttributes, pairs.Select(p => getAttributesPublic(p.Item1, p.Item2))); + + CompileAndVerify(comp, expectedOutput: +@"b__0_0: [method: A] +b__0: [parameter: A] [parameter: B] +b__2_0: [parameter: A] +b__3_0: [method: System.Runtime.CompilerServices.CompilerGeneratedAttribute] [method: B] [return: A]"); + + static string getAttributesInternal(LambdaExpressionSyntax expr, ISymbol symbol) + { + var method = symbol.GetSymbol(); + return format(expr, method.GetAttributes(), method.GetReturnTypeAttributes(), method.Parameters.SelectMany(p => p.GetAttributes())); + } + + static string getAttributesPublic(LambdaExpressionSyntax expr, ISymbol symbol) + { + var method = (IMethodSymbol)symbol; + return format(expr, method.GetAttributes(), method.GetReturnTypeAttributes(), method.Parameters.SelectMany(p => p.GetAttributes())); + } + + static string format(LambdaExpressionSyntax expr, IEnumerable methodAttributes, IEnumerable returnAttributes, IEnumerable parameterAttributes) + { + var forMethod = toString("method", methodAttributes); + var forReturn = toString("return", returnAttributes); + var forParameters = toString("parameter", parameterAttributes); + return $"{expr}:{forMethod}{forReturn}{forParameters}"; + } + + static string toString(string target, IEnumerable attributes) + { + var builder = new StringBuilder(); + foreach (var attribute in attributes) + builder.Append($" [{target}: {attribute}]"); + return builder.ToString(); + } + } + + [Fact] + public void LambdaAttributes_02() + { + var source = +@"using System; +class AAttribute : Attribute { } +class BAttribute : Attribute { } +class C +{ + static void Main() + { + Action a; + a = [A, B] (x, y) => { }; + a = ([A] x, [B] y) => { }; + a = (object x, [A][B] object y) => { }; + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (9,13): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = [A, B] (x, y) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[A, B]").WithArguments("lambda attributes").WithLocation(9, 13), + // (10,14): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = ([A] x, [B] y) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[A]").WithArguments("lambda attributes").WithLocation(10, 14), + // (10,21): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = ([A] x, [B] y) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[B]").WithArguments("lambda attributes").WithLocation(10, 21), + // (11,24): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = (object x, [A][B] object y) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[A]").WithArguments("lambda attributes").WithLocation(11, 24), + // (11,27): error CS8652: The feature 'lambda attributes' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // a = (object x, [A][B] object y) => { }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[B]").WithArguments("lambda attributes").WithLocation(11, 27)); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + [Fact] + public void LambdaAttributes_03() + { + var source = +@"using System; +class AAttribute : Attribute { } +class BAttribute : Attribute { } +class C +{ + static void Main() + { + Action a = delegate (object x, [A][B] object y) { }; + Func f = [A][B] x => x; + } +}"; + + var expectedDiagnostics = new[] + { + // (8,56): error CS7014: Attributes are not valid in this context. + // Action a = delegate (object x, [A][B] object y) { }; + Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[A]").WithLocation(8, 56), + // (8,59): error CS7014: Attributes are not valid in this context. + // Action a = delegate (object x, [A][B] object y) { }; + Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[B]").WithLocation(8, 59), + // (9,34): error CS8916: Attributes on lambda expressions require a parenthesized parameter list. + // Func f = [A][B] x => x; + Diagnostic(ErrorCode.ERR_AttributesRequireParenthesizedLambdaExpression, "[A]").WithLocation(9, 34), + // (9,37): error CS8916: Attributes on lambda expressions require a parenthesized parameter list. + // Func f = [A][B] x => x; + Diagnostic(ErrorCode.ERR_AttributesRequireParenthesizedLambdaExpression, "[B]").WithLocation(9, 37) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void LambdaAttributes_04() + { + var sourceA = +@"namespace N1 +{ + class A1Attribute : System.Attribute { } +} +namespace N2 +{ + class A2Attribute : System.Attribute { } +}"; + var sourceB = +@"using N1; +using N2; +class Program +{ + static void Main() + { + System.Action a1 = [A1] () => { }; + System.Action a2 = ([A2] object obj) => { }; + } +}"; + var comp = CreateCompilation(new[] { sourceA, sourceB }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + [Fact] + public void LambdaAttributes_05() + { + var source = +@"class Program +{ + static void Main() + { + System.Action a1 = [A1] () => { }; + System.Func a2 = [return: A2] () => null; + System.Action a3 = ([A3] object obj) => { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (5,29): error CS0246: The type or namespace name 'A1Attribute' could not be found (are you missing a using directive or an assembly reference?) + // System.Action a1 = [A1] () => { }; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A1").WithArguments("A1Attribute").WithLocation(5, 29), + // (5,29): error CS0246: The type or namespace name 'A1' could not be found (are you missing a using directive or an assembly reference?) + // System.Action a1 = [A1] () => { }; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A1").WithArguments("A1").WithLocation(5, 29), + // (6,43): error CS0246: The type or namespace name 'A2Attribute' could not be found (are you missing a using directive or an assembly reference?) + // System.Func a2 = [return: A2] () => null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A2").WithArguments("A2Attribute").WithLocation(6, 43), + // (6,43): error CS0246: The type or namespace name 'A2' could not be found (are you missing a using directive or an assembly reference?) + // System.Func a2 = [return: A2] () => null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A2").WithArguments("A2").WithLocation(6, 43), + // (7,38): error CS0246: The type or namespace name 'A3Attribute' could not be found (are you missing a using directive or an assembly reference?) + // System.Action a3 = ([A3] object obj) => { }; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A3").WithArguments("A3Attribute").WithLocation(7, 38), + // (7,38): error CS0246: The type or namespace name 'A3' could not be found (are you missing a using directive or an assembly reference?) + // System.Action a3 = ([A3] object obj) => { }; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "A3").WithArguments("A3").WithLocation(7, 38)); + } + + [Fact] + public void LambdaAttributes_06() + { + var source = +@"using System; +class AAttribute : Attribute +{ + public AAttribute(Action a) { } +} +[A([B] () => { })] +class BAttribute : Attribute +{ +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (6,2): error CS0181: Attribute constructor parameter 'a' has type 'Action', which is not a valid attribute parameter type + // [A([B] () => { })] + Diagnostic(ErrorCode.ERR_BadAttributeParamType, "A").WithArguments("a", "System.Action").WithLocation(6, 2)); + } + + [Fact] + public void LambdaAttributes_BadAttributeLocation() + { + var source = +@"using System; + +[AttributeUsage(AttributeTargets.Property)] +class PropAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Method)] +class MethodAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.ReturnValue)] +class ReturnAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Parameter)] +class ParamAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.GenericParameter)] +class TypeParamAttribute : Attribute { } + +class Program +{ + static void Main() + { + Action a = + [Prop] // 1 + [Return] // 2 + [Method] + [return: Prop] // 3 + [return: Return] + [return: Method] // 4 + ( + [Param] + [TypeParam] // 5 + object o) => + { + }; + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (23,14): error CS0592: Attribute 'Prop' is not valid on this declaration type. It is only valid on 'property, indexer' declarations. + // [Prop] // 1 + Diagnostic(ErrorCode.ERR_AttributeOnBadSymbolType, "Prop").WithArguments("Prop", "property, indexer").WithLocation(23, 14), + // (24,14): error CS0592: Attribute 'Return' is not valid on this declaration type. It is only valid on 'return' declarations. + // [Return] // 2 + Diagnostic(ErrorCode.ERR_AttributeOnBadSymbolType, "Return").WithArguments("Return", "return").WithLocation(24, 14), + // (26,22): error CS0592: Attribute 'Prop' is not valid on this declaration type. It is only valid on 'property, indexer' declarations. + // [return: Prop] // 3 + Diagnostic(ErrorCode.ERR_AttributeOnBadSymbolType, "Prop").WithArguments("Prop", "property, indexer").WithLocation(26, 22), + // (28,22): error CS0592: Attribute 'Method' is not valid on this declaration type. It is only valid on 'method' declarations. + // [return: Method] // 4 + Diagnostic(ErrorCode.ERR_AttributeOnBadSymbolType, "Method").WithArguments("Method", "method").WithLocation(28, 22), + // (31,14): error CS0592: Attribute 'TypeParam' is not valid on this declaration type. It is only valid on 'type parameter' declarations. + // [TypeParam] // 5 + Diagnostic(ErrorCode.ERR_AttributeOnBadSymbolType, "TypeParam").WithArguments("TypeParam", "type parameter").WithLocation(31, 14)); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var lambda = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbol = (IMethodSymbol)model.GetSymbolInfo(lambda).Symbol; + Assert.NotNull(symbol); + + verifyAttributes(symbol.GetAttributes(), "PropAttribute", "ReturnAttribute", "MethodAttribute"); + verifyAttributes(symbol.GetReturnTypeAttributes(), "PropAttribute", "ReturnAttribute", "MethodAttribute"); + verifyAttributes(symbol.Parameters[0].GetAttributes(), "ParamAttribute", "TypeParamAttribute"); + + void verifyAttributes(ImmutableArray attributes, params string[] expectedAttributeNames) + { + var actualAttributes = attributes.SelectAsArray(a => a.AttributeClass.GetSymbol()); + var expectedAttributes = expectedAttributeNames.Select(n => comp.GetTypeByMetadataName(n)); + AssertEx.Equal(expectedAttributes, actualAttributes); + } + } + + [Fact] + public void LambdaAttributes_AttributeSemanticModel() + { + var source = +@"using System; +class AAttribute : Attribute { } +class BAttribute : Attribute { } +class CAttribute : Attribute { } +class DAttribute : Attribute { } +class Program +{ + static void Main() + { + Action a = [A] () => { }; + Func b = [return: B] () => null; + Action c = ([C] object obj) => { }; + Func d = [D] x => x; + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (13,34): error CS8916: Attributes on lambda expressions require a parenthesized parameter list. + // Func d = [D] x => x; + Diagnostic(ErrorCode.ERR_AttributesRequireParenthesizedLambdaExpression, "[D]").WithLocation(13, 34)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var attributeSyntaxes = tree.GetRoot().DescendantNodes().OfType(); + var actualAttributes = attributeSyntaxes.Select(a => model.GetSymbolInfo(a).Symbol.GetSymbol()).ToImmutableArray(); + var expectedAttributes = new[] { "AAttribute", "BAttribute", "CAttribute", "DAttribute" }.Select(a => comp.GetTypeByMetadataName(a).InstanceConstructors.Single()).ToImmutableArray(); + AssertEx.Equal(expectedAttributes, actualAttributes); + } + + [Theory] + [InlineData("Action a = [A] () => { };")] + [InlineData("Func f = [return: A] () => null;")] + [InlineData("Action a = ([A] int i) => { };")] + public void LambdaAttributes_SpeculativeSemanticModel(string statement) + { + string source = +$@"using System; +class AAttribute : Attribute {{ }} +class Program +{{ + static void Main() + {{ + {statement} + }} +}}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var a = (IdentifierNameSyntax)tree.GetRoot().DescendantNodes().OfType().Single().Name; + Assert.Equal("A", a.Identifier.Text); + var attrInfo = model.GetSymbolInfo(a); + var attrType = comp.GetMember("AAttribute").GetPublicSymbol(); + var attrCtor = attrType.GetMember(".ctor"); + Assert.Equal(attrCtor, attrInfo.Symbol); + + // Assert that this is also true for the speculative semantic model + var newTree = SyntaxFactory.ParseSyntaxTree(source + " "); + var m = newTree.GetRoot().DescendantNodes().OfType().Single(); + + Assert.True(model.TryGetSpeculativeSemanticModelForMethodBody(m.Body.SpanStart, m, out model)); + + a = (IdentifierNameSyntax)newTree.GetRoot().DescendantNodes().OfType().Single().Name; + Assert.Equal("A", a.Identifier.Text); + + // If we aren't using the right binder here, the compiler crashes going through the binder factory + var info = model.GetSymbolInfo(a); + // This behavior is wrong. See https://github.com/dotnet/roslyn/issues/24135 + Assert.Equal(attrType, info.Symbol); + } + + [Fact] + public void LambdaAttributes_DisallowedAttributes() + { + var source = +@"using System; +using System.Runtime.CompilerServices; +namespace System.Runtime.CompilerServices +{ + public class IsReadOnlyAttribute : Attribute { } + public class IsUnmanagedAttribute : Attribute { } + public class IsByRefLikeAttribute : Attribute { } + public class NullableContextAttribute : Attribute { public NullableContextAttribute(byte b) { } } +} +class Program +{ + static void Main() + { + Action a = + [IsReadOnly] // 1 + [IsUnmanaged] // 2 + [IsByRefLike] // 3 + [Extension] // 4 + [NullableContext(0)] // 5 + () => { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (15,14): error CS8335: Do not use 'System.Runtime.CompilerServices.IsReadOnlyAttribute'. This is reserved for compiler usage. + // [IsReadOnly] // 1 + Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "IsReadOnly").WithArguments("System.Runtime.CompilerServices.IsReadOnlyAttribute").WithLocation(15, 14), + // (16,14): error CS8335: Do not use 'System.Runtime.CompilerServices.IsUnmanagedAttribute'. This is reserved for compiler usage. + // [IsUnmanaged] // 2 + Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "IsUnmanaged").WithArguments("System.Runtime.CompilerServices.IsUnmanagedAttribute").WithLocation(16, 14), + // (17,14): error CS8335: Do not use 'System.Runtime.CompilerServices.IsByRefLikeAttribute'. This is reserved for compiler usage. + // [IsByRefLike] // 3 + Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "IsByRefLike").WithArguments("System.Runtime.CompilerServices.IsByRefLikeAttribute").WithLocation(17, 14), + // (18,14): error CS1112: Do not use 'System.Runtime.CompilerServices.ExtensionAttribute'. Use the 'this' keyword instead. + // [Extension] // 4 + Diagnostic(ErrorCode.ERR_ExplicitExtension, "Extension").WithLocation(18, 14), + // (19,14): error CS8335: Do not use 'System.Runtime.CompilerServices.NullableContextAttribute'. This is reserved for compiler usage. + // [NullableContext(0)] // 5 + Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "NullableContext(0)").WithArguments("System.Runtime.CompilerServices.NullableContextAttribute").WithLocation(19, 14)); + } + + [Fact] + public void LambdaAttributes_DisallowedSecurityAttributes() + { + var source = +@"using System; +using System.Security; +class Program +{ + static void Main() + { + Action a = + [SecurityCritical] // 1 + [SecuritySafeCriticalAttribute] // 2 + async () => { }; // 3 + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (8,14): error CS4030: Security attribute 'SecurityCritical' cannot be applied to an Async method. + // [SecurityCritical] // 1 + Diagnostic(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsync, "SecurityCritical").WithArguments("SecurityCritical").WithLocation(8, 14), + // (9,14): error CS4030: Security attribute 'SecuritySafeCriticalAttribute' cannot be applied to an Async method. + // [SecuritySafeCriticalAttribute] // 2 + Diagnostic(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsync, "SecuritySafeCriticalAttribute").WithArguments("SecuritySafeCriticalAttribute").WithLocation(9, 14), + // (10,22): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async () => { }; // 3 + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "=>").WithLocation(10, 22)); + } + + [Fact] + public void LambdaAttributes_ObsoleteAttribute() + { + var source = +@"using System; +class Program +{ + static void Report(Action a) + { + foreach (var attribute in a.Method.GetCustomAttributes(inherit: false)) + Console.Write(attribute); + } + static void Main() + { + Report([Obsolete] () => { }); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "System.ObsoleteAttribute"); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbol = model.GetSymbolInfo(expr).Symbol; + Assert.Equal("System.ObsoleteAttribute", symbol.GetAttributes().Single().ToString()); + } + + [Fact] + public void LambdaParameterAttributes_Conditional() + { + var source = +@"using System; +using System.Diagnostics; +class Program +{ + static void Report(Action a) + { + } + static void Main() + { + Report([Conditional(""DEBUG"")] static () => { }); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics( + // (10,17): error CS0577: The Conditional attribute is not valid on 'lambda expression' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + // Report([Conditional("DEBUG")] static () => { }); + Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"Conditional(""DEBUG"")").WithArguments("lambda expression").WithLocation(10, 17)); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + var lambda = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)).Single(); + Assert.Equal(new[] { "DEBUG" }, lambda.GetAppliedConditionalSymbols()); + } + + [Fact] + public void LambdaAttributes_WellKnownAttributes() + { + var sourceA = +@"using System; +using System.Runtime.InteropServices; +using System.Security; +class Program +{ + static void Main() + { + Action a1 = [DllImport(""MyModule.dll"")] static () => { }; + Action a2 = [DynamicSecurityMethod] () => { }; + Action a3 = [SuppressUnmanagedCodeSecurity] () => { }; + Func a4 = [return: MarshalAs((short)0)] () => null; + } +}"; + var sourceB = +@"namespace System.Security +{ + internal class DynamicSecurityMethodAttribute : Attribute { } +}"; + var comp = CreateCompilation(new[] { sourceA, sourceB }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (8,22): error CS0601: The DllImport attribute must be specified on a method marked 'static' and 'extern' + // Action a1 = [DllImport("MyModule.dll")] static () => { }; + Diagnostic(ErrorCode.ERR_DllImportOnInvalidMethod, "DllImport").WithLocation(8, 22)); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + Assert.Equal(4, exprs.Length); + var lambdas = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)); + Assert.Null(lambdas[0].GetDllImportData()); // [DllImport] is ignored if there are errors. + Assert.True(lambdas[1].RequiresSecurityObject); + Assert.True(lambdas[2].HasDeclarativeSecurity); + Assert.Equal(default, lambdas[3].ReturnValueMarshallingInformation.UnmanagedType); + } + + [Fact] + public void LambdaAttributes_Permissions() + { + var source = +@"#pragma warning disable 618 +using System; +using System.Security.Permissions; +class Program +{ + static void Main() + { + Action a1 = [PermissionSet(SecurityAction.Deny)] () => { }; + } +}"; + var comp = CreateCompilationWithMscorlib40(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + var lambda = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)).Single(); + Assert.NotEmpty(lambda.GetSecurityInformation()); + } + + [Fact] + public void LambdaAttributes_NullableAttributes_01() + { + var source = +@"using System; +using System.Diagnostics.CodeAnalysis; +class Program +{ + static void Main() + { + Func a1 = [return: MaybeNull][return: NotNull] () => null; + Func a2 = [return: NotNullIfNotNull(""obj"")] (object obj) => obj; + Func a4 = [MemberNotNull(""x"")][MemberNotNullWhen(false, ""y"")][MemberNotNullWhen(true, ""z"")] () => true; + } +}"; + var comp = CreateCompilation( + new[] { source, MaybeNullAttributeDefinition, NotNullAttributeDefinition, NotNullIfNotNullAttributeDefinition, MemberNotNullAttributeDefinition, MemberNotNullWhenAttributeDefinition }, + parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + Assert.Equal(3, exprs.Length); + var lambdas = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)); + Assert.Equal(FlowAnalysisAnnotations.MaybeNull | FlowAnalysisAnnotations.NotNull, lambdas[0].ReturnTypeFlowAnalysisAnnotations); + Assert.Equal(new[] { "obj" }, lambdas[1].ReturnNotNullIfParameterNotNull); + Assert.Equal(new[] { "x" }, lambdas[2].NotNullMembers); + Assert.Equal(new[] { "y" }, lambdas[2].NotNullWhenFalseMembers); + Assert.Equal(new[] { "z" }, lambdas[2].NotNullWhenTrueMembers); + } + + [Fact] + public void LambdaAttributes_NullableAttributes_02() + { + var source = +@"#nullable enable +using System; +using System.Diagnostics.CodeAnalysis; +class Program +{ + static void Main() + { + Func a1 = [return: MaybeNull] () => null; + Func a2 = [return: NotNull] () => null; + } +}"; + var comp = CreateCompilation(new[] { source, MaybeNullAttributeDefinition, NotNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + // https://github.com/dotnet/roslyn/issues/52827: Report WRN_NullReferenceReturn for a2, not for a1. + comp.VerifyDiagnostics( + // (8,53): warning CS8603: Possible null reference return. + // Func a1 = [return: MaybeNull] () => null; + Diagnostic(ErrorCode.WRN_NullReferenceReturn, "null").WithLocation(8, 53)); + } + + [Fact] + public void LambdaAttributes_NullableAttributes_03() + { + var source = +@"#nullable enable +using System; +using System.Diagnostics.CodeAnalysis; +class Program +{ + static void Main() + { + Action a1 = ([AllowNull] x) => { x.ToString(); }; + Action a2 = ([DisallowNull] x) => { x.ToString(); }; + } +}"; + var comp = CreateCompilation(new[] { source, AllowNullAttributeDefinition, DisallowNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + // https://github.com/dotnet/roslyn/issues/52827: Report nullability mismatch warning assigning lambda to a2. + comp.VerifyDiagnostics( + // (8,50): warning CS8602: Dereference of a possibly null reference. + // Action a1 = ([AllowNull] x) => { x.ToString(); }; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 50)); + } + + [Fact] + public void LambdaAttributes_DoesNotReturn() + { + var source = +@"using System; +using System.Diagnostics.CodeAnalysis; +class Program +{ + static void Main() + { + Action a1 = [DoesNotReturn] () => { }; + Action a2 = [DoesNotReturn] () => throw new Exception(); + } +}"; + var comp = CreateCompilation(new[] { source, DoesNotReturnAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + // https://github.com/dotnet/roslyn/issues/52827: Report warning that lambda expression in a1 initializer returns. + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + Assert.Equal(2, exprs.Length); + var lambdas = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)); + Assert.Equal(FlowAnalysisAnnotations.DoesNotReturn, lambdas[0].FlowAnalysisAnnotations); + Assert.Equal(FlowAnalysisAnnotations.DoesNotReturn, lambdas[1].FlowAnalysisAnnotations); + } + + [Fact] + public void LambdaAttributes_UnmanagedCallersOnly() + { + var source = +@"using System; +using System.Runtime.InteropServices; +class Program +{ + static void Main() + { + Action a = [UnmanagedCallersOnly] static () => { }; + } +}"; + var comp = CreateCompilation(new[] { source, UnmanagedCallersOnlyAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (7,21): error CS8896: 'UnmanagedCallersOnly' can only be applied to ordinary static methods or static local functions. + // Action a = [UnmanagedCallersOnly] static () => { }; + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyRequiresStatic, "UnmanagedCallersOnly").WithLocation(7, 21)); + } + + [Fact] + public void LambdaParameterAttributes_OptionalAndDefaultValueAttributes() + { + var source = +@"using System; +using System.Runtime.InteropServices; +class Program +{ + static void Main() + { + Action a1 = ([Optional, DefaultParameterValue(2)] int i) => { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + var lambda = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)).Single(); + var parameter = (SourceParameterSymbol)lambda.Parameters[0]; + Assert.True(parameter.HasOptionalAttribute); + Assert.False(parameter.HasExplicitDefaultValue); + Assert.Equal(2, parameter.DefaultValueFromAttributes.Value); + } + + [ConditionalFact(typeof(DesktopOnly))] + public void LambdaParameterAttributes_WellKnownAttributes() + { + var source = +@"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + Action a1 = ([IDispatchConstant] object obj) => { }; + Action a2 = ([IUnknownConstant] object obj) => { }; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + Assert.Equal(2, exprs.Length); + var lambdas = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)); + Assert.True(lambdas[0].Parameters[0].IsIDispatchConstant); + Assert.True(lambdas[1].Parameters[0].IsIUnknownConstant); + } + + [Fact] + public void LambdaParameterAttributes_NullableAttributes_01() + { + var source = +@"using System; +using System.Diagnostics.CodeAnalysis; +class Program +{ + static void Main() + { + Action a1 = ([AllowNull][MaybeNullWhen(false)] object obj) => { }; + Action a2 = (object x, [NotNullIfNotNull(""x"")] object y) => { }; + } +}"; + var comp = CreateCompilation( + new[] { source, AllowNullAttributeDefinition, MaybeNullWhenAttributeDefinition, NotNullIfNotNullAttributeDefinition }, + parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var exprs = tree.GetRoot().DescendantNodes().OfType().ToImmutableArray(); + Assert.Equal(2, exprs.Length); + var lambdas = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)); + Assert.Equal(FlowAnalysisAnnotations.AllowNull | FlowAnalysisAnnotations.MaybeNullWhenFalse, lambdas[0].Parameters[0].FlowAnalysisAnnotations); + Assert.Equal(new[] { "x" }, lambdas[1].Parameters[1].NotNullIfParameterNotNull); + } + + [Fact] + public void LambdaParameterAttributes_NullableAttributes_02() + { + var source = +@"#nullable enable +using System.Diagnostics.CodeAnalysis; +delegate bool D(out object? obj); +class Program +{ + static void Main() + { + D d = ([NotNullWhen(true)] out object? obj) => + { + obj = null; + return true; + }; + } +}"; + var comp = CreateCompilation(new[] { source, NotNullWhenAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + // https://github.com/dotnet/roslyn/issues/52827: Should report WRN_ParameterConditionallyDisallowsNull. + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single(); + var lambda = GetLambdaSymbol(model, expr); + Assert.Equal(FlowAnalysisAnnotations.NotNullWhenTrue, lambda.Parameters[0].FlowAnalysisAnnotations); + } + + private static LambdaSymbol GetLambdaSymbol(SemanticModel model, LambdaExpressionSyntax syntax) + { + return model.GetSymbolInfo(syntax).Symbol.GetSymbol(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupTests.cs index 2b3a7580d5824..c7db163d93d9d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupTests.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -1069,6 +1068,36 @@ static void local1(int z) Assert.Contains("System.Int32 y", lookupSymbols); } + [Fact] + public void LookupInsideLambdaAttribute() + { + var testSrc = @" +using System; + +class Program +{ + const int w = 0451; + + void M() + { + int x = 42; + const int y = 123; + Action a = + [ObsoleteAttribute(/*pos*/ + (int z) => { }; + } +} +"; + + var lookupNames = GetLookupNames(testSrc); + var lookupSymbols = GetLookupSymbols(testSrc).Select(e => e.ToTestDisplayString()).ToList(); + + Assert.Contains("w", lookupNames); + Assert.Contains("y", lookupNames); + Assert.Contains("System.Int32 Program.w", lookupSymbols); + Assert.Contains("System.Int32 y", lookupSymbols); + } + [Fact] public void LookupInsideIncompleteStatementAttribute() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index d2361595a7ab4..e2544c87fa8af 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -1949,9 +1949,9 @@ static void M(A a) // (8,9): error CS1656: Cannot assign to 'E' because it is a 'method group' // a.E! += a.E!; // 1 Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "a.E").WithArguments("E", "method group").WithLocation(8, 9), - // (9,13): error CS0019: Operator '!=' cannot be applied to operands of type 'method group' and '' + // (9,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // if (a.E! != null) // 2 - Diagnostic(ErrorCode.ERR_BadBinaryOps, "a.E! != null").WithArguments("!=", "method group", "").WithLocation(9, 13), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "a.E").WithArguments("inferred delegate type").WithLocation(9, 13), // (11,15): error CS1503: Argument 1: cannot convert from 'method group' to 'A' // M(a.E!); // 3 Diagnostic(ErrorCode.ERR_BadArgType, "a.E").WithArguments("1", "method group", "A").WithLocation(11, 15), @@ -1970,9 +1970,9 @@ static void M(A a) // (17,9): error CS1656: Cannot assign to 'F' because it is a 'method group' // a.F! += a.F!; // 8 Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "a.F").WithArguments("F", "method group").WithLocation(17, 9), - // (18,13): error CS0019: Operator '!=' cannot be applied to operands of type 'method group' and '' + // (18,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // if (a.F! != null) // 9 - Diagnostic(ErrorCode.ERR_BadBinaryOps, "a.F! != null").WithArguments("!=", "method group", "").WithLocation(18, 13), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "a.F").WithArguments("inferred delegate type").WithLocation(18, 13), // (20,15): error CS1503: Argument 1: cannot convert from 'method group' to 'A' // M(a.F!); // 10 Diagnostic(ErrorCode.ERR_BadArgType, "a.F").WithArguments("1", "method group", "A").WithLocation(20, 15), @@ -5112,9 +5112,10 @@ void M(T t) var declaration = syntaxTree.GetRoot().DescendantNodes().OfType().Single(); var model = comp.GetSemanticModel(syntaxTree); var local = (ILocalSymbol)model.GetDeclaredSymbol(declaration); - Assert.Equal(CodeAnalysis.NullableAnnotation.NotAnnotated, local.NullableAnnotation); - Assert.Equal(CodeAnalysis.NullableAnnotation.NotAnnotated, local.Type.NullableAnnotation); - Assert.Equal("T", local.Type.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal("T? t2", local.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableAnnotation.Annotated, local.NullableAnnotation); + Assert.Equal(CodeAnalysis.NullableAnnotation.Annotated, local.Type.NullableAnnotation); + Assert.Equal("T?", local.Type.ToTestDisplayString(includeNonNullable: true)); } [Fact] @@ -56027,7 +56028,7 @@ static Action M(Action a) comp.VerifyDiagnostics(); } - [Fact] + [ConditionalFact(typeof(IsRelease))] [WorkItem(48174, "https://github.com/dotnet/roslyn/issues/48174")] public void Lambda_Nesting_Large_01() { @@ -56108,7 +56109,7 @@ static void M() ); } - [Fact] + [ConditionalFact(typeof(IsRelease))] [WorkItem(48174, "https://github.com/dotnet/roslyn/issues/48174")] public void Lambda_Nesting_Large_02() { @@ -56137,7 +56138,7 @@ void M1() comp.VerifyDiagnostics(); } - [Fact] + [ConditionalFact(typeof(IsRelease))] [WorkItem(48174, "https://github.com/dotnet/roslyn/issues/48174")] public void Lambda_Nesting_Large_03() { @@ -56284,7 +56285,7 @@ public void Initial(Recursive recurse) comp.VerifyDiagnostics(); } - [Fact] + [ConditionalFact(typeof(IsRelease))] [WorkItem(48174, "https://github.com/dotnet/roslyn/issues/48174")] public void Lambda_Nesting_ErrorType() { @@ -60536,7 +60537,7 @@ public Func M1() { } public Func M5() { - return Helper.Method1; + return Helper.Method1; // 2 } public Func M6() { @@ -60544,7 +60545,7 @@ public Func M5() { } public Func M7() { - return Helper.Method1; // 2 + return Helper.Method1; // 3 } public Func M8() { @@ -60563,8 +60564,11 @@ static class Helper // (15,16): warning CS8621: Nullability of reference types in return type of 'string? Helper.Method1(string? t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). // return Helper.Method1; // 1 Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("string? Helper.Method1(string? t)", "System.Func").WithLocation(15, 16), - // (31,16): warning CS8621: Nullability of reference types in return type of 'T? Helper.Method1(T? t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). + // (23,16): warning CS8621: Nullability of reference types in return type of 'T? Helper.Method1(T? t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). // return Helper.Method1; // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("T? Helper.Method1(T? t)", "System.Func").WithLocation(23, 16), + // (31,16): warning CS8621: Nullability of reference types in return type of 'T? Helper.Method1(T? t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). + // return Helper.Method1; // 3 Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("T? Helper.Method1(T? t)", "System.Func").WithLocation(31, 16) ); } @@ -60671,6 +60675,107 @@ static class Helper ); } + [Fact] + [WorkItem(49865, "https://github.com/dotnet/roslyn/issues/49865")] + public void DelegateCreation_FromMethodGroup_NotNullIfNotNull_06() + { + var source = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C { + public Func M1() { + return Helper.Method1; // 1 + } + + public Func M2() where T : notnull { + return Helper.Method1; + } + + public Func M3() where T : class { + return Helper.Method1; + } + + public Func M4() where T : class? { + return Helper.Method1; // 2 + } + + public Func M5() where T : struct { + return Helper.Method1; + + } + + public Func M6() { + return Helper.Method1; // 3 + } +} + +static class Helper +{ + [return: NotNullIfNotNull(""t"")] + public static string? Method1(T t) + { + return t?.ToString(); + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullIfNotNullAttributeDefinition, AllowNullAttributeDefinition, DisallowNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (7,16): warning CS8621: Nullability of reference types in return type of 'string? Helper.Method1(T t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). + // return Helper.Method1; // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("string? Helper.Method1(T t)", "System.Func").WithLocation(7, 16), + // (19,16): warning CS8621: Nullability of reference types in return type of 'string? Helper.Method1(T t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). + // return Helper.Method1; // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("string? Helper.Method1(T t)", "System.Func").WithLocation(19, 16), + // (28,16): warning CS8621: Nullability of reference types in return type of 'string? Helper.Method1(T? t)' doesn't match the target delegate 'Func' (possibly because of nullability attributes). + // return Helper.Method1; // 3 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("string? Helper.Method1(T? t)", "System.Func").WithLocation(28, 16)); + } + + [Fact] + [WorkItem(49865, "https://github.com/dotnet/roslyn/issues/49865")] + public void DelegateCreation_FromMethodGroup_NotNullIfNotNull_07() + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +public class C { + public D1 M1() { + return Helper.Method1; // 1 + } + + public D2 M2() { + return Helper.Method1; // 2 + } + + public D3 M3() { + return Helper.Method1; + } +} + +public delegate string D1(T t); +public delegate string D2(T? t); +public delegate string D3([DisallowNull] T t); + +static class Helper +{ + [return: NotNullIfNotNull(""t"")] + public static string? Method1(T t) + { + return t?.ToString(); + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullIfNotNullAttributeDefinition, AllowNullAttributeDefinition, DisallowNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (6,16): warning CS8621: Nullability of reference types in return type of 'string? Helper.Method1(T t)' doesn't match the target delegate 'D1' (possibly because of nullability attributes). + // return Helper.Method1; // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("string? Helper.Method1(T t)", "D1").WithLocation(6, 16), + // (10,16): warning CS8621: Nullability of reference types in return type of 'string? Helper.Method1(T? t)' doesn't match the target delegate 'D2' (possibly because of nullability attributes). + // return Helper.Method1; // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "Helper.Method1").WithArguments("string? Helper.Method1(T? t)", "D2").WithLocation(10, 16)); + } + [Fact] [WorkItem(44129, "https://github.com/dotnet/roslyn/issues/44174")] public void NotNullIfNotNull_Override() @@ -142785,30 +142890,18 @@ static T F5(U x5) where U : T? }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (7,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // y1 = default(T); - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "default(T)").WithLocation(7, 14), // (8,16): warning CS8603: Possible null reference return. // return y1; // 1 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "y1").WithLocation(8, 16), // (14,16): warning CS8603: Possible null reference return. // return y2; // 2 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "y2").WithLocation(14, 16), - // (19,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // y3 = default(T); - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "default(T)").WithLocation(19, 14), // (20,16): warning CS8603: Possible null reference return. // return y3; // 3 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "y3").WithLocation(20, 16), - // (25,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // y4 = default(T); - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "default(T)").WithLocation(25, 14), // (26,16): warning CS8603: Possible null reference return. // return y4; // 4 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "y4").WithLocation(26, 16), - // (31,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // y5 = default(U); - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "default(U)").WithLocation(31, 14), // (32,16): warning CS8603: Possible null reference return. // return y5; // 5 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "y5").WithLocation(32, 16)); @@ -142818,11 +142911,11 @@ static T F5(U x5) where U : T? var locals = tree.GetRoot().DescendantNodes().OfType().ToArray(); Assert.Equal(5, locals.Length); // https://github.com/dotnet/roslyn/issues/46236: Locals should be treated as annotated. - VerifyVariableAnnotation(model, locals[0], "T y1", NullableAnnotation.NotAnnotated); + VerifyVariableAnnotation(model, locals[0], "T? y1", NullableAnnotation.Annotated); VerifyVariableAnnotation(model, locals[1], "T? y2", NullableAnnotation.Annotated); - VerifyVariableAnnotation(model, locals[2], "T y3", NullableAnnotation.NotAnnotated); - VerifyVariableAnnotation(model, locals[3], "T y4", NullableAnnotation.NotAnnotated); - VerifyVariableAnnotation(model, locals[4], "U y5", NullableAnnotation.NotAnnotated); + VerifyVariableAnnotation(model, locals[2], "T? y3", NullableAnnotation.Annotated); + VerifyVariableAnnotation(model, locals[3], "T? y4", NullableAnnotation.Annotated); + VerifyVariableAnnotation(model, locals[4], "U? y5", NullableAnnotation.Annotated); } private static void VerifyVariableAnnotation(SemanticModel model, VariableDeclaratorSyntax syntax, string expectedDisplay, NullableAnnotation expectedAnnotation) @@ -146615,5 +146708,143 @@ void M(I3 i) AssertEx.Equal(new string[] { "I", "I2", "I" }, i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); } + + [Fact] + public void VarPatternDeclaration_TopLevel() + { + var src = @" +#nullable enable +public class C +{ + public void M(string? x) + { + if (Identity(x) is var y) + { + y.ToString(); // 1 + } + + if (Identity(x) is not null and var z) + { + z.ToString(); + } + } + + public T Identity(T t) => t; +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (9,13): warning CS8602: Dereference of a possibly null reference. + // y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(9, 13) + ); + } + + [Fact] + public void VarPatternDeclaration_Nested() + { + var src = @" +#nullable enable +public class Container +{ + public T Item { get; set; } = default!; +} +public class C +{ + public void M(Container x) + { + if (Identity(x) is { Item: var y }) + { + y.ToString(); // 1 + } + + if (Identity(x) is { Item: not null and var z }) + { + z.ToString(); + } + } + + public T Identity(T t) => t; +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (13,13): warning CS8602: Dereference of a possibly null reference. + // y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(13, 13) + ); + } + + [Theory, WorkItem(52925, "https://github.com/dotnet/roslyn/issues/52925")] + [WorkItem(46236, "https://github.com/dotnet/roslyn/issues/46236")] + [InlineData("")] + [InlineData(" where T : notnull")] + [InlineData(" where T : class")] + [InlineData(" where T : class?")] + public void VarDeclarationWithGenericType(string constraint) + { + var src = $@" +#nullable enable + +class C {constraint} +{{ + void M(T initial, System.Func selector) + {{ + for (var current = initial; current != null; current = selector(current)) + {{ + }} + + var current2 = initial; + current2 = default; + + for (T? current3 = initial; current3 != null; current3 = selector(current3)) + {{ + }} + + T? current4 = initial; + current4 = default; + }} +}} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var declarations = tree.GetRoot().DescendantNodes().OfType(); + foreach (var declaration in declarations) + { + var local = (ILocalSymbol)model.GetDeclaredSymbol(declaration.Variables.Single()); + Assert.Equal("T?", local.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableAnnotation.Annotated, local.Type.NullableAnnotation); + } + } + + [Theory, WorkItem(52925, "https://github.com/dotnet/roslyn/issues/52925")] + [InlineData("")] + [InlineData(" where T : notnull")] + [InlineData(" where T : class")] + [InlineData(" where T : class?")] + public void VarDeclarationWithGenericType_RefValue(string constraint) + { + var src = $@" +#nullable enable + +class C {constraint} +{{ + ref T Passthrough(ref T value) + {{ + ref var value2 = ref value; + return ref value2; + }} +}} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (9,20): warning CS8619: Nullability of reference types in value of type 'T?' doesn't match target type 'T'. + // return ref value2; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "value2").WithArguments("T?", "T").WithLocation(9, 20) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs index 7125de88c065b..99740a7cd5898 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs @@ -19587,7 +19587,7 @@ static void Test2(object x, System.ArgIterator y) // (11,25): error CS1601: Cannot make reference to variable of type 'ArgIterator' // static object Test1(out System.ArgIterator x) Diagnostic(ErrorCode.ERR_MethodArgCantBeRefAny, "out System.ArgIterator x").WithArguments("System.ArgIterator").WithLocation(11, 25), - // (8,25): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or lambda expressions. + // (8,25): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or async lambda expressions. // Test2(Test1(out var x1), x1); Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("System.ArgIterator").WithLocation(8, 25), // (6,16): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. @@ -19636,10 +19636,10 @@ static void Test2(object x, System.ArgIterator y) // (12,25): error CS1601: Cannot make reference to variable of type 'ArgIterator' // static object Test1(out System.ArgIterator x) Diagnostic(ErrorCode.ERR_MethodArgCantBeRefAny, "out System.ArgIterator x").WithArguments("System.ArgIterator").WithLocation(12, 25), - // (8,25): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or lambda expressions. + // (8,25): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or async lambda expressions. // Test2(Test1(out System.ArgIterator x1), x1); Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(8, 25), - // (9,9): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or lambda expressions. + // (9,9): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or async lambda expressions. // var x = default(System.ArgIterator); Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("System.ArgIterator").WithLocation(9, 9), // (6,16): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs index 14439c9a45768..10a114556df13 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs @@ -1567,7 +1567,7 @@ static void Main() { IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, IsInvalid) (Syntax: '(C)(o switc ... default })') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: C?, IsInvalid) (Syntax: 'o switch { ... > default }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: C?, IsInvalid) (Syntax: 'o switch { ... > default }') Value: IFieldReferenceOperation: System.Object C.o (Static) (OperationKind.FieldReference, Type: System.Object, IsInvalid) (Syntax: 'o') Instance Receiver: @@ -1590,7 +1590,7 @@ static void Main() { IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, IsInvalid) (Syntax: '(C)(o switc ... ow null! })') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: - ISwitchExpressionOperation (1 arms) (OperationKind.SwitchExpression, Type: C, IsInvalid) (Syntax: 'o switch { ... row null! }') + ISwitchExpressionOperation (1 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: C, IsInvalid) (Syntax: 'o switch { ... row null! }') Value: IFieldReferenceOperation: System.Object C.o (Static) (OperationKind.FieldReference, Type: System.Object, IsInvalid) (Syntax: 'o') Instance Receiver: @@ -3014,7 +3014,7 @@ class C ); VerifyOperationTreeForTest(compilation, @" -ISwitchExpressionOperation (3 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'c switch ... }') +ISwitchExpressionOperation (3 arms, IsExhaustive: True) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: 'c switch ... }') Value: IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: System.UInt32) (Syntax: 'c') Arms(3): diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs index bb40c220fc1ae..b070426011afd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs @@ -3505,5 +3505,320 @@ void M(object e) CreatePatternCompilation(source, options: TestOptions.ReleaseDll).VerifyDiagnostics( ); } + +#if DEBUG + [Fact, WorkItem(53868, "https://github.com/dotnet/roslyn/issues/53868")] + public void DecisionDag_Dump_SwitchStatement_01() + { + var source = @" +using System; + +class C +{ + void M(object obj) + { + switch (obj) + { + case ""a"": + Console.Write(""b""); + break; + case string { Length: 1 } s: + Console.Write(s); + break; + case int and < 42: + Console.Write(43); + break; + case int i when (i % 2) == 0: + obj = i + 1; + break; + default: + Console.Write(false); + break; + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchStatement)binder.BindStatement(@switch, BindingDiagnosticBag.Discarded); + AssertEx.Equal( +@"[0]: t0 is string ? [1] : [8] +[1]: t1 = (string)t0; [2] +[2]: t1 == ""a"" ? [3] : [4] +[3]: leaf `case ""a"":` +[4]: t2 = t1.Length; [5] +[5]: t2 == 1 ? [6] : [13] +[6]: when ? [7] : +[7]: leaf `case string { Length: 1 } s:` +[8]: t0 is int ? [9] : [13] +[9]: t3 = (int)t0; [10] +[10]: t3 < 42 ? [11] : [12] +[11]: leaf `case int and < 42:` +[12]: when ((i % 2) == 0) ? [14] : [13] +[13]: leaf `default` +[14]: leaf `case int i when (i % 2) == 0:` +", boundSwitch.DecisionDag.Dump()); + } + + [Fact, WorkItem(53868, "https://github.com/dotnet/roslyn/issues/53868")] + public void DecisionDag_Dump_SwitchStatement_02() + { + var source = @" +using System; + +class C +{ + void Deconstruct(out int i1, out string i2, out int? i3) + { + i1 = 1; + i2 = ""a""; + i3 = null; + } + + void M(C c) + { + switch (c) + { + case null: + Console.Write(0); + break; + case (42, ""b"", 43): + Console.Write(1); + break; + case (< 10, { Length: 0 }, { }): + Console.Write(2); + break; + case (< 10, object): // 1, 2 + Console.Write(3); + break; + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (26,18): error CS7036: There is no argument given that corresponds to the required formal parameter 'i3' of 'C.Deconstruct(out int, out string, out int?)' + // case (< 10, object): // 1, 2 + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "(< 10, object)").WithArguments("i3", "C.Deconstruct(out int, out string, out int?)").WithLocation(26, 18), + // (26,18): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'C', with 2 out parameters and a void return type. + // case (< 10, object): // 1, 2 + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(< 10, object)").WithArguments("C", "2").WithLocation(26, 18) + ); + + var tree = comp.SyntaxTrees.Single(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchStatement)binder.BindStatement(@switch, BindingDiagnosticBag.Discarded); + AssertEx.Equal( +@"[0]: t0 == null ? [1] : [2] +[1]: leaf `case null:` +[2]: (Item1, Item2, Item3) t1 = t0; [3] +[3]: t1.Item1 == 42 ? [4] : [9] +[4]: t1.Item2 == ""b"" ? [5] : [15] +[5]: t1.Item3 != null ? [6] : [15] +[6]: t2 = (int)t1.Item3; [7] +[7]: t2 == 43 ? [8] : [15] +[8]: leaf `case (42, ""b"", 43):` +[9]: t1.Item1 < 10 ? [10] : [15] +[10]: t1.Item2 != null ? [11] : [15] +[11]: t3 = t1.Item2.Length; [12] +[12]: t3 == 0 ? [13] : [15] +[13]: t1.Item3 != null ? [14] : [15] +[14]: leaf `case (< 10, { Length: 0 }, { }):` +[15]: t0 is ? [16] : [17] +[16]: leaf `case (< 10, object):` +[17]: leaf `switch (c) + { + case null: + Console.Write(0); + break; + case (42, ""b"", 43): + Console.Write(1); + break; + case (< 10, { Length: 0 }, { }): + Console.Write(2); + break; + case (< 10, object): // 1, 2 + Console.Write(3); + break; + }` +", boundSwitch.DecisionDag.Dump()); + } + + [Fact, WorkItem(53868, "https://github.com/dotnet/roslyn/issues/53868")] + public void DecisionDag_Dump_SwitchStatement_03() + { + var source = @" +using System; +using System.Runtime.CompilerServices; + +class C : ITuple +{ + int ITuple.Length => 3; + object ITuple.this[int i] => i + 3; + + void M(C c) + { + switch (c) + { + case (3, 4, 4): + Console.Write(0); + break; + case (3, 4, 5): + Console.Write(1); + break; + case (int x, 4, 5): + Console.Write(2); + break; + } + } +} +"; + var comp = CreatePatternCompilation(source, TestOptions.DebugDll); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchStatement)binder.BindStatement(@switch, BindingDiagnosticBag.Discarded); + AssertEx.Equal( +@"[0]: t0 is System.Runtime.CompilerServices.ITuple ? [1] : [28] +[1]: t1 = t0.Length; [2] +[2]: t1 == 3 ? [3] : [28] +[3]: t2 = t0[0]; [4] +[4]: t2 is int ? [5] : [28] +[5]: t3 = (int)t2; [6] +[6]: t3 == 3 ? [7] : [18] +[7]: t4 = t0[1]; [8] +[8]: t4 is int ? [9] : [28] +[9]: t5 = (int)t4; [10] +[10]: t5 == 4 ? [11] : [28] +[11]: t6 = t0[2]; [12] +[12]: t6 is int ? [13] : [28] +[13]: t7 = (int)t6; [14] +[14]: t7 == 4 ? [15] : [16] +[15]: leaf `case (3, 4, 4):` +[16]: t7 == 5 ? [17] : [28] +[17]: leaf `case (3, 4, 5):` +[18]: t4 = t0[1]; [19] +[19]: t4 is int ? [20] : [28] +[20]: t5 = (int)t4; [21] +[21]: t5 == 4 ? [22] : [28] +[22]: t6 = t0[2]; [23] +[23]: t6 is int ? [24] : [28] +[24]: t7 = (int)t6; [25] +[25]: t7 == 5 ? [26] : [28] +[26]: when ? [27] : +[27]: leaf `case (int x, 4, 5):` +[28]: leaf `switch (c) + { + case (3, 4, 4): + Console.Write(0); + break; + case (3, 4, 5): + Console.Write(1); + break; + case (int x, 4, 5): + Console.Write(2); + break; + }` +", boundSwitch.DecisionDag.Dump()); + } + + [Fact, WorkItem(53868, "https://github.com/dotnet/roslyn/issues/53868")] + public void DecisionDag_Dump_IsPattern() + { + var source = @" +using System; + +class C +{ + void M(object obj) + { + if (obj + is < 5 + or string { Length: 1 } + or bool) + { + Console.Write(1); + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var @is = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@is.SpanStart); + var boundIsPattern = (BoundIsPatternExpression)binder.BindExpression(@is, BindingDiagnosticBag.Discarded); + AssertEx.Equal( +@"[0]: t0 is int ? [1] : [3] +[1]: t1 = (int)t0; [2] +[2]: t1 < 5 ? [8] : [9] +[3]: t0 is string ? [4] : [7] +[4]: t2 = (string)t0; [5] +[5]: t3 = t2.Length; [6] +[6]: t3 == 1 ? [8] : [9] +[7]: t0 is bool ? [8] : [9] +[8]: leaf `< 5 + or string { Length: 1 } + or bool` +[9]: leaf `< 5 + or string { Length: 1 } + or bool` +", boundIsPattern.DecisionDag.Dump()); + } + + [Fact, WorkItem(53868, "https://github.com/dotnet/roslyn/issues/53868")] + public void DecisionDag_Dump_SwitchExpression() + { + var source = @" +class C +{ + void M(object obj) + { + var x = obj switch + { + < 5 => 1, + string { Length: 1 } => 2, + bool => 3, + _ => 4 + }; + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var @switch = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + var binder = model.GetEnclosingBinder(@switch.SpanStart); + var boundSwitch = (BoundSwitchExpression)binder.BindExpression(@switch, BindingDiagnosticBag.Discarded); + AssertEx.Equal( +@"[0]: t0 is int ? [1] : [4] +[1]: t1 = (int)t0; [2] +[2]: t1 < 5 ? [3] : [11] +[3]: leaf `< 5 => 1` +[4]: t0 is string ? [5] : [9] +[5]: t2 = (string)t0; [6] +[6]: t3 = t2.Length; [7] +[7]: t3 == 1 ? [8] : [11] +[8]: leaf `string { Length: 1 } => 2` +[9]: t0 is bool ? [10] : [11] +[10]: leaf `bool => 3` +[11]: leaf `_ => 4` +", boundSwitch.DecisionDag.Dump()); + } +#endif } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs new file mode 100644 index 0000000000000..4fe37305c5c55 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -0,0 +1,10326 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; +using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics +{ + [CompilerTrait(CompilerFeature.RecordStructs)] + public class RecordStructTests : CompilingTestBase + { + private static CSharpCompilation CreateCompilation(CSharpTestSource source) + => CSharpTestBase.CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, + parseOptions: TestOptions.RegularPreview); + + private CompilationVerifier CompileAndVerify( + CSharpTestSource src, + string? expectedOutput = null, + IEnumerable? references = null) + => base.CompileAndVerify( + new[] { src, IsExternalInitTypeDefinition }, + expectedOutput: expectedOutput, + parseOptions: TestOptions.RegularPreview, + references: references, + // init-only is unverifiable + verify: Verification.Skipped); + + [Fact] + public void StructRecord1() + { + var src = @" +record struct Point(int X, int Y);"; + + var verifier = CompileAndVerify(src).VerifyDiagnostics(); + verifier.VerifyIL("Point.Equals(object)", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: isinst ""Point"" + IL_0006: brfalse.s IL_0015 + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: unbox.any ""Point"" + IL_000f: call ""bool Point.Equals(Point)"" + IL_0014: ret + IL_0015: ldc.i4.0 + IL_0016: ret +}"); + verifier.VerifyIL("Point.Equals(Point)", @" +{ + // Code size 49 (0x31) + .maxstack 3 + IL_0000: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0005: ldarg.0 + IL_0006: ldfld ""int Point.k__BackingField"" + IL_000b: ldarg.1 + IL_000c: ldfld ""int Point.k__BackingField"" + IL_0011: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" + IL_0016: brfalse.s IL_002f + IL_0018: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_001d: ldarg.0 + IL_001e: ldfld ""int Point.k__BackingField"" + IL_0023: ldarg.1 + IL_0024: ldfld ""int Point.k__BackingField"" + IL_0029: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" + IL_002e: ret + IL_002f: ldc.i4.0 + IL_0030: ret +}"); + } + + [Fact] + public void StructRecord2() + { + var src = @" +using System; +record struct S(int X, int Y) +{ + public static void Main() + { + var s1 = new S(0, 1); + var s2 = new S(0, 1); + Console.WriteLine(s1.X); + Console.WriteLine(s1.Y); + Console.WriteLine(s1.Equals(s2)); + Console.WriteLine(s1.Equals(new S(1, 0))); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"0 +1 +True +False").VerifyDiagnostics(); + } + + [Fact] + public void StructRecord3() + { + var src = @" +using System; +record struct S(int X, int Y) +{ + public bool Equals(S s) => false; + public static void Main() + { + var s1 = new S(0, 1); + Console.WriteLine(s1.Equals(s1)); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"False") + .VerifyDiagnostics( + // (5,17): warning CS8851: 'S' defines 'Equals' but not 'GetHashCode' + // public bool Equals(S s) => false; + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("S").WithLocation(5, 17)); + + verifier.VerifyIL("S.Main", @" +{ + // Code size 23 (0x17) + .maxstack 3 + .locals init (S V_0) //s1 + IL_0000: ldloca.s V_0 + IL_0002: ldc.i4.0 + IL_0003: ldc.i4.1 + IL_0004: call ""S..ctor(int, int)"" + IL_0009: ldloca.s V_0 + IL_000b: ldloc.0 + IL_000c: call ""bool S.Equals(S)"" + IL_0011: call ""void System.Console.WriteLine(bool)"" + IL_0016: ret +}"); + } + + [Fact] + public void StructRecord5() + { + var src = @" +using System; +record struct S(int X, int Y) +{ + public bool Equals(S s) + { + Console.Write(""s""); + return true; + } + public static void Main() + { + var s1 = new S(0, 1); + s1.Equals((object)s1); + s1.Equals(s1); + } +}"; + CompileAndVerify(src, expectedOutput: @"ss") + .VerifyDiagnostics( + // (5,17): warning CS8851: 'S' defines 'Equals' but not 'GetHashCode' + // public bool Equals(S s) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("S").WithLocation(5, 17)); + } + + [Fact] + public void StructRecordDefaultCtor() + { + const string src = @" +public record struct S(int X);"; + + const string src2 = @" +class C +{ + public S M() => new S(); +}"; + var comp = CreateCompilation(src + src2); + comp.VerifyDiagnostics(); + + comp = CreateCompilation(src); + var comp2 = CreateCompilation(src2, references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + } + + [Fact] + public void Equality_01() + { + var source = +@"using static System.Console; +record struct S; + +class Program +{ + static void Main() + { + var x = new S(); + var y = new S(); + WriteLine(x.Equals(y)); + WriteLine(((object)x).Equals(y)); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + + var verifier = CompileAndVerify(comp, expectedOutput: @"True +True").VerifyDiagnostics(); + + verifier.VerifyIL("S.Equals(S)", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: ret +}"); + verifier.VerifyIL("S.Equals(object)", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: isinst ""S"" + IL_0006: brfalse.s IL_0015 + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: unbox.any ""S"" + IL_000f: call ""bool S.Equals(S)"" + IL_0014: ret + IL_0015: ldc.i4.0 + IL_0016: ret +}"); + } + + [Fact] + public void RecordStructLanguageVersion() + { + var src1 = @" +struct Point(int x, int y); +"; + var src2 = @" +record struct Point { } +"; + var src3 = @" +record struct Point(int x, int y); +"; + + var comp = CreateCompilation(new[] { src1, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics( + // (2,13): error CS1514: { expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(2, 13), + // (2,13): error CS1513: } expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(2, 13), + // (2,13): error CS8803: Top-level statements must precede namespace and type declarations. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "(int x, int y);").WithLocation(2, 13), + // (2,13): error CS8805: Program using top-level statements must be an executable. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_SimpleProgramNotAnExecutable, "(int x, int y);").WithLocation(2, 13), + // (2,13): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_IllegalStatement, "(int x, int y)").WithLocation(2, 13), + // (2,14): error CS8185: A declaration is not allowed in this context. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int x").WithLocation(2, 14), + // (2,14): error CS0165: Use of unassigned local variable 'x' + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_UseDefViolation, "int x").WithArguments("x").WithLocation(2, 14), + // (2,21): error CS8185: A declaration is not allowed in this context. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int y").WithLocation(2, 21), + // (2,21): error CS0165: Use of unassigned local variable 'y' + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_UseDefViolation, "int y").WithArguments("y").WithLocation(2, 21) + ); + + comp = CreateCompilation(new[] { src2, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics( + // (2,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct Point { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(2, 8) + ); + + comp = CreateCompilation(new[] { src3, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics( + // (2,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(2, 8) + ); + + comp = CreateCompilation(new[] { src1, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics( + // (2,13): error CS1514: { expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(2, 13), + // (2,13): error CS1513: } expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(2, 13), + // (2,13): error CS8803: Top-level statements must precede namespace and type declarations. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "(int x, int y);").WithLocation(2, 13), + // (2,13): error CS8805: Program using top-level statements must be an executable. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_SimpleProgramNotAnExecutable, "(int x, int y);").WithLocation(2, 13), + // (2,13): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_IllegalStatement, "(int x, int y)").WithLocation(2, 13), + // (2,14): error CS8185: A declaration is not allowed in this context. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int x").WithLocation(2, 14), + // (2,14): error CS0165: Use of unassigned local variable 'x' + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_UseDefViolation, "int x").WithArguments("x").WithLocation(2, 14), + // (2,21): error CS8185: A declaration is not allowed in this context. + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int y").WithLocation(2, 21), + // (2,21): error CS0165: Use of unassigned local variable 'y' + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_UseDefViolation, "int y").WithArguments("y").WithLocation(2, 21) + ); + + comp = CreateCompilation(new[] { src2, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics(); + + comp = CreateCompilation(new[] { src3, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics(); + } + + [Fact] + public void RecordStructLanguageVersion_Nested() + { + var src1 = @" +class C +{ + struct Point(int x, int y); +} +"; + var src2 = @" +class D +{ + record struct Point { } +} +"; + var src3 = @" +struct E +{ + record struct Point(int x, int y); +} +"; + var src4 = @" +namespace NS +{ + record struct Point { } +} +"; + var comp = CreateCompilation(src1, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (4,17): error CS1514: { expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(4, 17), + // (4,17): error CS1513: } expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(4, 17), + // (4,31): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 31), + // (4,31): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 31) + ); + + comp = CreateCompilation(new[] { src2, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (4,12): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct Point { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(4, 12) + ); + + comp = CreateCompilation(new[] { src3, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (4,12): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(4, 12) + ); + + comp = CreateCompilation(src4, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (4,12): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct Point { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(4, 12) + ); + + comp = CreateCompilation(src1); + comp.VerifyDiagnostics( + // (4,17): error CS1514: { expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(4, 17), + // (4,17): error CS1513: } expected + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(4, 17), + // (4,31): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 31), + // (4,31): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // struct Point(int x, int y); + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 31) + ); + + comp = CreateCompilation(src2); + comp.VerifyDiagnostics(); + + comp = CreateCompilation(src3); + comp.VerifyDiagnostics(); + + comp = CreateCompilation(src4); + comp.VerifyDiagnostics(); + } + + [Fact] + public void TypeDeclaration_IsStruct() + { + var src = @" +record struct Point(int x, int y); +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + CompileAndVerify(comp, symbolValidator: validateModule, sourceSymbolValidator: validateModule); + Assert.True(SyntaxFacts.IsTypeDeclaration(SyntaxKind.RecordStructDeclaration)); + + static void validateModule(ModuleSymbol module) + { + var isSourceSymbol = module is SourceModuleSymbol; + + var point = module.GlobalNamespace.GetTypeMember("Point"); + Assert.True(point.IsValueType); + Assert.False(point.IsReferenceType); + Assert.False(point.IsRecord); + Assert.Equal(TypeKind.Struct, point.TypeKind); + Assert.Equal(SpecialType.System_ValueType, point.BaseTypeNoUseSiteDiagnostics.SpecialType); + Assert.Equal("Point", point.ToTestDisplayString()); + + if (isSourceSymbol) + { + Assert.True(point is SourceNamedTypeSymbol); + Assert.True(point.IsRecordStruct); + Assert.True(point.GetPublicSymbol().IsRecord); + Assert.Equal("record struct Point", point.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword))); + } + else + { + Assert.True(point is PENamedTypeSymbol); + Assert.False(point.IsRecordStruct); + Assert.False(point.GetPublicSymbol().IsRecord); + Assert.Equal("struct Point", point.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword))); + } + } + } + + [Fact] + public void TypeDeclaration_IsStruct_InConstraints() + { + var src = @" +record struct Point(int x, int y); + +class C where T : struct +{ + void M(C c) { } +} + +class C2 where T : new() +{ + void M(C2 c) { } +} + +class C3 where T : class +{ + void M(C3 c) { } // 1 +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (16,22): error CS0452: The type 'Point' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C3' + // void M(C3 c) { } // 1 + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "c").WithArguments("C3", "T", "Point").WithLocation(16, 22) + ); + } + + [Fact] + public void TypeDeclaration_IsStruct_Unmanaged() + { + var src = @" +record struct Point(int x, int y); +record struct Point2(string x, string y); + +class C where T : unmanaged +{ + void M(C c) { } + void M2(C c) { } // 1 +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (8,23): error CS8377: The type 'Point2' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'C' + // void M2(C c) { } // 1 + Diagnostic(ErrorCode.ERR_UnmanagedConstraintNotSatisfied, "c").WithArguments("C", "T", "Point2").WithLocation(8, 23) + ); + } + + [Fact] + public void IsRecord_Generic() + { + var src = @" +record struct Point(T x, T y); +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + CompileAndVerify(comp, symbolValidator: validateModule, sourceSymbolValidator: validateModule); + + static void validateModule(ModuleSymbol module) + { + var isSourceSymbol = module is SourceModuleSymbol; + + var point = module.GlobalNamespace.GetTypeMember("Point"); + Assert.True(point.IsValueType); + Assert.False(point.IsReferenceType); + Assert.False(point.IsRecord); + Assert.Equal(TypeKind.Struct, point.TypeKind); + Assert.Equal(SpecialType.System_ValueType, point.BaseTypeNoUseSiteDiagnostics.SpecialType); + Assert.True(SyntaxFacts.IsTypeDeclaration(SyntaxKind.RecordStructDeclaration)); + + if (isSourceSymbol) + { + Assert.True(point is SourceNamedTypeSymbol); + Assert.True(point.IsRecordStruct); + Assert.True(point.GetPublicSymbol().IsRecord); + } + else + { + Assert.True(point is PENamedTypeSymbol); + Assert.False(point.IsRecordStruct); + Assert.False(point.GetPublicSymbol().IsRecord); + } + } + } + + [Fact] + public void IsRecord_Retargeting() + { + var src = @" +public record struct Point(int x, int y); +"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Mscorlib40); + var comp2 = CreateCompilation("", targetFramework: TargetFramework.Mscorlib46, references: new[] { comp.ToMetadataReference() }); + var point = comp2.GlobalNamespace.GetTypeMember("Point"); + + Assert.Equal("Point", point.ToTestDisplayString()); + Assert.IsType(point); + Assert.True(point.IsRecordStruct); + Assert.True(point.GetPublicSymbol().IsRecord); + } + + [Fact] + public void IsRecord_AnonymousType() + { + var src = @" +class C +{ + void M() + { + var x = new { X = 1 }; + } +} +"; + var comp = CreateCompilation(src); + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var creation = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetTypeInfo(creation).Type!; + + Assert.Equal("", type.ToTestDisplayString()); + Assert.IsType(((Symbols.PublicModel.NonErrorNamedTypeSymbol)type).UnderlyingNamedTypeSymbol); + Assert.False(type.IsRecord); + } + + [Fact] + public void IsRecord_ErrorType() + { + var src = @" +class C +{ + Error M() => throw null; +} +"; + var comp = CreateCompilation(src); + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var method = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetDeclaredSymbol(method)!.ReturnType; + + Assert.Equal("Error", type.ToTestDisplayString()); + Assert.IsType(((Symbols.PublicModel.ErrorTypeSymbol)type).UnderlyingNamedTypeSymbol); + Assert.False(type.IsRecord); + } + + [Fact] + public void IsRecord_Pointer() + { + var src = @" +class C +{ + int* M() => throw null; +} +"; + var comp = CreateCompilation(src, options: TestOptions.UnsafeReleaseDll); + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var method = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetDeclaredSymbol(method)!.ReturnType; + + Assert.Equal("System.Int32*", type.ToTestDisplayString()); + Assert.IsType(((Symbols.PublicModel.PointerTypeSymbol)type).UnderlyingTypeSymbol); + Assert.False(type.IsRecord); + } + + [Fact] + public void IsRecord_Dynamic() + { + var src = @" +class C +{ + void M(dynamic d) + { + } +} +"; + var comp = CreateCompilation(src); + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var method = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetDeclaredSymbol(method)!.GetParameterType(0); + + Assert.Equal("dynamic", type.ToTestDisplayString()); + Assert.IsType(((Symbols.PublicModel.DynamicTypeSymbol)type).UnderlyingTypeSymbol); + Assert.False(type.IsRecord); + } + + [Fact] + public void TypeDeclaration_MayNotHaveBaseType() + { + var src = @" +record struct Point(int x, int y) : object; +record struct Point2(int x, int y) : System.ValueType; +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,37): error CS0527: Type 'object' in interface list is not an interface + // record struct Point(int x, int y) : object; + Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "object").WithArguments("object").WithLocation(2, 37), + // (3,38): error CS0527: Type 'ValueType' in interface list is not an interface + // record struct Point2(int x, int y) : System.ValueType; + Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "System.ValueType").WithArguments("System.ValueType").WithLocation(3, 38) + ); + } + + [Fact] + public void TypeDeclaration_MayNotHaveTypeConstraintsWithoutTypeParameters() + { + var src = @" +record struct Point(int x, int y) where T : struct; +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,35): error CS0080: Constraints are not allowed on non-generic declarations + // record struct Point(int x, int y) where T : struct; + Diagnostic(ErrorCode.ERR_ConstraintOnlyAllowedOnGenericDecl, "where").WithLocation(2, 35) + ); + } + + [Fact] + public void TypeDeclaration_AllowedModifiers() + { + var src = @" +readonly partial record struct S1; +public record struct S2; +internal record struct S3; + +public class Base +{ + public int S6; +} +public class C : Base +{ + private protected record struct S4; + protected internal record struct S5; + new record struct S6; +} +unsafe record struct S7; +"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeDebugDll); + comp.VerifyDiagnostics(); + Assert.Equal(Accessibility.Internal, comp.GlobalNamespace.GetTypeMember("S1").DeclaredAccessibility); + Assert.Equal(Accessibility.Public, comp.GlobalNamespace.GetTypeMember("S2").DeclaredAccessibility); + Assert.Equal(Accessibility.Internal, comp.GlobalNamespace.GetTypeMember("S3").DeclaredAccessibility); + Assert.Equal(Accessibility.ProtectedAndInternal, comp.GlobalNamespace.GetTypeMember("C").GetTypeMember("S4").DeclaredAccessibility); + Assert.Equal(Accessibility.ProtectedOrInternal, comp.GlobalNamespace.GetTypeMember("C").GetTypeMember("S5").DeclaredAccessibility); + } + + [Fact] + public void TypeDeclaration_DisallowedModifiers() + { + var src = @" +abstract record struct S1; +volatile record struct S2; +extern record struct S3; +virtual record struct S4; +override record struct S5; +async record struct S6; +ref record struct S7; +unsafe record struct S8; +static record struct S9; +sealed record struct S10; +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,24): error CS0106: The modifier 'abstract' is not valid for this item + // abstract record struct S1; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S1").WithArguments("abstract").WithLocation(2, 24), + // (3,24): error CS0106: The modifier 'volatile' is not valid for this item + // volatile record struct S2; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S2").WithArguments("volatile").WithLocation(3, 24), + // (4,22): error CS0106: The modifier 'extern' is not valid for this item + // extern record struct S3; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S3").WithArguments("extern").WithLocation(4, 22), + // (5,23): error CS0106: The modifier 'virtual' is not valid for this item + // virtual record struct S4; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S4").WithArguments("virtual").WithLocation(5, 23), + // (6,24): error CS0106: The modifier 'override' is not valid for this item + // override record struct S5; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S5").WithArguments("override").WithLocation(6, 24), + // (7,21): error CS0106: The modifier 'async' is not valid for this item + // async record struct S6; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S6").WithArguments("async").WithLocation(7, 21), + // (8,19): error CS0106: The modifier 'ref' is not valid for this item + // ref record struct S7; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S7").WithArguments("ref").WithLocation(8, 19), + // (9,22): error CS0227: Unsafe code may only appear if compiling with /unsafe + // unsafe record struct S8; + Diagnostic(ErrorCode.ERR_IllegalUnsafe, "S8").WithLocation(9, 22), + // (10,22): error CS0106: The modifier 'static' is not valid for this item + // static record struct S9; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S9").WithArguments("static").WithLocation(10, 22), + // (11,22): error CS0106: The modifier 'sealed' is not valid for this item + // sealed record struct S10; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "S10").WithArguments("sealed").WithLocation(11, 22) + ); + } + + [Fact] + public void TypeDeclaration_DuplicatesModifiers() + { + var src = @" +public public record struct S2; +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,8): error CS1004: Duplicate 'public' modifier + // public public record struct S2; + Diagnostic(ErrorCode.ERR_DuplicateModifier, "public").WithArguments("public").WithLocation(2, 8) + ); + } + + [Fact] + public void TypeDeclaration_BeforeTopLevelStatement() + { + var src = @" +record struct S; +System.Console.WriteLine(); +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (3,1): error CS8803: Top-level statements must precede namespace and type declarations. + // System.Console.WriteLine(); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "System.Console.WriteLine();").WithLocation(3, 1) + ); + } + + [Fact] + public void TypeDeclaration_WithTypeParameters() + { + var src = @" +S local = default; +local.ToString(); + +record struct S; +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + Assert.Equal(new[] { "T" }, comp.GlobalNamespace.GetTypeMember("S").TypeParameters.ToTestDisplayStrings()); + } + + [Fact] + public void TypeDeclaration_AllowedModifiersForMembers() + { + var src = @" +record struct S +{ + protected int Property { get; set; } // 1 + internal protected string field; // 2, 3 + abstract void M(); // 4 + virtual void M2() { } // 5 +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,19): error CS0666: 'S.Property': new protected member declared in struct + // protected int Property { get; set; } // 1 + Diagnostic(ErrorCode.ERR_ProtectedInStruct, "Property").WithArguments("S.Property").WithLocation(4, 19), + // (5,31): error CS0666: 'S.field': new protected member declared in struct + // internal protected string field; // 2, 3 + Diagnostic(ErrorCode.ERR_ProtectedInStruct, "field").WithArguments("S.field").WithLocation(5, 31), + // (5,31): warning CS0649: Field 'S.field' is never assigned to, and will always have its default value null + // internal protected string field; // 2, 3 + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("S.field", "null").WithLocation(5, 31), + // (6,19): error CS0621: 'S.M()': virtual or abstract members cannot be private + // abstract void M(); // 4 + Diagnostic(ErrorCode.ERR_VirtualPrivate, "M").WithArguments("S.M()").WithLocation(6, 19), + // (7,18): error CS0621: 'S.M2()': virtual or abstract members cannot be private + // virtual void M2() { } // 5 + Diagnostic(ErrorCode.ERR_VirtualPrivate, "M2").WithArguments("S.M2()").WithLocation(7, 18) + ); + } + + [Fact] + public void TypeDeclaration_ImplementInterface() + { + var src = @" +I i = (I)default(S); +System.Console.Write(i.M(""four"")); + +I i2 = (I)default(S2); +System.Console.Write(i2.M(""four"")); + +interface I +{ + int M(string s); +} +public record struct S : I +{ + public int M(string s) + => s.Length; +} +public record struct S2 : I +{ + int I.M(string s) + => s.Length + 1; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + CompileAndVerify(comp, expectedOutput: "45"); + + AssertEx.Equal(new[] { + "System.Int32 S.M(System.String s)", + "System.String S.ToString()", + "System.Boolean S.PrintMembers(System.Text.StringBuilder builder)", + "System.Boolean S.op_Inequality(S left, S right)", + "System.Boolean S.op_Equality(S left, S right)", + "System.Int32 S.GetHashCode()", + "System.Boolean S.Equals(System.Object obj)", + "System.Boolean S.Equals(S other)", + "S..ctor()" }, + comp.GetMember("S").GetMembers().ToTestDisplayStrings()); + } + + [Fact] + public void TypeDeclaration_SatisfiesStructConstraint() + { + var src = @" +S s = default; +System.Console.Write(M(s)); + +static int M(T t) where T : struct, I + => t.Property; + +public interface I +{ + int Property { get; } +} +public record struct S : I +{ + public int Property => 42; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + CompileAndVerify(comp, expectedOutput: "42"); + } + + [Fact] + public void TypeDeclaration_AccessingThis() + { + var src = @" +S s = new S(); +System.Console.Write(s.M()); + +public record struct S +{ + public int Property => 42; + + public int M() + => this.Property; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("S.M", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""int S.Property.get"" + IL_0006: ret +} +"); + } + + [Fact] + public void TypeDeclaration_NoBaseInitializer() + { + var src = @" +public record struct S +{ + public S(int i) : base() { } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,12): error CS0522: 'S': structs cannot call base class constructors + // public S(int i) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S").WithArguments("S").WithLocation(4, 12) + ); + } + + [Fact] + public void TypeDeclaration_NoParameterlessConstructor() + { + var src = @" +public record struct S +{ + public S() { } +} +"; + // This will be allowed in C# 10 + // Tracking issue https://github.com/dotnet/roslyn/issues/52240 + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,12): error CS0568: Structs cannot contain explicit parameterless constructors + // public S() { } + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S").WithLocation(4, 12) + ); + } + + [Fact] + public void TypeDeclaration_NoInstanceInitializers() + { + var src = @" +public record struct S +{ + public int field = 42; + public int Property { get; set; } = 43; +} +"; + // This will be allowed in C# 10, or we need to improve the message + // Tracking issue https://github.com/dotnet/roslyn/issues/52240 + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,16): error CS0573: 'S': cannot have instance property or field initializers in structs + // public int field = 42; + Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "field").WithArguments("S").WithLocation(4, 16), + // (5,16): error CS0573: 'S': cannot have instance property or field initializers in structs + // public int Property { get; set; } = 43; + Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "Property").WithArguments("S").WithLocation(5, 16) + ); + } + + [Fact] + public void TypeDeclaration_NoDestructor() + { + var src = @" +public record struct S +{ + ~S() { } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,6): error CS0575: Only class types can contain destructors + // ~S() { } + Diagnostic(ErrorCode.ERR_OnlyClassesCanContainDestructors, "S").WithArguments("S.~S()").WithLocation(4, 6) + ); + } + + [Fact] + public void TypeDeclaration_DifferentPartials() + { + var src = @" +partial record struct S1; +partial struct S1 { } + +partial struct S2 { } +partial record struct S2; + +partial record struct S3; +partial record S3 { } + +partial record struct S4; +partial record class S4 { } + +partial record struct S5; +partial class S5 { } + +partial record struct S6; +partial interface S6 { } + +partial record class C1; +partial struct C1 { } + +partial record class C2; +partial record struct C2 { } + +partial record class C3 { } +partial record C3; + +partial record class C4; +partial class C4 { } + +partial record class C5; +partial interface C5 { } +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (3,16): error CS0261: Partial declarations of 'S1' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial struct S1 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "S1").WithArguments("S1").WithLocation(3, 16), + // (6,23): error CS0261: Partial declarations of 'S2' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial record struct S2; + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "S2").WithArguments("S2").WithLocation(6, 23), + // (9,16): error CS0261: Partial declarations of 'S3' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial record S3 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "S3").WithArguments("S3").WithLocation(9, 16), + // (12,22): error CS0261: Partial declarations of 'S4' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial record class S4 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "S4").WithArguments("S4").WithLocation(12, 22), + // (15,15): error CS0261: Partial declarations of 'S5' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial class S5 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "S5").WithArguments("S5").WithLocation(15, 15), + // (18,19): error CS0261: Partial declarations of 'S6' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial interface S6 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "S6").WithArguments("S6").WithLocation(18, 19), + // (21,16): error CS0261: Partial declarations of 'C1' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial struct C1 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "C1").WithArguments("C1").WithLocation(21, 16), + // (24,23): error CS0261: Partial declarations of 'C2' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial record struct C2 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "C2").WithArguments("C2").WithLocation(24, 23), + // (30,15): error CS0261: Partial declarations of 'C4' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial class C4 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "C4").WithArguments("C4").WithLocation(30, 15), + // (33,19): error CS0261: Partial declarations of 'C5' must be all classes, all record classes, all structs, all record structs, or all interfaces + // partial interface C5 { } + Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "C5").WithArguments("C5").WithLocation(33, 19) + ); + } + + [Fact] + public void PartialRecord_OnlyOnePartialHasParameterList() + { + var src = @" +partial record struct S(int i); +partial record struct S(int i); + +partial record struct S2(int i); +partial record struct S2(); + +partial record struct S3(); +partial record struct S3(); +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (3,24): error CS8863: Only a single record partial declaration may have a parameter list + // partial record struct S(int i); + Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "(int i)").WithLocation(3, 24), + // (6,25): error CS8863: Only a single record partial declaration may have a parameter list + // partial record struct S2(); + Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(6, 25), + // (6,25): error CS0568: Structs cannot contain explicit parameterless constructors + // partial record struct S2(); + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(6, 25), + // (8,25): error CS0568: Structs cannot contain explicit parameterless constructors + // partial record struct S3(); + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(8, 25), + // (9,25): error CS8863: Only a single record partial declaration may have a parameter list + // partial record struct S3(); + Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(9, 25), + // (9,25): error CS0568: Structs cannot contain explicit parameterless constructors + // partial record struct S3(); + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(9, 25) + ); + } + + [Fact] + public void PartialRecord_ParametersInScopeOfBothParts() + { + var src = @" +var c = new C(2); +System.Console.Write((c.P1, c.P2)); + +public partial record struct C(int X) +{ + public int P1 { get; set; } = X; +} +public partial record struct C +{ + public int P2 { get; set; } = X; +} +"; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "(2, 2)", verify: Verification.Skipped /* init-only */) + .VerifyDiagnostics( + // (5,30): warning CS0282: There is no defined ordering between fields in multiple declarations of partial struct 'C'. To specify an ordering, all instance fields must be in the same declaration. + // public partial record struct C(int X) + Diagnostic(ErrorCode.WRN_SequentialOnPartialClass, "C").WithArguments("C").WithLocation(5, 30) + ); + } + + [Fact] + public void PartialRecord_DuplicateMemberNames() + { + var src = @" +public partial record struct C(int X) +{ + public void M(int i) { } +} +public partial record struct C +{ + public void M(string s) { } +} +"; + var comp = CreateCompilation(src); + var expectedMemberNames = new string[] + { + ".ctor", + "k__BackingField", + "get_X", + "set_X", + "X", + "M", + "M", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "Deconstruct", + ".ctor", + }; + AssertEx.Equal(expectedMemberNames, comp.GetMember("C").GetPublicSymbol().MemberNames); + } + + [Fact] + public void RecordInsideGenericType() + { + var src = @" +var c = new C.Nested(2); +System.Console.Write(c.T); + +public class C +{ + public record struct Nested(T T); +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "2"); + } + + [Fact] + public void PositionalMemberModifiers_RefOrOut() + { + var src = @" +record struct R(ref int P1, out int P2); +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,15): error CS0177: The out parameter 'P2' must be assigned to before control leaves the current method + // record struct R(ref int P1, out int P2); + Diagnostic(ErrorCode.ERR_ParamUnassigned, "R").WithArguments("P2").WithLocation(2, 15), + // (2,17): error CS0631: ref and out are not valid in this context + // record struct R(ref int P1, out int P2); + Diagnostic(ErrorCode.ERR_IllegalRefParam, "ref").WithLocation(2, 17), + // (2,29): error CS0631: ref and out are not valid in this context + // record struct R(ref int P1, out int P2); + Diagnostic(ErrorCode.ERR_IllegalRefParam, "out").WithLocation(2, 29) + ); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberModifiers_This() + { + var src = @" +record struct R(this int i); +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,17): error CS0027: Keyword 'this' is not available in the current context + // record struct R(this int i); + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(2, 17) + ); + } + + [Fact, WorkItem(45591, "https://github.com/dotnet/roslyn/issues/45591")] + public void Clone_DisallowedInSource() + { + var src = @" +record struct C1(string Clone); // 1 +record struct C2 +{ + string Clone; // 2 +} +record struct C3 +{ + string Clone { get; set; } // 3 +} +record struct C5 +{ + void Clone() { } // 4 + void Clone(int i) { } // 5 +} +record struct C6 +{ + class Clone { } // 6 +} +record struct C7 +{ + delegate void Clone(); // 7 +} +record struct C8 +{ + event System.Action Clone; // 8 +} +record struct Clone +{ + Clone(int i) => throw null; +} +record struct C9 : System.ICloneable +{ + object System.ICloneable.Clone() => throw null; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,25): error CS8859: Members named 'Clone' are disallowed in records. + // record struct C1(string Clone); // 1 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(2, 25), + // (5,12): error CS8859: Members named 'Clone' are disallowed in records. + // string Clone; // 2 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(5, 12), + // (5,12): warning CS0169: The field 'C2.Clone' is never used + // string Clone; // 2 + Diagnostic(ErrorCode.WRN_UnreferencedField, "Clone").WithArguments("C2.Clone").WithLocation(5, 12), + // (9,12): error CS8859: Members named 'Clone' are disallowed in records. + // string Clone { get; set; } // 3 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(9, 12), + // (13,10): error CS8859: Members named 'Clone' are disallowed in records. + // void Clone() { } // 4 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(13, 10), + // (14,10): error CS8859: Members named 'Clone' are disallowed in records. + // void Clone(int i) { } // 5 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(14, 10), + // (18,11): error CS8859: Members named 'Clone' are disallowed in records. + // class Clone { } // 6 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(18, 11), + // (22,19): error CS8859: Members named 'Clone' are disallowed in records. + // delegate void Clone(); // 7 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(22, 19), + // (26,25): error CS8859: Members named 'Clone' are disallowed in records. + // event System.Action Clone; // 8 + Diagnostic(ErrorCode.ERR_CloneDisallowedInRecord, "Clone").WithLocation(26, 25), + // (26,25): warning CS0067: The event 'C8.Clone' is never used + // event System.Action Clone; // 8 + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "Clone").WithArguments("C8.Clone").WithLocation(26, 25) + ); + } + + [ConditionalFact(typeof(DesktopOnly), Reason = ConditionalSkipReason.RestrictedTypesNeedDesktop)] + [WorkItem(48115, "https://github.com/dotnet/roslyn/issues/48115")] + public void RestrictedTypesAndPointerTypes() + { + var src = @" +class C { } +static class C2 { } +ref struct RefLike{} + +unsafe record struct C( // 1 + int* P1, // 2 + int*[] P2, // 3 + C P3, + delegate* P4, // 4 + void P5, // 5 + C2 P6, // 6, 7 + System.ArgIterator P7, // 8 + System.TypedReference P8, // 9 + RefLike P9); // 10 +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (6,22): error CS0721: 'C2': static types cannot be used as parameters + // unsafe record struct C( // 1 + Diagnostic(ErrorCode.ERR_ParameterIsStaticClass, "C").WithArguments("C2").WithLocation(6, 22), + // (7,10): error CS8908: The type 'int*' may not be used for a field of a record. + // int* P1, // 2 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "P1").WithArguments("int*").WithLocation(7, 10), + // (8,12): error CS8908: The type 'int*[]' may not be used for a field of a record. + // int*[] P2, // 3 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "P2").WithArguments("int*[]").WithLocation(8, 12), + // (10,25): error CS8908: The type 'delegate*' may not be used for a field of a record. + // delegate* P4, // 4 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "P4").WithArguments("delegate*").WithLocation(10, 25), + // (11,5): error CS1536: Invalid parameter type 'void' + // void P5, // 5 + Diagnostic(ErrorCode.ERR_NoVoidParameter, "void").WithLocation(11, 5), + // (12,8): error CS0722: 'C2': static types cannot be used as return types + // C2 P6, // 6, 7 + Diagnostic(ErrorCode.ERR_ReturnTypeIsStaticClass, "P6").WithArguments("C2").WithLocation(12, 8), + // (12,8): error CS0721: 'C2': static types cannot be used as parameters + // C2 P6, // 6, 7 + Diagnostic(ErrorCode.ERR_ParameterIsStaticClass, "P6").WithArguments("C2").WithLocation(12, 8), + // (13,5): error CS0610: Field or property cannot be of type 'ArgIterator' + // System.ArgIterator P7, // 8 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(13, 5), + // (14,5): error CS0610: Field or property cannot be of type 'TypedReference' + // System.TypedReference P8, // 9 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.TypedReference").WithArguments("System.TypedReference").WithLocation(14, 5), + // (15,5): error CS8345: Field or auto-implemented property cannot be of type 'RefLike' unless it is an instance member of a ref struct. + // RefLike P9); // 10 + Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "RefLike").WithArguments("RefLike").WithLocation(15, 5) + ); + } + + [ConditionalFact(typeof(DesktopOnly), Reason = ConditionalSkipReason.RestrictedTypesNeedDesktop)] + [WorkItem(48115, "https://github.com/dotnet/roslyn/issues/48115")] + public void RestrictedTypesAndPointerTypes_NominalMembers() + { + var src = @" +public class C { } +public static class C2 { } +public ref struct RefLike{} + +public unsafe record struct C +{ + public int* f1; // 1 + public int*[] f2; // 2 + public C f3; + public delegate* f4; // 3 + public void f5; // 4 + public C2 f6; // 5 + public System.ArgIterator f7; // 6 + public System.TypedReference f8; // 7 + public RefLike f9; // 8 +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (8,17): error CS8908: The type 'int*' may not be used for a field of a record. + // public int* f1; // 1 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "f1").WithArguments("int*").WithLocation(8, 17), + // (9,19): error CS8908: The type 'int*[]' may not be used for a field of a record. + // public int*[] f2; // 2 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "f2").WithArguments("int*[]").WithLocation(9, 19), + // (11,32): error CS8908: The type 'delegate*' may not be used for a field of a record. + // public delegate* f4; // 3 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "f4").WithArguments("delegate*").WithLocation(11, 32), + // (12,12): error CS0670: Field cannot have void type + // public void f5; // 4 + Diagnostic(ErrorCode.ERR_FieldCantHaveVoidType, "void").WithLocation(12, 12), + // (13,15): error CS0723: Cannot declare a variable of static type 'C2' + // public C2 f6; // 5 + Diagnostic(ErrorCode.ERR_VarDeclIsStaticClass, "f6").WithArguments("C2").WithLocation(13, 15), + // (14,12): error CS0610: Field or property cannot be of type 'ArgIterator' + // public System.ArgIterator f7; // 6 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(14, 12), + // (15,12): error CS0610: Field or property cannot be of type 'TypedReference' + // public System.TypedReference f8; // 7 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.TypedReference").WithArguments("System.TypedReference").WithLocation(15, 12), + // (16,12): error CS8345: Field or auto-implemented property cannot be of type 'RefLike' unless it is an instance member of a ref struct. + // public RefLike f9; // 8 + Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "RefLike").WithArguments("RefLike").WithLocation(16, 12) + ); + } + + [ConditionalFact(typeof(DesktopOnly), Reason = ConditionalSkipReason.RestrictedTypesNeedDesktop)] + [WorkItem(48115, "https://github.com/dotnet/roslyn/issues/48115")] + public void RestrictedTypesAndPointerTypes_NominalMembers_AutoProperties() + { + var src = @" +public class C { } +public static class C2 { } +public ref struct RefLike{} + +public unsafe record struct C +{ + public int* f1 { get; set; } // 1 + public int*[] f2 { get; set; } // 2 + public C f3 { get; set; } + public delegate* f4 { get; set; } // 3 + public void f5 { get; set; } // 4 + public C2 f6 { get; set; } // 5, 6 + public System.ArgIterator f7 { get; set; } // 6 + public System.TypedReference f8 { get; set; } // 7 + public RefLike f9 { get; set; } // 8 +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (8,17): error CS8908: The type 'int*' may not be used for a field of a record. + // public int* f1 { get; set; } // 1 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "f1").WithArguments("int*").WithLocation(8, 17), + // (9,19): error CS8908: The type 'int*[]' may not be used for a field of a record. + // public int*[] f2 { get; set; } // 2 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "f2").WithArguments("int*[]").WithLocation(9, 19), + // (11,32): error CS8908: The type 'delegate*' may not be used for a field of a record. + // public delegate* f4 { get; set; } // 3 + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "f4").WithArguments("delegate*").WithLocation(11, 32), + // (12,17): error CS0547: 'C.f5': property or indexer cannot have void type + // public void f5 { get; set; } // 4 + Diagnostic(ErrorCode.ERR_PropertyCantHaveVoidType, "f5").WithArguments("C.f5").WithLocation(12, 17), + // (13,20): error CS0722: 'C2': static types cannot be used as return types + // public C2 f6 { get; set; } // 5, 6 + Diagnostic(ErrorCode.ERR_ReturnTypeIsStaticClass, "get").WithArguments("C2").WithLocation(13, 20), + // (13,25): error CS0721: 'C2': static types cannot be used as parameters + // public C2 f6 { get; set; } // 5, 6 + Diagnostic(ErrorCode.ERR_ParameterIsStaticClass, "set").WithArguments("C2").WithLocation(13, 25), + // (14,12): error CS0610: Field or property cannot be of type 'ArgIterator' + // public System.ArgIterator f7 { get; set; } // 6 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(14, 12), + // (15,12): error CS0610: Field or property cannot be of type 'TypedReference' + // public System.TypedReference f8 { get; set; } // 7 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.TypedReference").WithArguments("System.TypedReference").WithLocation(15, 12), + // (16,12): error CS8345: Field or auto-implemented property cannot be of type 'RefLike' unless it is an instance member of a ref struct. + // public RefLike f9 { get; set; } // 8 + Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "RefLike").WithArguments("RefLike").WithLocation(16, 12) + ); + } + + [Fact] + [WorkItem(48115, "https://github.com/dotnet/roslyn/issues/48115")] + public void RestrictedTypesAndPointerTypes_PointerTypeAllowedForParameterAndProperty() + { + var src = @" +class C { } + +unsafe record struct C(int* P1, int*[] P2, C P3) +{ + int* P1 + { + get { System.Console.Write(""P1 ""); return null; } + init { } + } + int*[] P2 + { + get { System.Console.Write(""P2 ""); return null; } + init { } + } + C P3 + { + get { System.Console.Write(""P3 ""); return null; } + init { } + } + + public unsafe static void Main() + { + var x = new C(null, null, null); + var (x1, x2, x3) = x; + System.Console.Write(""RAN""); + } +} +"; + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.UnsafeDebugExe); + comp.VerifyEmitDiagnostics( + // (4,29): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? + // unsafe record struct C(int* P1, int*[] P2, C P3) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(4, 29), + // (4,40): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? + // unsafe record struct C(int* P1, int*[] P2, C P3) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(4, 40), + // (4,54): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? + // unsafe record struct C(int* P1, int*[] P2, C P3) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P3").WithArguments("P3").WithLocation(4, 54) + ); + + CompileAndVerify(comp, expectedOutput: "P1 P2 P3 RAN", verify: Verification.Skipped /* pointers */); + } + + [ConditionalFact(typeof(DesktopOnly), Reason = ConditionalSkipReason.RestrictedTypesNeedDesktop)] + [WorkItem(48115, "https://github.com/dotnet/roslyn/issues/48115")] + public void RestrictedTypesAndPointerTypes_StaticFields() + { + var src = @" +public class C { } +public static class C2 { } +public ref struct RefLike{} + +public unsafe record C +{ + public static int* f1; + public static int*[] f2; + public static C f3; + public static delegate* f4; + public static C2 f6; // 1 + public static System.ArgIterator f7; // 2 + public static System.TypedReference f8; // 3 + public static RefLike f9; // 4 +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (12,22): error CS0723: Cannot declare a variable of static type 'C2' + // public static C2 f6; // 1 + Diagnostic(ErrorCode.ERR_VarDeclIsStaticClass, "f6").WithArguments("C2").WithLocation(12, 22), + // (13,19): error CS0610: Field or property cannot be of type 'ArgIterator' + // public static System.ArgIterator f7; // 2 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(13, 19), + // (14,19): error CS0610: Field or property cannot be of type 'TypedReference' + // public static System.TypedReference f8; // 3 + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.TypedReference").WithArguments("System.TypedReference").WithLocation(14, 19), + // (15,19): error CS8345: Field or auto-implemented property cannot be of type 'RefLike' unless it is an instance member of a ref struct. + // public static RefLike f9; // 4 + Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "RefLike").WithArguments("RefLike").WithLocation(15, 19) + ); + } + + [Fact] + public void RecordProperties_01() + { + var src = @" +using System; +record struct C(int X, int Y) +{ + int Z = 345; + public static void Main() + { + var c = new C(1, 2); + Console.Write(c.X); + Console.Write(c.Y); + Console.Write(c.Z); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"12345").VerifyDiagnostics(); + + verifier.VerifyIL("C..ctor(int, int)", @" +{ + // Code size 26 (0x1a) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int C.k__BackingField"" + IL_0007: ldarg.0 + IL_0008: ldarg.2 + IL_0009: stfld ""int C.k__BackingField"" + IL_000e: ldarg.0 + IL_000f: ldc.i4 0x159 + IL_0014: stfld ""int C.Z"" + IL_0019: ret +} +"); + + var c = verifier.Compilation.GlobalNamespace.GetTypeMember("C"); + Assert.False(c.IsReadOnly); + var x = (IPropertySymbol)c.GetMember("X"); + Assert.Equal("readonly System.Int32 C.X.get", x.GetMethod.ToTestDisplayString()); + Assert.Equal("void C.X.set", x.SetMethod.ToTestDisplayString()); + Assert.False(x.SetMethod!.IsInitOnly); + + var xBackingField = (IFieldSymbol)c.GetMember("k__BackingField"); + Assert.Equal("System.Int32 C.k__BackingField", xBackingField.ToTestDisplayString()); + Assert.False(xBackingField.IsReadOnly); + } + + [Fact] + public void RecordProperties_01_EmptyParameterList() + { + // We will allow declaring parameterless constructors + // Tracking issue https://github.com/dotnet/roslyn/issues/52240 + var src = @" +using System; +record struct C() +{ + int Z = 345; + public static void Main() + { + var c = new C(); + Console.Write(c.Z); + } +}"; + CreateCompilation(src).VerifyEmitDiagnostics( + // (3,16): error CS0568: Structs cannot contain explicit parameterless constructors + // record struct C() + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(3, 16) + ); + } + + [Fact] + public void RecordProperties_01_Readonly() + { + var src = @" +using System; +readonly record struct C(int X, int Y) +{ + readonly int Z = 345; + public static void Main() + { + var c = new C(1, 2); + Console.Write(c.X); + Console.Write(c.Y); + Console.Write(c.Z); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"12345").VerifyDiagnostics(); + + var c = verifier.Compilation.GlobalNamespace.GetTypeMember("C"); + Assert.True(c.IsReadOnly); + var x = (IPropertySymbol)c.GetMember("X"); + Assert.Equal("System.Int32 C.X.get", x.GetMethod.ToTestDisplayString()); + Assert.Equal("void modreq(System.Runtime.CompilerServices.IsExternalInit) C.X.init", x.SetMethod.ToTestDisplayString()); + Assert.True(x.SetMethod!.IsInitOnly); + + var xBackingField = (IFieldSymbol)c.GetMember("k__BackingField"); + Assert.Equal("System.Int32 C.k__BackingField", xBackingField.ToTestDisplayString()); + Assert.True(xBackingField.IsReadOnly); + } + + [Fact] + public void RecordProperties_01_ReadonlyMismatch() + { + var src = @" +readonly record struct C(int X) +{ + public int X { get; set; } = X; // 1 +} +record struct C2(int X) +{ + public int X { get; init; } = X; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,16): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // public int X { get; set; } = X; // 1 + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "X").WithLocation(4, 16) + ); + } + + [Fact] + public void RecordProperties_02() + { + var src = @" +using System; +record struct C(int X, int Y) +{ + public C(int a, int b) + { + } + + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.X); + Console.WriteLine(c.Y); + } + + private int X1 = X; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (5,12): error CS0111: Type 'C' already defines a member called 'C' with the same parameter types + // public C(int a, int b) + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "C").WithArguments("C", "C").WithLocation(5, 12), + // (5,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public C(int a, int b) + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "C").WithLocation(5, 12), + // (11,21): error CS0121: The call is ambiguous between the following methods or properties: 'C.C(int, int)' and 'C.C(int, int)' + // var c = new C(1, 2); + Diagnostic(ErrorCode.ERR_AmbigCall, "C").WithArguments("C.C(int, int)", "C.C(int, int)").WithLocation(11, 21) + ); + } + + [Fact] + public void RecordProperties_03() + { + var src = @" +using System; +record struct C(int X, int Y) +{ + public int X { get; } + + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.X); + Console.WriteLine(c.Y); + } +}"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (3,15): error CS0843: Auto-implemented property 'C.X' must be fully assigned before control is returned to the caller. + // record struct C(int X, int Y) + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "C").WithArguments("C.X").WithLocation(3, 15), + // (3,21): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(int X, int Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(3, 21) + ); + } + + [Fact] + public void RecordProperties_03_InitializedWithY() + { + var src = @" +using System; +record struct C(int X, int Y) +{ + public int X { get; } = Y; + + public static void Main() + { + var c = new C(1, 2); + Console.Write(c.X); + Console.Write(c.Y); + } +}"; + CompileAndVerify(src, expectedOutput: "22") + .VerifyDiagnostics( + // (3,21): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(int X, int Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(3, 21) + ); + } + + [Fact] + public void RecordProperties_04() + { + var src = @" +using System; +record struct C(int X, int Y) +{ + public int X { get; } = 3; + + public static void Main() + { + var c = new C(1, 2); + Console.Write(c.X); + Console.Write(c.Y); + } +}"; + CompileAndVerify(src, expectedOutput: "32") + .VerifyDiagnostics( + // (3,21): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(int X, int Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(3, 21) + ); + } + + [Fact] + public void RecordProperties_05() + { + var src = @" +record struct C(int X, int X) +{ +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,28): error CS0100: The parameter name 'X' is a duplicate + // record struct C(int X, int X) + Diagnostic(ErrorCode.ERR_DuplicateParamName, "X").WithArguments("X").WithLocation(2, 28), + // (2,28): error CS0102: The type 'C' already contains a definition for 'X' + // record struct C(int X, int X) + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "X").WithArguments("C", "X").WithLocation(2, 28) + ); + + var expectedMembers = new[] + { + "System.Int32 C.X { get; set; }", + "System.Int32 C.X { get; set; }" + }; + AssertEx.Equal(expectedMembers, + comp.GetMember("C").GetMembers().OfType().ToTestDisplayStrings()); + + var expectedMemberNames = new[] { + ".ctor", + "k__BackingField", + "get_X", + "set_X", + "X", + "k__BackingField", + "get_X", + "set_X", + "X", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "Deconstruct", + ".ctor" + }; + AssertEx.Equal(expectedMemberNames, comp.GetMember("C").GetPublicSymbol().MemberNames); + } + + [Fact] + public void RecordProperties_06() + { + var src = @" +record struct C(int X, int Y) +{ + public void get_X() { } + public void set_X() { } + int get_Y(int value) => value; + int set_Y(int value) => value; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,21): error CS0082: Type 'C' already reserves a member called 'get_X' with the same parameter types + // record struct C(int X, int Y) + Diagnostic(ErrorCode.ERR_MemberReserved, "X").WithArguments("get_X", "C").WithLocation(2, 21), + // (2,28): error CS0082: Type 'C' already reserves a member called 'set_Y' with the same parameter types + // record struct C(int X, int Y) + Diagnostic(ErrorCode.ERR_MemberReserved, "Y").WithArguments("set_Y", "C").WithLocation(2, 28) + ); + + var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); + var expectedMembers = new[] + { + "C..ctor(System.Int32 X, System.Int32 Y)", + "System.Int32 C.k__BackingField", + "readonly System.Int32 C.X.get", + "void C.X.set", + "System.Int32 C.X { get; set; }", + "System.Int32 C.k__BackingField", + "readonly System.Int32 C.Y.get", + "void C.Y.set", + "System.Int32 C.Y { get; set; }", + "void C.get_X()", + "void C.set_X()", + "System.Int32 C.get_Y(System.Int32 value)", + "System.Int32 C.set_Y(System.Int32 value)", + "System.String C.ToString()", + "System.Boolean C.PrintMembers(System.Text.StringBuilder builder)", + "System.Boolean C.op_Inequality(C left, C right)", + "System.Boolean C.op_Equality(C left, C right)", + "System.Int32 C.GetHashCode()", + "System.Boolean C.Equals(System.Object obj)", + "System.Boolean C.Equals(C other)", + "void C.Deconstruct(out System.Int32 X, out System.Int32 Y)", + "C..ctor()", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void RecordProperties_07() + { + var comp = CreateCompilation(@" +record struct C1(object P, object get_P); +record struct C2(object get_P, object P);"); + comp.VerifyDiagnostics( + // (2,25): error CS0102: The type 'C1' already contains a definition for 'get_P' + // record struct C1(object P, object get_P); + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C1", "get_P").WithLocation(2, 25), + // (3,39): error CS0102: The type 'C2' already contains a definition for 'get_P' + // record struct C2(object get_P, object P); + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C2", "get_P").WithLocation(3, 39) + ); + } + + [Fact] + public void RecordProperties_08() + { + var comp = CreateCompilation(@" +record struct C1(object O1) +{ + public object O1 { get; } = O1; + public object O2 { get; } = O1; +}"); + comp.VerifyDiagnostics(); + } + + [Fact] + public void RecordProperties_09() + { + var src = @" +record struct C(object P1, object P2, object P3, object P4) +{ + class P1 { } + object P2 = 2; + int P3(object o) => 3; + int P4(T t) => 4; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,24): error CS0102: The type 'C' already contains a definition for 'P1' + // record struct C(object P1, object P2, object P3, object P4) + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P1").WithArguments("C", "P1").WithLocation(2, 24), + // (2,35): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(object P1, object P2, object P3, object P4) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(2, 35), + // (6,9): error CS0102: The type 'C' already contains a definition for 'P3' + // int P3(object o) => 3; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P3").WithArguments("C", "P3").WithLocation(6, 9), + // (7,9): error CS0102: The type 'C' already contains a definition for 'P4' + // int P4(T t) => 4; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P4").WithArguments("C", "P4").WithLocation(7, 9) + ); + } + + [Fact] + public void RecordProperties_10() + { + var src = @" +record struct C(object P) +{ + const int P = 4; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,24): error CS8866: Record member 'C.P' must be a readable instance property or field of type 'object' to match positional parameter 'P'. + // record struct C(object P) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("C.P", "object", "P").WithLocation(2, 24), + // (2,24): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(object P) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P").WithArguments("P").WithLocation(2, 24) + ); + } + + [Fact] + public void RecordProperties_11_UnreadPositionalParameter() + { + var comp = CreateCompilation(@" +record struct C1(object O1, object O2, object O3) // 1, 2 +{ + public object O1 { get; init; } + public object O2 { get; init; } = M(O2); + public object O3 { get; init; } = M(O3 = null); + private static object M(object o) => o; +} +"); + comp.VerifyDiagnostics( + // (2,15): error CS0843: Auto-implemented property 'C1.O1' must be fully assigned before control is returned to the caller. + // record struct C1(object O1, object O2, object O3) // 1, 2 + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "C1").WithArguments("C1.O1").WithLocation(2, 15), + // (2,25): warning CS8907: Parameter 'O1' is unread. Did you forget to use it to initialize the property with that name? + // record struct C1(object O1, object O2, object O3) // 1, 2 + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "O1").WithArguments("O1").WithLocation(2, 25), + // (2,47): warning CS8907: Parameter 'O3' is unread. Did you forget to use it to initialize the property with that name? + // record struct C1(object O1, object O2, object O3) // 1, 2 + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "O3").WithArguments("O3").WithLocation(2, 47) + ); + } + + [Fact] + public void RecordProperties_11_UnreadPositionalParameter_InRefOut() + { + var comp = CreateCompilation(@" +record struct C1(object O1, object O2, object O3) // 1 +{ + public object O1 { get; init; } = MIn(in O1); + public object O2 { get; init; } = MRef(ref O2); + public object O3 { get; init; } = MOut(out O3); + + static object MIn(in object o) => o; + static object MRef(ref object o) => o; + static object MOut(out object o) => throw null; +} +"); + comp.VerifyDiagnostics( + // (2,47): warning CS8907: Parameter 'O3' is unread. Did you forget to use it to initialize the property with that name? + // record struct C1(object O1, object O2, object O3) // 1 + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "O3").WithArguments("O3").WithLocation(2, 47) + ); + } + + [Fact] + public void RecordProperties_SelfContainedStruct() + { + var comp = CreateCompilation(@" +record struct C(C c); +"); + comp.VerifyDiagnostics( + // (2,19): error CS0523: Struct member 'C.c' of type 'C' causes a cycle in the struct layout + // record struct C(C c); + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "c").WithArguments("C.c", "C").WithLocation(2, 19) + ); + } + + [Fact] + public void RecordProperties_PropertyInValueType() + { + var corlib_cs = @" +namespace System +{ + public class Object + { + public virtual bool Equals(object x) => throw null; + public virtual int GetHashCode() => throw null; + public virtual string ToString() => throw null; + } + public class Exception { } + public class ValueType + { + public bool X { get; set; } + } + public class Attribute { } + public class String { } + public struct Void { } + public struct Boolean { } + public struct Int32 { } + public interface IEquatable { } +} +namespace System.Collections.Generic +{ + public abstract class EqualityComparer + { + public static EqualityComparer Default => throw null; + public abstract int GetHashCode(T t); + } +} +namespace System.Text +{ + public class StringBuilder + { + public StringBuilder Append(string s) => null; + public StringBuilder Append(object s) => null; + } +} +"; + var corlibRef = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + { + var src = @" +record struct C(bool X) +{ + bool M() + { + return X; + } +} +"; + var comp = CreateEmptyCompilation(src, parseOptions: TestOptions.RegularPreview, references: new[] { corlibRef }); + comp.VerifyEmitDiagnostics( + // (2,22): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(bool X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(2, 22) + ); + + Assert.Null(comp.GlobalNamespace.GetTypeMember("C").GetMember("X")); + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var x = tree.GetRoot().DescendantNodes().OfType().Single().Expression; + Assert.Equal("System.Boolean System.ValueType.X { get; set; }", model.GetSymbolInfo(x!).Symbol.ToTestDisplayString()); + } + + { + var src = @" +readonly record struct C(bool X) +{ + bool M() + { + return X; + } +} +"; + var comp = CreateEmptyCompilation(src, parseOptions: TestOptions.RegularPreview, references: new[] { corlibRef }); + comp.VerifyEmitDiagnostics( + // (2,31): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // readonly record struct C(bool X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(2, 31) + ); + + Assert.Null(comp.GlobalNamespace.GetTypeMember("C").GetMember("X")); + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var x = tree.GetRoot().DescendantNodes().OfType().Single().Expression; + Assert.Equal("System.Boolean System.ValueType.X { get; set; }", model.GetSymbolInfo(x!).Symbol.ToTestDisplayString()); + } + } + + [Fact] + public void RecordProperties_PropertyInValueType_Static() + { + var corlib_cs = @" +namespace System +{ + public class Object + { + public virtual bool Equals(object x) => throw null; + public virtual int GetHashCode() => throw null; + public virtual string ToString() => throw null; + } + public class Exception { } + public class ValueType + { + public static bool X { get; set; } + } + public class Attribute { } + public class String { } + public struct Void { } + public struct Boolean { } + public struct Int32 { } + public interface IEquatable { } +} +namespace System.Collections.Generic +{ + public abstract class EqualityComparer + { + public static EqualityComparer Default => throw null; + public abstract int GetHashCode(T t); + } +} +namespace System.Text +{ + public class StringBuilder + { + public StringBuilder Append(string s) => null; + public StringBuilder Append(object s) => null; + } +} +"; + var corlibRef = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + var src = @" +record struct C(bool X) +{ + bool M() + { + return X; + } +} +"; + var comp = CreateEmptyCompilation(src, parseOptions: TestOptions.RegularPreview, references: new[] { corlibRef }); + comp.VerifyEmitDiagnostics( + // (2,22): error CS8866: Record member 'System.ValueType.X' must be a readable instance property or field of type 'bool' to match positional parameter 'X'. + // record struct C(bool X) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("System.ValueType.X", "bool", "X").WithLocation(2, 22), + // (2,22): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(bool X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(2, 22) + ); + } + + [Fact] + public void StaticCtor() + { + var src = @" +record R(int x) +{ + static void Main() { } + + static R() + { + System.Console.Write(""static ctor""); + } +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "static ctor", verify: Verification.Skipped /* init-only */); + } + + [Fact] + public void StaticCtor_ParameterlessPrimaryCtor() + { + var src = @" +record struct R(int I) +{ + static R() { } +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void StaticCtor_CopyCtor() + { + var src = @" +record struct R(int I) +{ + static R(R r) { } +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,12): error CS0132: 'R.R(R)': a static constructor must be parameterless + // static R(R r) { } + Diagnostic(ErrorCode.ERR_StaticConstParam, "R").WithArguments("R.R(R)").WithLocation(4, 12) + ); + } + + [Fact] + public void InterfaceImplementation_NotReadonly() + { + var source = @" +I r = new R(42); +r.P2 = 43; +r.P3 = 44; +System.Console.Write((r.P1, r.P2, r.P3)); + +interface I +{ + int P1 { get; set; } + int P2 { get; set; } + int P3 { get; set; } +} +record struct R(int P1) : I +{ + public int P2 { get; set; } = 0; + int I.P3 { get; set; } = 0; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(42, 43, 44)"); + } + + [Fact] + public void InterfaceImplementation_NotReadonly_InitOnlyInterface() + { + var source = @" +interface I +{ + int P1 { get; init; } +} +record struct R(int P1) : I; +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,27): error CS8854: 'R' does not implement interface member 'I.P1.init'. 'R.P1.set' cannot implement 'I.P1.init'. + // record struct R(int P1) : I; + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberWrongInitOnly, "I").WithArguments("R", "I.P1.init", "R.P1.set").WithLocation(6, 27) + ); + } + + [Fact] + public void InterfaceImplementation_Readonly() + { + var source = @" +I r = new R(42) { P2 = 43 }; +System.Console.Write((r.P1, r.P2)); + +interface I +{ + int P1 { get; init; } + int P2 { get; init; } +} +readonly record struct R(int P1) : I +{ + public int P2 { get; init; } = 0; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(42, 43)", verify: Verification.Skipped /* init-only */); + } + + [Fact] + public void InterfaceImplementation_Readonly_SetInterface() + { + var source = @" +interface I +{ + int P1 { get; set; } +} +readonly record struct R(int P1) : I; +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,36): error CS8854: 'R' does not implement interface member 'I.P1.set'. 'R.P1.init' cannot implement 'I.P1.set'. + // readonly record struct R(int P1) : I; + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberWrongInitOnly, "I").WithArguments("R", "I.P1.set", "R.P1.init").WithLocation(6, 36) + ); + } + + [Fact] + public void InterfaceImplementation_Readonly_PrivateImplementation() + { + var source = @" +I r = new R(42) { P2 = 43, P3 = 44 }; +System.Console.Write((r.P1, r.P2, r.P3)); + +interface I +{ + int P1 { get; init; } + int P2 { get; init; } + int P3 { get; init; } +} +readonly record struct R(int P1) : I +{ + public int P2 { get; init; } = 0; + int I.P3 { get; init; } = 0; // not practically initializable +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,28): error CS0117: 'R' does not contain a definition for 'P3' + // I r = new R(42) { P2 = 43, P3 = 44 }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "P3").WithArguments("R", "P3").WithLocation(2, 28) + ); + } + + [Fact] + public void Initializers_01() + { + var src = @" +using System; + +record struct C(int X) +{ + int Z = X + 1; + + public static void Main() + { + var c = new C(1); + Console.WriteLine(c.Z); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"2").VerifyDiagnostics(); + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("= X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Parameter, symbol!.Kind); + Assert.Equal("System.Int32 X", symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X)", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + + var recordDeclaration = tree.GetRoot().DescendantNodes().OfType().Single(); + Assert.Equal("C", recordDeclaration.Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclaration)); + } + + [Fact] + public void Initializers_02() + { + var src = @" +record struct C(int X) +{ + static int Z = X + 1; +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,20): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C.X' + // static int Z = X + 1; + Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "X").WithArguments("C.X").WithLocation(4, 20) + ); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("= X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Property, symbol!.Kind); + Assert.Equal("System.Int32 C.X { get; set; }", symbol.ToTestDisplayString()); + Assert.Equal("C", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void Initializers_03() + { + var src = @" +record struct C(int X) +{ + const int Z = X + 1; +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,19): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C.X' + // const int Z = X + 1; + Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "X").WithArguments("C.X").WithLocation(4, 19) + ); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("= X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Property, symbol!.Kind); + Assert.Equal("System.Int32 C.X { get; set; }", symbol.ToTestDisplayString()); + Assert.Equal("C", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void Initializers_04() + { + var src = @" +using System; + +record struct C(int X) +{ + Func Z = () => X + 1; + + public static void Main() + { + var c = new C(1); + Console.WriteLine(c.Z()); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"2").VerifyDiagnostics(); + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("() => X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Parameter, symbol!.Kind); + Assert.Equal("System.Int32 X", symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X)", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("lambda expression", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void SynthesizedRecordPointerProperty() + { + var src = @" +record struct R(int P1, int* P2, delegate* P3);"; + + var comp = CreateCompilation(src); + var p = comp.GlobalNamespace.GetTypeMember("R").GetMember("P1"); + Assert.False(p.HasPointerType); + + p = comp.GlobalNamespace.GetTypeMember("R").GetMember("P2"); + Assert.True(p.HasPointerType); + + p = comp.GlobalNamespace.GetTypeMember("R").GetMember("P3"); + Assert.True(p.HasPointerType); + } + + [Fact] + public void PositionalMemberModifiers_In() + { + var src = @" +var r = new R(42); +int i = 43; +var r2 = new R(in i); +System.Console.Write((r.P1, r2.P1)); + +record struct R(in int P1); +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "(42, 43)"); + + var actualMembers = comp.GetMember("R").Constructors.ToTestDisplayStrings(); + var expectedMembers = new[] + { + "R..ctor(in System.Int32 P1)", + "R..ctor()" + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void PositionalMemberModifiers_Params() + { + var src = @" +var r = new R(42, 43); +var r2 = new R(new[] { 44, 45 }); +System.Console.Write((r.Array[0], r.Array[1], r2.Array[0], r2.Array[1])); + +record struct R(params int[] Array); +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(42, 43, 44, 45)"); + + var actualMembers = comp.GetMember("R").Constructors.ToTestDisplayStrings(); + var expectedMembers = new[] + { + "R..ctor(params System.Int32[] Array)", + "R..ctor()" + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void PositionalMemberDefaultValue() + { + var src = @" +var r = new R(); // This uses the parameterless contructor +System.Console.Write(r.P); + +record struct R(int P = 42); +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "0"); + } + + [Fact] + public void PositionalMemberDefaultValue_PassingOneArgument() + { + var src = @" +var r = new R(41); +System.Console.Write(r.O); +System.Console.Write("" ""); +System.Console.Write(r.P); + +record struct R(int O, int P = 42); +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "41 42"); + } + + [Fact] + public void PositionalMemberDefaultValue_AndPropertyWithInitializer() + { + var src = @" +var r = new R(0); +System.Console.Write(r.P); + +record struct R(int O, int P = 1) +{ + public int P { get; init; } = 42; +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (5,28): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? + // record struct R(int O, int P = 1) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P").WithArguments("P").WithLocation(5, 28) + ); + var verifier = CompileAndVerify(comp, expectedOutput: "42", verify: Verification.Skipped /* init-only */); + + verifier.VerifyIL("R..ctor(int, int)", @" +{ + // Code size 16 (0x10) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int R.k__BackingField"" + IL_0007: ldarg.0 + IL_0008: ldc.i4.s 42 + IL_000a: stfld ""int R.

k__BackingField"" + IL_000f: ret +}"); + } + + [Fact] + public void PositionalMemberDefaultValue_AndPropertyWithoutInitializer() + { + var src = @" +record struct R(int P = 42) +{ + public int P { get; init; } + + public static void Main() + { + var r = new R(); + System.Console.Write(r.P); + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,15): error CS0843: Auto-implemented property 'R.P' must be fully assigned before control is returned to the caller. + // record struct R(int P = 42) + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "R").WithArguments("R.P").WithLocation(2, 15), + // (2,21): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? + // record struct R(int P = 42) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P").WithArguments("P").WithLocation(2, 21) + ); + } + + [Fact] + public void PositionalMemberDefaultValue_AndPropertyWithInitializer_CopyingParameter() + { + var src = @" +var r = new R(0); +System.Console.Write(r.P); + +record struct R(int O, int P = 42) +{ + public int P { get; init; } = P; +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42", verify: Verification.Skipped /* init-only */); + + verifier.VerifyIL("R..ctor(int, int)", @" +{ + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int R.k__BackingField"" + IL_0007: ldarg.0 + IL_0008: ldarg.2 + IL_0009: stfld ""int R.

k__BackingField"" + IL_000e: ret +}"); + } + + [Fact] + public void RecordWithConstraints_NullableWarning() + { + var src = @" +#nullable enable +var r = new R(""R""); +var r2 = new R2(""R2""); +System.Console.Write((r.P, r2.P)); + +record struct R(T P) where T : class; +record struct R2(T P) where T : class { } +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (3,15): warning CS8634: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'R'. Nullability of type argument 'string?' doesn't match 'class' constraint. + // var r = new R("R"); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, "string?").WithArguments("R", "T", "string?").WithLocation(3, 15), + // (4,17): warning CS8634: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'R2'. Nullability of type argument 'string?' doesn't match 'class' constraint. + // var r2 = new R2("R2"); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, "string?").WithArguments("R2", "T", "string?").WithLocation(4, 17) + ); + CompileAndVerify(comp, expectedOutput: "(R, R2)"); + } + + [Fact] + public void RecordWithConstraints_ConstraintError() + { + var src = @" +record struct R(T P) where T : class; +record struct R2(T P) where T : class { } + +public class C +{ + public static void Main() + { + _ = new R(1); + _ = new R2(2); + } +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (9,19): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'R' + // _ = new R(1); + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "int").WithArguments("R", "T", "int").WithLocation(9, 19), + // (10,20): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'R2' + // _ = new R2(2); + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "int").WithArguments("R2", "T", "int").WithLocation(10, 20) + ); + } + + [Fact] + public void CyclicBases4() + { + var text = +@" +record struct A : B> { } +record struct B : A> +{ + A F() { return null; } +} +"; + var comp = CreateCompilation(text); + comp.GetDeclarationDiagnostics().Verify( + // (3,22): error CS0527: Type 'A>' in interface list is not an interface + // record struct B : A> + Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "A>").WithArguments("A>").WithLocation(3, 22), + // (2,22): error CS0527: Type 'B>' in interface list is not an interface + // record struct A : B> { } + Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "B>").WithArguments("B>").WithLocation(2, 22) + ); + } + + [Fact] + public void PartialClassWithDifferentTupleNamesInImplementedInterfaces() + { + var source = @" +public interface I { } +public partial record C1 : I<(int a, int b)> { } +public partial record C1 : I<(int notA, int notB)> { } + +public partial record C2 : I<(int a, int b)> { } +public partial record C2 : I<(int, int)> { } + +public partial record C3 : I<(int a, int b)> { } +public partial record C3 : I<(int a, int b)> { } + +public partial record C4 : I<(int a, int b)> { } +public partial record C4 : I<(int b, int a)> { } +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,23): error CS8140: 'I<(int notA, int notB)>' is already listed in the interface list on type 'C1' with different tuple element names, as 'I<(int a, int b)>'. + // public partial record C1 : I<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithTupleNamesInBaseList, "C1").WithArguments("I<(int notA, int notB)>", "I<(int a, int b)>", "C1").WithLocation(3, 23), + // (6,23): error CS8140: 'I<(int, int)>' is already listed in the interface list on type 'C2' with different tuple element names, as 'I<(int a, int b)>'. + // public partial record C2 : I<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithTupleNamesInBaseList, "C2").WithArguments("I<(int, int)>", "I<(int a, int b)>", "C2").WithLocation(6, 23), + // (12,23): error CS8140: 'I<(int b, int a)>' is already listed in the interface list on type 'C4' with different tuple element names, as 'I<(int a, int b)>'. + // public partial record C4 : I<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithTupleNamesInBaseList, "C4").WithArguments("I<(int b, int a)>", "I<(int a, int b)>", "C4").WithLocation(12, 23) + ); + } + + [Fact] + public void CS0267ERR_PartialMisplaced() + { + var test = @" +partial public record struct C // CS0267 +{ +} +"; + + CreateCompilation(test).VerifyDiagnostics( + // (2,1): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + // partial public record struct C // CS0267 + Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(2, 1) + ); + } + + [Fact] + public void SealedStaticRecord() + { + var source = @" +sealed static record struct R; +"; + CreateCompilation(source).VerifyDiagnostics( + // (2,29): error CS0106: The modifier 'sealed' is not valid for this item + // sealed static record struct R; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "R").WithArguments("sealed").WithLocation(2, 29), + // (2,29): error CS0106: The modifier 'static' is not valid for this item + // sealed static record struct R; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "R").WithArguments("static").WithLocation(2, 29) + ); + } + + [Fact] + public void CS0513ERR_AbstractInConcreteClass02() + { + var text = @" +record struct C +{ + public abstract event System.Action E; + public abstract int this[int x] { get; set; } +} +"; + CreateCompilation(text).VerifyDiagnostics( + // (5,25): error CS0106: The modifier 'abstract' is not valid for this item + // public abstract int this[int x] { get; set; } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "this").WithArguments("abstract").WithLocation(5, 25), + // (4,41): error CS0106: The modifier 'abstract' is not valid for this item + // public abstract event System.Action E; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "E").WithArguments("abstract").WithLocation(4, 41) + ); + } + + [Fact] + public void CS0574ERR_BadDestructorName() + { + var test = @" +public record struct iii +{ + ~iiii(){} +} +"; + + CreateCompilation(test).VerifyDiagnostics( + // (4,6): error CS0574: Name of destructor must match name of type + // ~iiii(){} + Diagnostic(ErrorCode.ERR_BadDestructorName, "iiii").WithLocation(4, 6), + // (4,6): error CS0575: Only class types can contain destructors + // ~iiii(){} + Diagnostic(ErrorCode.ERR_OnlyClassesCanContainDestructors, "iiii").WithArguments("iii.~iii()").WithLocation(4, 6) + ); + } + + [Fact] + public void StaticRecordWithConstructorAndDestructor() + { + var text = @" +static record struct R(int I) +{ + R() : this(0) { } + ~R() { } +} +"; + var comp = CreateCompilation(text); + comp.VerifyDiagnostics( + // (2,22): error CS0106: The modifier 'static' is not valid for this item + // static record struct R(int I) + Diagnostic(ErrorCode.ERR_BadMemberFlag, "R").WithArguments("static").WithLocation(2, 22), + // (4,5): error CS0568: Structs cannot contain explicit parameterless constructors + // R() : this(0) { } + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "R").WithLocation(4, 5), + // (5,6): error CS0575: Only class types can contain destructors + // ~R() { } + Diagnostic(ErrorCode.ERR_OnlyClassesCanContainDestructors, "R").WithArguments("R.~R()").WithLocation(5, 6) + ); + } + + [Fact] + public void RecordWithPartialMethodExplicitImplementation() + { + var source = +@"record struct R +{ + partial void M(); +}"; + CreateCompilation(source).VerifyDiagnostics( + // (3,18): error CS0751: A partial method must be declared within a partial type + // partial void M(); + Diagnostic(ErrorCode.ERR_PartialMethodOnlyInPartialClass, "M").WithLocation(3, 18) + ); + } + + [Fact] + public void RecordWithPartialMethodRequiringBody() + { + var source = +@"partial record struct R +{ + public partial int M(); +}"; + CreateCompilation(source).VerifyDiagnostics( + // (3,24): error CS8795: Partial method 'R.M()' must have an implementation part because it has accessibility modifiers. + // public partial int M(); + Diagnostic(ErrorCode.ERR_PartialMethodWithAccessibilityModsMustHaveImplementation, "M").WithArguments("R.M()").WithLocation(3, 24) + ); + } + + [Fact] + public void CanDeclareIteratorInRecord() + { + var source = @" +using System.Collections.Generic; + +foreach(var i in new X(42).GetItems()) +{ + System.Console.Write(i); +} + +public record struct X(int a) +{ + public IEnumerable GetItems() { yield return a; yield return a + 1; } +}"; + + var comp = CreateCompilation(source).VerifyDiagnostics(); + + CompileAndVerify(comp, expectedOutput: "4243"); + } + + [Fact] + public void ParameterlessConstructor() + { + var src = @" +record struct C() +{ + int Property { get; set; } = 42; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,16): error CS0568: Structs cannot contain explicit parameterless constructors + // record struct C() + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(2, 16) + ); + } + + [Fact] + public void XmlDoc() + { + var src = @" +///

Summary +/// Description for I1 +public record struct C(int I1); + +namespace System.Runtime.CompilerServices +{ + /// Ignored + public static class IsExternalInit + { + } +} +"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithDocumentationComments.WithLanguageVersion(LanguageVersion.Preview)); + comp.VerifyDiagnostics(); + + var cMember = comp.GetMember("C"); + Assert.Equal( +@" + Summary + Description for I1 + +", cMember.GetDocumentationCommentXml()); + var constructor = cMember.GetMembers(".ctor").OfType().Single(); + Assert.Equal( +@" + Summary + Description for I1 + +", constructor.GetDocumentationCommentXml()); + + Assert.Equal("", constructor.GetParameters()[0].GetDocumentationCommentXml()); + + var property = cMember.GetMembers("I1").Single(); + Assert.Equal("", property.GetDocumentationCommentXml()); + } + + [Fact] + public void XmlDoc_Cref() + { + var src = @" +/// Summary +/// Description for +public record struct C(int I1) +{ + /// Summary + /// Description for + public void M(int x) { } +} + +namespace System.Runtime.CompilerServices +{ + /// Ignored + public static class IsExternalInit + { + } +} +"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithDocumentationComments.WithLanguageVersion(LanguageVersion.Preview)); + comp.VerifyDiagnostics( + // (7,52): warning CS1574: XML comment has cref attribute 'x' that could not be resolved + // /// Description for + Diagnostic(ErrorCode.WRN_BadXMLRef, "x").WithArguments("x").WithLocation(7, 52) + ); + + var tree = comp.SyntaxTrees.Single(); + var docComments = tree.GetCompilationUnitRoot().DescendantTrivia().Select(trivia => trivia.GetStructure()).OfType(); + var cref = docComments.First().DescendantNodes().OfType().First().Cref; + Assert.Equal("I1", cref.ToString()); + + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + Assert.Equal(SymbolKind.Property, model.GetSymbolInfo(cref).Symbol!.Kind); + } + + [Fact] + public void Deconstruct_Simple() + { + var source = +@"using System; + +record struct B(int X, int Y) +{ + public static void Main() + { + M(new B(1, 2)); + } + + static void M(B b) + { + switch (b) + { + case B(int x, int y): + Console.Write(x); + Console.Write(y); + break; + } + } +}"; + var verifier = CompileAndVerify(source, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("B.Deconstruct", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""readonly int B.X.get"" + IL_0007: stind.i4 + IL_0008: ldarg.2 + IL_0009: ldarg.0 + IL_000a: call ""readonly int B.Y.get"" + IL_000f: stind.i4 + IL_0010: ret +}"); + + var deconstruct = ((CSharpCompilation)verifier.Compilation).GetMember("B.Deconstruct"); + Assert.Equal(2, deconstruct.ParameterCount); + + Assert.Equal(RefKind.Out, deconstruct.Parameters[0].RefKind); + Assert.Equal("X", deconstruct.Parameters[0].Name); + + Assert.Equal(RefKind.Out, deconstruct.Parameters[1].RefKind); + Assert.Equal("Y", deconstruct.Parameters[1].Name); + + Assert.True(deconstruct.ReturnsVoid); + Assert.False(deconstruct.IsVirtual); + Assert.False(deconstruct.IsStatic); + Assert.Equal(Accessibility.Public, deconstruct.DeclaredAccessibility); + } + + [Fact] + public void Deconstruct_PositionalAndNominalProperty() + { + var source = +@"using System; + +record struct B(int X) +{ + public int Y { get; init; } = 0; + + public static void Main() + { + M(new B(1)); + } + + static void M(B b) + { + switch (b) + { + case B(int x): + Console.Write(x); + break; + } + } +}"; + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + Assert.Equal( + "void B.Deconstruct(out System.Int32 X)", + verifier.Compilation.GetMember("B.Deconstruct").ToTestDisplayString(includeNonNullable: false)); + } + + [Fact] + public void Deconstruct_Nested() + { + var source = +@"using System; + +record struct B(int X, int Y); + +record struct C(B B, int Z) +{ + public static void Main() + { + M(new C(new B(1, 2), 3)); + } + + static void M(C c) + { + switch (c) + { + case C(B(int x, int y), int z): + Console.Write(x); + Console.Write(y); + Console.Write(z); + break; + } + } +} +"; + + var verifier = CompileAndVerify(source, expectedOutput: "123"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("B.Deconstruct", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""readonly int B.X.get"" + IL_0007: stind.i4 + IL_0008: ldarg.2 + IL_0009: ldarg.0 + IL_000a: call ""readonly int B.Y.get"" + IL_000f: stind.i4 + IL_0010: ret +}"); + + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 21 (0x15) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""readonly B C.B.get"" + IL_0007: stobj ""B"" + IL_000c: ldarg.2 + IL_000d: ldarg.0 + IL_000e: call ""readonly int C.Z.get"" + IL_0013: stind.i4 + IL_0014: ret +}"); + } + + [Fact] + public void Deconstruct_PropertyCollision() + { + var source = +@"using System; + +record struct B(int X, int Y) +{ + public int X => 3; + + static void M(B b) + { + switch (b) + { + case B(int x, int y): + Console.Write(x); + Console.Write(y); + break; + } + } + + static void Main() + { + M(new B(1, 2)); + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: "32"); + verifier.VerifyDiagnostics( + // (3,21): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct B(int X, int Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(3, 21) + ); + + Assert.Equal( + "void B.Deconstruct(out System.Int32 X, out System.Int32 Y)", + verifier.Compilation.GetMember("B.Deconstruct").ToTestDisplayString(includeNonNullable: false)); + } + + [Fact] + public void Deconstruct_MethodCollision_01() + { + var source = @" +record struct B(int X, int Y) +{ + public int X() => 3; + + static void M(B b) + { + switch (b) + { + case B(int x, int y): + break; + } + } + + static void Main() + { + M(new B(1, 2)); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,16): error CS0102: The type 'B' already contains a definition for 'X' + // public int X() => 3; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "X").WithArguments("B", "X").WithLocation(4, 16) + ); + + Assert.Equal( + "void B.Deconstruct(out System.Int32 X, out System.Int32 Y)", + comp.GetMember("B.Deconstruct").ToTestDisplayString(includeNonNullable: false)); + } + + [Fact] + public void Deconstruct_FieldCollision() + { + var source = @" +using System; + +record struct C(int X) +{ + int X = 0; + + static void M(C c) + { + switch (c) + { + case C(int x): + Console.Write(x); + break; + } + } + + static void Main() + { + M(new C(0)); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,21): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(int X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(4, 21), + // (6,9): warning CS0414: The field 'C.X' is assigned but its value is never used + // int X = 0; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "X").WithArguments("C.X").WithLocation(6, 9)); + + Assert.Equal( + "void C.Deconstruct(out System.Int32 X)", + comp.GetMember("C.Deconstruct").ToTestDisplayString(includeNonNullable: false)); + } + + [Fact] + public void Deconstruct_Empty() + { + var source = @" +record struct C +{ + static void M(C c) + { + switch (c) + { + case C(): + break; + } + } + + static void Main() + { + M(new C()); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,19): error CS1061: 'C' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) + // case C(): + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "()").WithArguments("C", "Deconstruct").WithLocation(8, 19), + // (8,19): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'C', with 0 out parameters and a void return type. + // case C(): + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "()").WithArguments("C", "0").WithLocation(8, 19)); + + Assert.Null(comp.GetMember("C.Deconstruct")); + } + + [Fact] + public void Deconstruct_Conversion_02() + { + var source = @" +#nullable enable +using System; + +record struct C(string? X, string Y) +{ + public string X { get; init; } = null!; + public string? Y { get; init; } = string.Empty; + + static void M(C c) + { + switch (c) + { + case C(var x, string y): + Console.Write(x); + Console.Write(y); + break; + } + } + + static void Main() + { + M(new C(""a"", ""b"")); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,25): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(string? X, string Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(5, 25), + // (5,35): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? + // record struct C(string? X, string Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "Y").WithArguments("Y").WithLocation(5, 35) + ); + + Assert.Equal( + "void C.Deconstruct(out System.String? X, out System.String Y)", + comp.GetMember("C.Deconstruct").ToTestDisplayString(includeNonNullable: false)); + } + + [Fact] + public void Deconstruct_Empty_WithParameterList() + { + var source = @" +record struct C() +{ + static void M(C c) + { + switch (c) + { + case C(): + break; + } + } + + static void Main() + { + M(new C()); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (2,16): error CS0568: Structs cannot contain explicit parameterless constructors + // record struct C() + Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(2, 16), + // (8,19): error CS1061: 'C' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) + // case C(): + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "()").WithArguments("C", "Deconstruct").WithLocation(8, 19), + // (8,19): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'C', with 0 out parameters and a void return type. + // case C(): + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "()").WithArguments("C", "0").WithLocation(8, 19)); + + Assert.Null(comp.GetMember("C.Deconstruct")); + } + + [Fact] + public void Deconstruct_Empty_WithParameterList_UserDefined_01() + { + var source = +@"using System; + +record struct C(int I) +{ + public void Deconstruct() + { + } + + static void M(C c) + { + switch (c) + { + case C(): + Console.Write(12); + break; + } + } + + public static void Main() + { + M(new C(42)); + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void Deconstruct_UserDefined() + { + var source = +@"using System; + +record struct B(int X, int Y) +{ + public void Deconstruct(out int X, out int Y) + { + X = this.X + 1; + Y = this.Y + 2; + } + + static void M(B b) + { + switch (b) + { + case B(int x, int y): + Console.Write(x); + Console.Write(y); + break; + } + } + + public static void Main() + { + M(new B(0, 0)); + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void Deconstruct_UserDefined_DifferentSignature_02() + { + var source = +@"using System; + +record struct B(int X) +{ + public int Deconstruct(out int a) => throw null; + + static void M(B b) + { + switch (b) + { + case B(int x): + Console.Write(x); + break; + } + } + + public static void Main() + { + M(new B(1)); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,16): error CS8874: Record member 'B.Deconstruct(out int)' must return 'void'. + // public int Deconstruct(out int a) => throw null; + Diagnostic(ErrorCode.ERR_SignatureMismatchInRecord, "Deconstruct").WithArguments("B.Deconstruct(out int)", "void").WithLocation(5, 16), + // (11,19): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'B', with 1 out parameters and a void return type. + // case B(int x): + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(int x)").WithArguments("B", "1").WithLocation(11, 19)); + + Assert.Equal("System.Int32 B.Deconstruct(out System.Int32 a)", comp.GetMember("B.Deconstruct").ToTestDisplayString(includeNonNullable: false)); + } + + [Theory] + [InlineData("")] + [InlineData("private")] + [InlineData("internal")] + public void Deconstruct_UserDefined_Accessibility_07(string accessibility) + { + var source = +$@" +record struct A(int X) +{{ + { accessibility } void Deconstruct(out int a) + => throw null; +}} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,11): error CS8873: Record member 'A.Deconstruct(out int)' must be public. + // void Deconstruct(out int a) + Diagnostic(ErrorCode.ERR_NonPublicAPIInRecord, "Deconstruct").WithArguments("A.Deconstruct(out int)").WithLocation(4, 11 + accessibility.Length) + ); + } + + [Fact] + public void Deconstruct_UserDefined_Static_08() + { + var source = +@" +record struct A(int X) +{ + public static void Deconstruct(out int a) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,24): error CS8877: Record member 'A.Deconstruct(out int)' may not be static. + // public static void Deconstruct(out int a) + Diagnostic(ErrorCode.ERR_StaticAPIInRecord, "Deconstruct").WithArguments("A.Deconstruct(out int)").WithLocation(4, 24) + ); + } + + [Fact] + public void OutVarInPositionalParameterDefaultValue() + { + var source = +@" +record struct A(int X = A.M(out int a) + a) +{ + public static int M(out int a) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,25): error CS1736: Default parameter value for 'X' must be a compile-time constant + // record struct A(int X = A.M(out int a) + a) + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "A.M(out int a) + a").WithArguments("X").WithLocation(2, 25) + ); + } + + [Fact] + public void FieldConsideredUnassignedIfInitializationViaProperty() + { + var source = @" +record struct Pos(int X) +{ + private int x; + public int X { get { return x; } set { x = value; } } = X; +} + +record struct Pos2(int X) +{ + private int x = X; // value isn't validated by setter + public int X { get { return x; } set { x = value; } } +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,15): error CS0171: Field 'Pos.x' must be fully assigned before control is returned to the caller + // record struct Pos(int X) + Diagnostic(ErrorCode.ERR_UnassignedThis, "Pos").WithArguments("Pos.x").WithLocation(2, 15), + // (5,16): error CS8050: Only auto-implemented properties can have initializers. + // public int X { get { return x; } set { x = value; } } = X; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "X").WithArguments("Pos.X").WithLocation(5, 16) + ); + } + + [Fact] + public void IEquatableT_01() + { + var source = +@"record struct A; +class Program +{ + static void F(System.IEquatable t) + { + } + static void M() + { + F(new A()); + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + ); + } + + [Fact] + public void IEquatableT_02() + { + var source = +@"using System; +record struct A; +record struct B; + +class Program +{ + static bool F(IEquatable t, T t2) + { + return t.Equals(t2); + } + static void Main() + { + Console.Write(F(new A(), new A())); + Console.Write(F(new B(), new B())); + } +}"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "TrueTrue").VerifyDiagnostics(); + } + + [Fact] + public void IEquatableT_02_ImplicitImplementation() + { + var source = +@"using System; +record struct A +{ + public bool Equals(A other) + { + System.Console.Write(""A.Equals(A) ""); + return false; + } +} +record struct B +{ + public bool Equals(B other) + { + System.Console.Write(""B.Equals(B) ""); + return true; + } +} + +class Program +{ + static bool F(IEquatable t, T t2) + { + return t.Equals(t2); + } + static void Main() + { + Console.Write(F(new A(), new A())); + Console.Write("" ""); + Console.Write(F(new B(), new B())); + } +}"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "A.Equals(A) False B.Equals(B) True").VerifyDiagnostics( + // (4,17): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // public bool Equals(A other) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 17), + // (12,17): warning CS8851: 'B' defines 'Equals' but not 'GetHashCode' + // public bool Equals(B other) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("B").WithLocation(12, 17) + ); + } + + [Fact] + public void IEquatableT_02_ExplicitImplementation() + { + var source = +@"using System; +record struct A +{ + bool IEquatable.Equals(A other) + { + System.Console.Write(""A.Equals(A) ""); + return false; + } +} +record struct B +{ + bool IEquatable>.Equals(B other) + { + System.Console.Write(""B.Equals(B) ""); + return true; + } +} + +class Program +{ + static bool F(IEquatable t, T t2) + { + return t.Equals(t2); + } + static void Main() + { + Console.Write(F(new A(), new A())); + Console.Write("" ""); + Console.Write(F(new B(), new B())); + } +}"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "A.Equals(A) False B.Equals(B) True").VerifyDiagnostics(); + } + + [Fact] + public void IEquatableT_03() + { + var source = @" +record struct A : System.IEquatable>; +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var type = comp.GetMember("A"); + AssertEx.Equal(new[] { "System.IEquatable>" }, type.InterfacesNoUseSiteDiagnostics().ToTestDisplayStrings()); + AssertEx.Equal(new[] { "System.IEquatable>" }, type.AllInterfacesNoUseSiteDiagnostics.ToTestDisplayStrings()); + } + + [Fact] + public void IEquatableT_MissingIEquatable() + { + var source = @" +record struct A; +"; + var comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_IEquatable_T); + comp.VerifyEmitDiagnostics( + // (2,15): error CS0518: Predefined type 'System.IEquatable`1' is not defined or imported + // record struct A; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "A").WithArguments("System.IEquatable`1").WithLocation(2, 15), + // (2,15): error CS0518: Predefined type 'System.IEquatable`1' is not defined or imported + // record struct A; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "A").WithArguments("System.IEquatable`1").WithLocation(2, 15) + ); + + var type = comp.GetMember("A"); + AssertEx.Equal(new[] { "System.IEquatable>[missing]" }, type.InterfacesNoUseSiteDiagnostics().ToTestDisplayStrings()); + AssertEx.Equal(new[] { "System.IEquatable>[missing]" }, type.AllInterfacesNoUseSiteDiagnostics.ToTestDisplayStrings()); + } + + [Fact] + public void RecordEquals_01() + { + var source = @" +var a1 = new B(); +var a2 = new B(); +System.Console.WriteLine(a1.Equals(a2)); + +record struct B +{ + public bool Equals(B other) + { + System.Console.WriteLine(""B.Equals(B)""); + return false; + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,17): warning CS8851: 'B' defines 'Equals' but not 'GetHashCode' + // public bool Equals(B other) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("B").WithLocation(8, 17) + ); + + CompileAndVerify(comp, expectedOutput: +@" +B.Equals(B) +False +"); + } + + [Fact] + public void RecordEquals_01_NoInParameters() + { + var source = @" +var a1 = new B(); +var a2 = new B(); +System.Console.WriteLine(a1.Equals(in a2)); + +record struct B; +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,39): error CS1615: Argument 1 may not be passed with the 'in' keyword + // System.Console.WriteLine(a1.Equals(in a2)); + Diagnostic(ErrorCode.ERR_BadArgExtraRef, "a2").WithArguments("1", "in").WithLocation(4, 39) + ); + } + + [Theory] + [InlineData("protected")] + [InlineData("private protected")] + [InlineData("internal protected")] + public void RecordEquals_10(string accessibility) + { + var source = +$@" +record struct A +{{ + { accessibility } bool Equals(A x) + => throw null; + + bool System.IEquatable.Equals(A x) => throw null; +}} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,29): error CS0666: 'A.Equals(A)': new protected member declared in struct + // internal protected bool Equals(A x) + Diagnostic(ErrorCode.ERR_ProtectedInStruct, "Equals").WithArguments("A.Equals(A)").WithLocation(4, 11 + accessibility.Length), + // (4,29): error CS8873: Record member 'A.Equals(A)' must be public. + // internal protected bool Equals(A x) + Diagnostic(ErrorCode.ERR_NonPublicAPIInRecord, "Equals").WithArguments("A.Equals(A)").WithLocation(4, 11 + accessibility.Length), + // (4,29): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // internal protected bool Equals(A x) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 11 + accessibility.Length) + ); + } + + [Theory] + [InlineData("")] + [InlineData("private")] + [InlineData("internal")] + public void RecordEquals_11(string accessibility) + { + var source = +$@" +record struct A +{{ + { accessibility } bool Equals(A x) + => throw null; + + bool System.IEquatable.Equals(A x) => throw null; +}} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,...): error CS8873: Record member 'A.Equals(A)' must be public. + // { accessibility } bool Equals(A x) + Diagnostic(ErrorCode.ERR_NonPublicAPIInRecord, "Equals").WithArguments("A.Equals(A)").WithLocation(4, 11 + accessibility.Length), + // (4,11): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // bool Equals(A x) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 11 + accessibility.Length) + ); + } + + [Fact] + public void RecordEquals_12() + { + var source = @" +A a1 = new A(); +A a2 = new A(); + +System.Console.Write(a1.Equals(a2)); +System.Console.Write(a1.Equals((object)a2)); + +record struct A +{ + public bool Equals(B other) => throw null; +} +class B +{ +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "TrueTrue"); + verifier.VerifyIL("A.Equals(A)", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: ret +}"); + + verifier.VerifyIL("A.Equals(object)", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: isinst ""A"" + IL_0006: brfalse.s IL_0015 + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: unbox.any ""A"" + IL_000f: call ""bool A.Equals(A)"" + IL_0014: ret + IL_0015: ldc.i4.0 + IL_0016: ret +}"); + + verifier.VerifyIL("A.GetHashCode()", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.0 + IL_0001: ret +}"); + + var recordEquals = comp.GetMembers("A.Equals").OfType().Single(); + Assert.Equal("System.Boolean A.Equals(A other)", recordEquals.ToTestDisplayString()); + Assert.Equal(Accessibility.Public, recordEquals.DeclaredAccessibility); + Assert.False(recordEquals.IsAbstract); + Assert.False(recordEquals.IsVirtual); + Assert.False(recordEquals.IsOverride); + Assert.False(recordEquals.IsSealed); + Assert.True(recordEquals.IsImplicitlyDeclared); + + var objectEquals = comp.GetMembers("A.Equals").OfType().Single(); + Assert.Equal("System.Boolean A.Equals(System.Object obj)", objectEquals.ToTestDisplayString()); + Assert.Equal(Accessibility.Public, objectEquals.DeclaredAccessibility); + Assert.False(objectEquals.IsAbstract); + Assert.False(objectEquals.IsVirtual); + Assert.True(objectEquals.IsOverride); + Assert.False(objectEquals.IsSealed); + Assert.True(objectEquals.IsImplicitlyDeclared); + + MethodSymbol gethashCode = comp.GetMembers("A." + WellKnownMemberNames.ObjectGetHashCode).OfType().Single(); + Assert.Equal("System.Int32 A.GetHashCode()", gethashCode.ToTestDisplayString()); + Assert.Equal(Accessibility.Public, gethashCode.DeclaredAccessibility); + Assert.False(gethashCode.IsStatic); + Assert.False(gethashCode.IsAbstract); + Assert.False(gethashCode.IsVirtual); + Assert.True(gethashCode.IsOverride); + Assert.False(gethashCode.IsSealed); + Assert.True(gethashCode.IsImplicitlyDeclared); + } + + [Fact] + public void RecordEquals_13() + { + var source = @" +record struct A +{ + public int Equals(A other) + => throw null; + + bool System.IEquatable.Equals(A x) => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,16): error CS8874: Record member 'A.Equals(A)' must return 'bool'. + // public int Equals(A other) + Diagnostic(ErrorCode.ERR_SignatureMismatchInRecord, "Equals").WithArguments("A.Equals(A)", "bool").WithLocation(4, 16), + // (4,16): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // public int Equals(A other) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 16) + ); + } + + [Fact] + public void RecordEquals_14() + { + var source = @" +record struct A +{ + public bool Equals(A other) + => throw null; + + System.Boolean System.IEquatable.Equals(A x) => throw null; +} +"; + var comp = CreateCompilation(source); + comp.MakeTypeMissing(SpecialType.System_Boolean); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // record struct A + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"record struct A +{ + public bool Equals(A other) + => throw null; + + System.Boolean System.IEquatable.Equals(A x) => throw null; +}").WithArguments("System.Boolean").WithLocation(2, 1), + // (2,1): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // record struct A + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"record struct A +{ + public bool Equals(A other) + => throw null; + + System.Boolean System.IEquatable.Equals(A x) => throw null; +}").WithArguments("System.Boolean").WithLocation(2, 1), + // (2,15): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // record struct A + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "A").WithArguments("System.Boolean").WithLocation(2, 15), + // (2,15): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // record struct A + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "A").WithArguments("System.Boolean").WithLocation(2, 15), + // (2,15): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // record struct A + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "A").WithArguments("System.Boolean").WithLocation(2, 15), + // (2,15): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // record struct A + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "A").WithArguments("System.Boolean").WithLocation(2, 15), + // (4,12): error CS0518: Predefined type 'System.Boolean' is not defined or imported + // public bool Equals(A other) + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "bool").WithArguments("System.Boolean").WithLocation(4, 12), + // (4,17): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // public bool Equals(A other) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 17) + ); + } + + [Fact] + public void RecordEquals_19() + { + var source = @" +record struct A +{ + public static bool Equals(A x) => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,15): error CS0736: 'A' does not implement interface member 'IEquatable.Equals(A)'. 'A.Equals(A)' cannot implement an interface member because it is static. + // record struct A + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberStatic, "A").WithArguments("A", "System.IEquatable.Equals(A)", "A.Equals(A)").WithLocation(2, 15), + // (4,24): error CS8877: Record member 'A.Equals(A)' may not be static. + // public static bool Equals(A x) => throw null; + Diagnostic(ErrorCode.ERR_StaticAPIInRecord, "Equals").WithArguments("A.Equals(A)").WithLocation(4, 24), + // (4,24): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // public static bool Equals(A x) => throw null; + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 24) + ); + } + + [Fact] + public void RecordEquals_RecordEqualsInValueType() + { + var src = @" +public record struct A; + +namespace System +{ + public class Object + { + public virtual bool Equals(object x) => throw null; + public virtual int GetHashCode() => throw null; + public virtual string ToString() => throw null; + } + public class Exception { } + public class ValueType + { + public bool Equals(A x) => throw null; + } + public class Attribute { } + public class String { } + public struct Void { } + public struct Boolean { } + public struct Int32 { } + public interface IEquatable { } +} +namespace System.Collections.Generic +{ + public abstract class EqualityComparer + { + public static EqualityComparer Default => throw null; + public abstract int GetHashCode(T t); + } +} +namespace System.Text +{ + public class StringBuilder + { + public StringBuilder Append(string s) => null; + public StringBuilder Append(object s) => null; + } +} +"; + var comp = CreateEmptyCompilation(src, parseOptions: TestOptions.RegularPreview); + + comp.VerifyEmitDiagnostics( + // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. + Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1) + ); + + var recordEquals = comp.GetMembers("A.Equals").OfType().Single(); + Assert.Equal("System.Boolean A.Equals(A other)", recordEquals.ToTestDisplayString()); + } + + [Fact] + public void RecordEquals_FourFields() + { + var source = @" +A a1 = new A(1, ""hello""); + +System.Console.Write(a1.Equals(a1)); +System.Console.Write(a1.Equals((object)a1)); +System.Console.Write("" - ""); + +A a2 = new A(1, ""hello"") { fieldI = 100 }; + +System.Console.Write(a1.Equals(a2)); +System.Console.Write(a1.Equals((object)a2)); +System.Console.Write(a2.Equals(a1)); +System.Console.Write(a2.Equals((object)a1)); +System.Console.Write("" - ""); + +A a3 = new A(1, ""world""); + +System.Console.Write(a1.Equals(a3)); +System.Console.Write(a1.Equals((object)a3)); +System.Console.Write(a3.Equals(a1)); +System.Console.Write(a3.Equals((object)a1)); + +record struct A(int I, string S) +{ + public int fieldI = 42; + public string fieldS = ""hello""; +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "TrueTrue - FalseFalseFalseFalse - FalseFalseFalseFalse"); + verifier.VerifyIL("A.Equals(A)", @" +{ + // Code size 97 (0x61) + .maxstack 3 + IL_0000: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0005: ldarg.0 + IL_0006: ldfld ""int A.k__BackingField"" + IL_000b: ldarg.1 + IL_000c: ldfld ""int A.k__BackingField"" + IL_0011: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" + IL_0016: brfalse.s IL_005f + IL_0018: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_001d: ldarg.0 + IL_001e: ldfld ""string A.k__BackingField"" + IL_0023: ldarg.1 + IL_0024: ldfld ""string A.k__BackingField"" + IL_0029: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(string, string)"" + IL_002e: brfalse.s IL_005f + IL_0030: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0035: ldarg.0 + IL_0036: ldfld ""int A.fieldI"" + IL_003b: ldarg.1 + IL_003c: ldfld ""int A.fieldI"" + IL_0041: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" + IL_0046: brfalse.s IL_005f + IL_0048: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_004d: ldarg.0 + IL_004e: ldfld ""string A.fieldS"" + IL_0053: ldarg.1 + IL_0054: ldfld ""string A.fieldS"" + IL_0059: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(string, string)"" + IL_005e: ret + IL_005f: ldc.i4.0 + IL_0060: ret +}"); + + verifier.VerifyIL("A.Equals(object)", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: isinst ""A"" + IL_0006: brfalse.s IL_0015 + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: unbox.any ""A"" + IL_000f: call ""bool A.Equals(A)"" + IL_0014: ret + IL_0015: ldc.i4.0 + IL_0016: ret +}"); + + verifier.VerifyIL("A.GetHashCode()", @" +{ + // Code size 86 (0x56) + .maxstack 3 + IL_0000: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0005: ldarg.0 + IL_0006: ldfld ""int A.k__BackingField"" + IL_000b: callvirt ""int System.Collections.Generic.EqualityComparer.GetHashCode(int)"" + IL_0010: ldc.i4 0xa5555529 + IL_0015: mul + IL_0016: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_001b: ldarg.0 + IL_001c: ldfld ""string A.k__BackingField"" + IL_0021: callvirt ""int System.Collections.Generic.EqualityComparer.GetHashCode(string)"" + IL_0026: add + IL_0027: ldc.i4 0xa5555529 + IL_002c: mul + IL_002d: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0032: ldarg.0 + IL_0033: ldfld ""int A.fieldI"" + IL_0038: callvirt ""int System.Collections.Generic.EqualityComparer.GetHashCode(int)"" + IL_003d: add + IL_003e: ldc.i4 0xa5555529 + IL_0043: mul + IL_0044: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0049: ldarg.0 + IL_004a: ldfld ""string A.fieldS"" + IL_004f: callvirt ""int System.Collections.Generic.EqualityComparer.GetHashCode(string)"" + IL_0054: add + IL_0055: ret +}"); + } + + [Fact] + public void RecordEquals_StaticField() + { + var source = @" +record struct A +{ + public static int field = 42; +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp); + verifier.VerifyIL("A.Equals(A)", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: ret +}"); + + verifier.VerifyIL("A.GetHashCode()", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.0 + IL_0001: ret +}"); + } + + [Fact] + public void ObjectEquals_06() + { + var source = @" +record struct A +{ + public static new bool Equals(object obj) => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,28): error CS0111: Type 'A' already defines a member called 'Equals' with the same parameter types + // public static new bool Equals(object obj) => throw null; + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Equals").WithArguments("Equals", "A").WithLocation(4, 28) + ); + } + + [Fact] + public void ObjectEquals_UserDefined() + { + var source = @" +record struct A +{ + public override bool Equals(object obj) => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,26): error CS0111: Type 'A' already defines a member called 'Equals' with the same parameter types + // public override bool Equals(object obj) => throw null; + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Equals").WithArguments("Equals", "A").WithLocation(4, 26) + ); + } + + [Fact] + public void GetHashCode_UserDefined() + { + var source = @" +System.Console.Write(new A().GetHashCode()); + +record struct A +{ + public override int GetHashCode() => 42; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "42"); + } + + [Fact] + public void GetHashCode_GetHashCodeInValueType() + { + var src = @" +public record struct A; + +namespace System +{ + public class Object + { + public virtual bool Equals(object x) => throw null; + public virtual string ToString() => throw null; + } + public class Exception { } + public class ValueType + { + public virtual int GetHashCode() => throw null; + } + public class Attribute { } + public class String { } + public struct Void { } + public struct Boolean { } + public struct Int32 { } + public interface IEquatable { } +} +namespace System.Collections.Generic +{ + public abstract class EqualityComparer + { + public static EqualityComparer Default => throw null; + public abstract int GetHashCode(T t); + } +} +namespace System.Text +{ + public class StringBuilder + { + public StringBuilder Append(string s) => null; + public StringBuilder Append(object s) => null; + } +} +"; + var comp = CreateEmptyCompilation(src, parseOptions: TestOptions.RegularPreview); + + comp.VerifyEmitDiagnostics( + // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. + Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1), + // (2,22): error CS8869: 'A.GetHashCode()' does not override expected method from 'object'. + // public record struct A; + Diagnostic(ErrorCode.ERR_DoesNotOverrideMethodFromObject, "A").WithArguments("A.GetHashCode()").WithLocation(2, 22) + ); + } + + [Fact] + public void GetHashCode_MissingEqualityComparer_EmptyRecord() + { + var src = @" +public record struct A; +"; + var comp = CreateCompilation(src); + comp.MakeTypeMissing(WellKnownType.System_Collections_Generic_EqualityComparer_T); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void GetHashCode_MissingEqualityComparer_NonEmptyRecord() + { + var src = @" +public record struct A(int I); +"; + var comp = CreateCompilation(src); + comp.MakeTypeMissing(WellKnownType.System_Collections_Generic_EqualityComparer_T); + + comp.VerifyEmitDiagnostics( + // (2,1): error CS0656: Missing compiler required member 'System.Collections.Generic.EqualityComparer`1.GetHashCode' + // public record struct A(int I); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "public record struct A(int I);").WithArguments("System.Collections.Generic.EqualityComparer`1", "GetHashCode").WithLocation(2, 1), + // (2,1): error CS0656: Missing compiler required member 'System.Collections.Generic.EqualityComparer`1.get_Default' + // public record struct A(int I); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "public record struct A(int I);").WithArguments("System.Collections.Generic.EqualityComparer`1", "get_Default").WithLocation(2, 1) + ); + } + + [Fact] + public void GetHashCodeIsDefinedButEqualsIsNot() + { + var src = @" +public record struct C +{ + public object Data; + public override int GetHashCode() { return 0; } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + } + + [Fact] + public void EqualsIsDefinedButGetHashCodeIsNot() + { + var src = @" +public record struct C +{ + public object Data; + public bool Equals(C c) { return false; } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (5,17): warning CS8851: 'C' defines 'Equals' but not 'GetHashCode' + // public bool Equals(C c) { return false; } + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("C").WithLocation(5, 17)); + } + + [Fact] + public void EqualityOperators_01() + { + var source = @" +record struct A(int X) +{ + public bool Equals(ref A other) + => throw null; + + static void Main() + { + Test(default, default); + Test(default, new A(0)); + Test(new A(1), new A(1)); + Test(new A(2), new A(3)); + var a = new A(11); + Test(a, a); + } + + static void Test(A a1, A a2) + { + System.Console.WriteLine(""{0} {1} {2} {3}"", a1 == a2, a2 == a1, a1 != a2, a2 != a1); + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @" +True True False False +True True False False +True True False False +False False True True +True True False False +").VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + MethodSymbol op = comp.GetMembers("A." + WellKnownMemberNames.EqualityOperatorName).OfType().Single(); + Assert.Equal("System.Boolean A.op_Equality(A left, A right)", op.ToTestDisplayString()); + Assert.Equal(Accessibility.Public, op.DeclaredAccessibility); + Assert.True(op.IsStatic); + Assert.False(op.IsAbstract); + Assert.False(op.IsVirtual); + Assert.False(op.IsOverride); + Assert.False(op.IsSealed); + Assert.True(op.IsImplicitlyDeclared); + + op = comp.GetMembers("A." + WellKnownMemberNames.InequalityOperatorName).OfType().Single(); + Assert.Equal("System.Boolean A.op_Inequality(A left, A right)", op.ToTestDisplayString()); + Assert.Equal(Accessibility.Public, op.DeclaredAccessibility); + Assert.True(op.IsStatic); + Assert.False(op.IsAbstract); + Assert.False(op.IsVirtual); + Assert.False(op.IsOverride); + Assert.False(op.IsSealed); + Assert.True(op.IsImplicitlyDeclared); + + verifier.VerifyIL("bool A.op_Equality(A, A)", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarga.s V_0 + IL_0002: ldarg.1 + IL_0003: call ""bool A.Equals(A)"" + IL_0008: ret +} +"); + + verifier.VerifyIL("bool A.op_Inequality(A, A)", @" +{ + // Code size 11 (0xb) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call ""bool A.op_Equality(A, A)"" + IL_0007: ldc.i4.0 + IL_0008: ceq + IL_000a: ret +} +"); + } + + [Fact] + public void EqualityOperators_03() + { + var source = +@" +record struct A +{ + public static bool operator==(A r1, A r2) + => throw null; + public static bool operator==(A r1, string r2) + => throw null; + public static bool operator!=(A r1, string r2) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,32): error CS0111: Type 'A' already defines a member called 'op_Equality' with the same parameter types + // public static bool operator==(A r1, A r2) + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "==").WithArguments("op_Equality", "A").WithLocation(4, 32) + ); + } + + [Fact] + public void EqualityOperators_04() + { + var source = @" +record struct A +{ + public static bool operator!=(A r1, A r2) + => throw null; + public static bool operator!=(string r1, A r2) + => throw null; + public static bool operator==(string r1, A r2) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,32): error CS0111: Type 'A' already defines a member called 'op_Inequality' with the same parameter types + // public static bool operator!=(A r1, A r2) + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "!=").WithArguments("op_Inequality", "A").WithLocation(4, 32) + ); + } + + [Fact] + public void EqualityOperators_05() + { + var source = @" +record struct A +{ + public static bool op_Equality(A r1, A r2) + => throw null; + public static bool op_Equality(string r1, A r2) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,24): error CS0111: Type 'A' already defines a member called 'op_Equality' with the same parameter types + // public static bool op_Equality(A r1, A r2) + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "op_Equality").WithArguments("op_Equality", "A").WithLocation(4, 24) + ); + } + + [Fact] + public void EqualityOperators_06() + { + var source = @" +record struct A +{ + public static bool op_Inequality(A r1, A r2) + => throw null; + public static bool op_Inequality(A r1, string r2) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,24): error CS0111: Type 'A' already defines a member called 'op_Inequality' with the same parameter types + // public static bool op_Inequality(A r1, A r2) + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "op_Inequality").WithArguments("op_Inequality", "A").WithLocation(4, 24) + ); + } + + [Fact] + public void EqualityOperators_07() + { + var source = @" +record struct A +{ + public static bool Equals(A other) + => throw null; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,15): error CS0736: 'A' does not implement interface member 'IEquatable.Equals(A)'. 'A.Equals(A)' cannot implement an interface member because it is static. + // record struct A + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberStatic, "A").WithArguments("A", "System.IEquatable.Equals(A)", "A.Equals(A)").WithLocation(2, 15), + // (4,24): error CS8877: Record member 'A.Equals(A)' may not be static. + // public static bool Equals(A other) + Diagnostic(ErrorCode.ERR_StaticAPIInRecord, "Equals").WithArguments("A.Equals(A)").WithLocation(4, 24), + // (4,24): warning CS8851: 'A' defines 'Equals' but not 'GetHashCode' + // public static bool Equals(A other) + Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("A").WithLocation(4, 24) + ); + } + + [Theory] + [CombinatorialData] + public void EqualityOperators_09(bool useImageReference) + { + var source1 = @" +public record struct A(int X); +"; + var comp1 = CreateCompilation(source1); + + var source2 = +@" +class Program +{ + static void Main() + { + Test(default, default); + Test(default, new A(0)); + Test(new A(1), new A(1)); + Test(new A(2), new A(3)); + } + + static void Test(A a1, A a2) + { + System.Console.WriteLine(""{0} {1} {2} {3}"", a1 == a2, a2 == a1, a1 != a2, a2 != a1); + } +} +"; + CompileAndVerify(source2, references: new[] { useImageReference ? comp1.EmitToImageReference() : comp1.ToMetadataReference() }, expectedOutput: @" +True True False False +True True False False +True True False False +False False True True +").VerifyDiagnostics(); + } + + [Fact] + public void GetSimpleNonTypeMembers_DirectApiCheck() + { + var src = @" +public record struct RecordB(); +"; + var comp = CreateCompilation(src); + var b = comp.GlobalNamespace.GetTypeMember("RecordB"); + AssertEx.SetEqual(new[] { "System.Boolean RecordB.op_Equality(RecordB left, RecordB right)" }, + b.GetSimpleNonTypeMembers("op_Equality").ToTestDisplayStrings()); + } + + [Fact] + public void ToString_NestedRecord() + { + var src = @" +var c1 = new Outer.C1(42); +System.Console.Write(c1.ToString()); + +public class Outer +{ + public record struct C1(int I1); +} +"; + + var compDebug = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + var compRelease = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(compDebug, expectedOutput: "C1 { I1 = 42 }"); + compDebug.VerifyEmitDiagnostics(); + + CompileAndVerify(compRelease, expectedOutput: "C1 { I1 = 42 }"); + compRelease.VerifyEmitDiagnostics(); + } + + [Fact] + public void ToString_TopLevelRecord_Empty() + { + var src = @" +var c1 = new C1(); +System.Console.Write(c1.ToString()); + +record struct C1; +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + var v = CompileAndVerify(comp, expectedOutput: "C1 { }"); + + var print = comp.GetMember("C1." + WellKnownMemberNames.PrintMembersMethodName); + Assert.Equal(Accessibility.Private, print.DeclaredAccessibility); + Assert.False(print.IsOverride); + Assert.False(print.IsVirtual); + Assert.False(print.IsAbstract); + Assert.False(print.IsSealed); + Assert.True(print.IsImplicitlyDeclared); + + var toString = comp.GetMember("C1." + WellKnownMemberNames.ObjectToString); + Assert.Equal(Accessibility.Public, toString.DeclaredAccessibility); + Assert.True(toString.IsOverride); + Assert.False(toString.IsVirtual); + Assert.False(toString.IsAbstract); + Assert.False(toString.IsSealed); + Assert.True(toString.IsImplicitlyDeclared); + + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.0 + IL_0001: ret +} +"); + v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" +{ + // Code size 70 (0x46) + .maxstack 2 + .locals init (System.Text.StringBuilder V_0) + IL_0000: newobj ""System.Text.StringBuilder..ctor()"" + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: ldstr ""C1"" + IL_000c: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0011: pop + IL_0012: ldloc.0 + IL_0013: ldstr "" { "" + IL_0018: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_001d: pop + IL_001e: ldarg.0 + IL_001f: ldloc.0 + IL_0020: call ""bool C1.PrintMembers(System.Text.StringBuilder)"" + IL_0025: brfalse.s IL_0033 + IL_0027: ldloc.0 + IL_0028: ldstr "" "" + IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0032: pop + IL_0033: ldloc.0 + IL_0034: ldstr ""}"" + IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_003e: pop + IL_003f: ldloc.0 + IL_0040: callvirt ""string object.ToString()"" + IL_0045: ret +} +"); + } + + [Fact] + public void ToString_TopLevelRecord_MissingStringBuilder() + { + var src = @" +record struct C1; +"; + + var comp = CreateCompilation(src); + comp.MakeTypeMissing(WellKnownType.System_Text_StringBuilder); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0518: Predefined type 'System.Text.StringBuilder' is not defined or imported + // record struct C1; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "record struct C1;").WithArguments("System.Text.StringBuilder").WithLocation(2, 1), + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder..ctor' + // record struct C1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record struct C1;").WithArguments("System.Text.StringBuilder", ".ctor").WithLocation(2, 1), + // (2,15): error CS0518: Predefined type 'System.Text.StringBuilder' is not defined or imported + // record struct C1; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "C1").WithArguments("System.Text.StringBuilder").WithLocation(2, 15) + ); + } + + [Fact] + public void ToString_TopLevelRecord_MissingStringBuilderCtor() + { + var src = @" +record struct C1; +"; + + var comp = CreateCompilation(src); + comp.MakeMemberMissing(WellKnownMember.System_Text_StringBuilder__ctor); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder..ctor' + // record struct C1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record struct C1;").WithArguments("System.Text.StringBuilder", ".ctor").WithLocation(2, 1) + ); + } + + [Fact] + public void ToString_TopLevelRecord_MissingStringBuilderAppendString() + { + var src = @" +record struct C1; +"; + + var comp = CreateCompilation(src); + comp.MakeMemberMissing(WellKnownMember.System_Text_StringBuilder__AppendString); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder.Append' + // record struct C1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record struct C1;").WithArguments("System.Text.StringBuilder", "Append").WithLocation(2, 1) + ); + } + + [Fact] + public void ToString_TopLevelRecord_OneProperty_MissingStringBuilderAppendString() + { + var src = @" +record struct C1(int P); +"; + + var comp = CreateCompilation(src); + comp.MakeMemberMissing(WellKnownMember.System_Text_StringBuilder__AppendString); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder.Append' + // record struct C1(int P); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record struct C1(int P);").WithArguments("System.Text.StringBuilder", "Append").WithLocation(2, 1), + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder.Append' + // record struct C1(int P); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record struct C1(int P);").WithArguments("System.Text.StringBuilder", "Append").WithLocation(2, 1) + ); + } + + [Fact] + public void ToString_RecordWithIndexer() + { + var src = @" +var c1 = new C1(42); +System.Console.Write(c1.ToString()); + +record struct C1(int I1) +{ + private int field = 44; + public int this[int i] => 0; + public int PropertyWithoutGetter { set { } } + public int P2 { get => 43; } + public event System.Action a = null; + + private int field1 = 100; + internal int field2 = 100; + + private int Property1 { get; set; } = 100; + internal int Property2 { get; set; } = 100; +} +"; + + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "C1 { I1 = 42, P2 = 43 }"); + comp.VerifyEmitDiagnostics( + // (7,17): warning CS0414: The field 'C1.field' is assigned but its value is never used + // private int field = 44; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "field").WithArguments("C1.field").WithLocation(7, 17), + // (11,32): warning CS0414: The field 'C1.a' is assigned but its value is never used + // public event System.Action a = null; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "a").WithArguments("C1.a").WithLocation(11, 32), + // (13,17): warning CS0414: The field 'C1.field1' is assigned but its value is never used + // private int field1 = 100; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "field1").WithArguments("C1.field1").WithLocation(13, 17) + ); + } + + [Fact] + public void ToString_PrivateGetter() + { + var src = @" +var c1 = new C1(); +System.Console.Write(c1.ToString()); + +record struct C1 +{ + public int P1 { private get => 43; set => throw null; } +} +"; + + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "C1 { P1 = 43 }"); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void ToString_TopLevelRecord_OneField_ValueType() + { + var src = @" +var c1 = new C1() { field = 42 }; +System.Console.Write(c1.ToString()); + +record struct C1 +{ + public int field; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field = 42 }"); + + var print = comp.GetMember("C1." + WellKnownMemberNames.PrintMembersMethodName); + Assert.Equal(Accessibility.Private, print.DeclaredAccessibility); + Assert.False(print.IsOverride); + Assert.False(print.IsVirtual); + Assert.False(print.IsAbstract); + Assert.False(print.IsSealed); + Assert.True(print.IsImplicitlyDeclared); + + var toString = comp.GetMember("C1." + WellKnownMemberNames.ObjectToString); + Assert.Equal(Accessibility.Public, toString.DeclaredAccessibility); + Assert.True(toString.IsOverride); + Assert.False(toString.IsVirtual); + Assert.False(toString.IsAbstract); + Assert.False(toString.IsSealed); + Assert.True(toString.IsImplicitlyDeclared); + + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" +{ + // Code size 50 (0x32) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""field"" + IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_000b: pop + IL_000c: ldarg.1 + IL_000d: ldstr "" = "" + IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0017: pop + IL_0018: ldarg.1 + IL_0019: ldarg.0 + IL_001a: ldflda ""int C1.field"" + IL_001f: constrained. ""int"" + IL_0025: callvirt ""string object.ToString()"" + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_002f: pop + IL_0030: ldc.i4.1 + IL_0031: ret +} +"); + } + + [Fact] + public void ToString_TopLevelRecord_OneField_ConstrainedValueType() + { + var src = @" +var c1 = new C1() { field = 42 }; +System.Console.Write(c1.ToString()); + +record struct C1 where T : struct +{ + public T field; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field = 42 }"); + + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" +{ + // Code size 50 (0x32) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""field"" + IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_000b: pop + IL_000c: ldarg.1 + IL_000d: ldstr "" = "" + IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0017: pop + IL_0018: ldarg.1 + IL_0019: ldarg.0 + IL_001a: ldflda ""T C1.field"" + IL_001f: constrained. ""T"" + IL_0025: callvirt ""string object.ToString()"" + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_002f: pop + IL_0030: ldc.i4.1 + IL_0031: ret +} +"); + } + + [Fact] + public void ToString_TopLevelRecord_OneField_ReferenceType() + { + var src = @" +var c1 = new C1() { field = ""hello"" }; +System.Console.Write(c1.ToString()); + +record struct C1 +{ + public string field; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field = hello }"); + + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" +{ + // Code size 39 (0x27) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""field"" + IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_000b: pop + IL_000c: ldarg.1 + IL_000d: ldstr "" = "" + IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0017: pop + IL_0018: ldarg.1 + IL_0019: ldarg.0 + IL_001a: ldfld ""string C1.field"" + IL_001f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0024: pop + IL_0025: ldc.i4.1 + IL_0026: ret +} +"); + } + + [Fact] + public void ToString_TopLevelRecord_TwoFields_ReferenceType() + { + var src = @" +var c1 = new C1(42) { field1 = ""hi"", field2 = null }; +System.Console.Write(c1.ToString()); + +record struct C1(int I) +{ + public string field1 = null; + public string field2 = null; + + private string field3 = null; + internal string field4 = null; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (10,20): warning CS0414: The field 'C1.field3' is assigned but its value is never used + // private string field3 = null; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "field3").WithArguments("C1.field3").WithLocation(10, 20) + ); + var v = CompileAndVerify(comp, expectedOutput: "C1 { I = 42, field1 = hi, field2 = }"); + + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" +{ + // Code size 151 (0x97) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.1 + IL_0001: ldstr ""I"" + IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_000b: pop + IL_000c: ldarg.1 + IL_000d: ldstr "" = "" + IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0017: pop + IL_0018: ldarg.1 + IL_0019: ldarg.0 + IL_001a: call ""readonly int C1.I.get"" + IL_001f: stloc.0 + IL_0020: ldloca.s V_0 + IL_0022: constrained. ""int"" + IL_0028: callvirt ""string object.ToString()"" + IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0032: pop + IL_0033: ldarg.1 + IL_0034: ldstr "", "" + IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_003e: pop + IL_003f: ldarg.1 + IL_0040: ldstr ""field1"" + IL_0045: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_004a: pop + IL_004b: ldarg.1 + IL_004c: ldstr "" = "" + IL_0051: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0056: pop + IL_0057: ldarg.1 + IL_0058: ldarg.0 + IL_0059: ldfld ""string C1.field1"" + IL_005e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0063: pop + IL_0064: ldarg.1 + IL_0065: ldstr "", "" + IL_006a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_006f: pop + IL_0070: ldarg.1 + IL_0071: ldstr ""field2"" + IL_0076: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_007b: pop + IL_007c: ldarg.1 + IL_007d: ldstr "" = "" + IL_0082: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0087: pop + IL_0088: ldarg.1 + IL_0089: ldarg.0 + IL_008a: ldfld ""string C1.field2"" + IL_008f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0094: pop + IL_0095: ldc.i4.1 + IL_0096: ret +} +"); + } + + [Fact] + public void ToString_TopLevelRecord_Readonly() + { + var src = @" +var c1 = new C1(42); +System.Console.Write(c1.ToString()); + +readonly record struct C1(int I); +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var v = CompileAndVerify(comp, expectedOutput: "C1 { I = 42 }", verify: Verification.Skipped /* init-only */); + + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" +{ + // Code size 53 (0x35) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.1 + IL_0001: ldstr ""I"" + IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_000b: pop + IL_000c: ldarg.1 + IL_000d: ldstr "" = "" + IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0017: pop + IL_0018: ldarg.1 + IL_0019: ldarg.0 + IL_001a: call ""int C1.I.get"" + IL_001f: stloc.0 + IL_0020: ldloca.s V_0 + IL_0022: constrained. ""int"" + IL_0028: callvirt ""string object.ToString()"" + IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0032: pop + IL_0033: ldc.i4.1 + IL_0034: ret +} +"); + v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" +{ + // Code size 70 (0x46) + .maxstack 2 + .locals init (System.Text.StringBuilder V_0) + IL_0000: newobj ""System.Text.StringBuilder..ctor()"" + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: ldstr ""C1"" + IL_000c: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0011: pop + IL_0012: ldloc.0 + IL_0013: ldstr "" { "" + IL_0018: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_001d: pop + IL_001e: ldarg.0 + IL_001f: ldloc.0 + IL_0020: call ""bool C1.PrintMembers(System.Text.StringBuilder)"" + IL_0025: brfalse.s IL_0033 + IL_0027: ldloc.0 + IL_0028: ldstr "" "" + IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0032: pop + IL_0033: ldloc.0 + IL_0034: ldstr ""}"" + IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_003e: pop + IL_003f: ldloc.0 + IL_0040: callvirt ""string object.ToString()"" + IL_0045: ret +} +"); + } + + [Fact] + public void ToString_TopLevelRecord_UserDefinedToString() + { + var src = @" +var c1 = new C1(); +System.Console.Write(c1.ToString()); + +record struct C1 +{ + public override string ToString() => ""RAN""; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "RAN"); + + var print = comp.GetMember("C1." + WellKnownMemberNames.PrintMembersMethodName); + Assert.Equal("System.Boolean C1." + WellKnownMemberNames.PrintMembersMethodName + "(System.Text.StringBuilder builder)", print.ToTestDisplayString()); + } + + [Fact] + public void ToString_TopLevelRecord_UserDefinedToString_New() + { + var src = @" +record struct C1 +{ + public new string ToString() => throw null; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,23): error CS8869: 'C1.ToString()' does not override expected method from 'object'. + // public new string ToString() => throw null; + Diagnostic(ErrorCode.ERR_DoesNotOverrideMethodFromObject, "ToString").WithArguments("C1.ToString()").WithLocation(4, 23) + ); + } + + [Fact] + public void ToString_TopLevelRecord_UserDefinedToString_Sealed() + { + var src = @" +record struct C1 +{ + public sealed override string ToString() => throw null; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,35): error CS0106: The modifier 'sealed' is not valid for this item + // public sealed override string ToString() => throw null; + Diagnostic(ErrorCode.ERR_BadMemberFlag, "ToString").WithArguments("sealed").WithLocation(4, 35) + ); + } + + [Fact] + public void ToString_UserDefinedPrintMembers_WithNullableStringBuilder() + { + var src = @" +#nullable enable +record struct C1 +{ + private bool PrintMembers(System.Text.StringBuilder? builder) => throw null!; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void ToString_UserDefinedPrintMembers_ErrorReturnType() + { + var src = @" +record struct C1 +{ + private Error PrintMembers(System.Text.StringBuilder builder) => throw null; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,13): error CS0246: The type or namespace name 'Error' could not be found (are you missing a using directive or an assembly reference?) + // private Error PrintMembers(System.Text.StringBuilder builder) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error").WithArguments("Error").WithLocation(4, 13) + ); + } + + [Fact] + public void ToString_UserDefinedPrintMembers_WrongReturnType() + { + var src = @" +record struct C1 +{ + private int PrintMembers(System.Text.StringBuilder builder) => throw null; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,17): error CS8874: Record member 'C1.PrintMembers(StringBuilder)' must return 'bool'. + // private int PrintMembers(System.Text.StringBuilder builder) => throw null; + Diagnostic(ErrorCode.ERR_SignatureMismatchInRecord, "PrintMembers").WithArguments("C1.PrintMembers(System.Text.StringBuilder)", "bool").WithLocation(4, 17) + ); + } + + [Fact] + public void ToString_UserDefinedPrintMembers() + { + var src = @" +var c1 = new C1(); +System.Console.Write(c1.ToString()); +System.Console.Write("" - ""); +c1.M(); + +record struct C1 +{ + private bool PrintMembers(System.Text.StringBuilder builder) + { + builder.Append(""RAN""); + return true; + } + + public void M() + { + var builder = new System.Text.StringBuilder(); + if (PrintMembers(builder)) + { + System.Console.Write(builder.ToString()); + } + } +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "C1 { RAN } - RAN"); + } + + [Fact] + public void ToString_CallingSynthesizedPrintMembers() + { + var src = @" +var c1 = new C1(1, 2, 3); +System.Console.Write(c1.ToString()); +System.Console.Write("" - ""); +c1.M(); + +record struct C1(int I, int I2, int I3) +{ + public void M() + { + var builder = new System.Text.StringBuilder(); + if (PrintMembers(builder)) + { + System.Console.Write(builder.ToString()); + } + } +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "C1 { I = 1, I2 = 2, I3 = 3 } - I = 1, I2 = 2, I3 = 3"); + } + + [Fact] + public void ToString_UserDefinedPrintMembers_WrongAccessibility() + { + var src = @" +var c = new C1(); +System.Console.Write(c.ToString()); + +record struct C1 +{ + internal bool PrintMembers(System.Text.StringBuilder builder) => throw null; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (7,19): error CS8879: Record member 'C1.PrintMembers(StringBuilder)' must be private. + // internal bool PrintMembers(System.Text.StringBuilder builder) => throw null; + Diagnostic(ErrorCode.ERR_NonPrivateAPIInRecord, "PrintMembers").WithArguments("C1.PrintMembers(System.Text.StringBuilder)").WithLocation(7, 19) + ); + } + + [Fact] + public void ToString_UserDefinedPrintMembers_Static() + { + var src = @" +record struct C1 +{ + static private bool PrintMembers(System.Text.StringBuilder builder) => throw null; +} +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,25): error CS8877: Record member 'C1.PrintMembers(StringBuilder)' may not be static. + // static private bool PrintMembers(System.Text.StringBuilder builder) => throw null; + Diagnostic(ErrorCode.ERR_StaticAPIInRecord, "PrintMembers").WithArguments("C1.PrintMembers(System.Text.StringBuilder)").WithLocation(4, 25) + ); + } + + [Fact] + public void AmbigCtor_WithPropertyInitializer() + { + // Scenario causes ambiguous ctor for record class, but not record struct + var src = @" +record struct R(R X) +{ + public R X { get; init; } = X; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,14): error CS0523: Struct member 'R.X' of type 'R' causes a cycle in the struct layout + // public R X { get; init; } = X; + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "X").WithArguments("R.X", "R").WithLocation(4, 14) + ); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var parameterSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); + var parameter = model.GetDeclaredSymbol(parameterSyntax)!; + Assert.Equal("R X", parameter.ToTestDisplayString()); + Assert.Equal(SymbolKind.Parameter, parameter.Kind); + Assert.Equal("R..ctor(R X)", parameter.ContainingSymbol.ToTestDisplayString()); + + var initializerSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); + var initializer = model.GetSymbolInfo(initializerSyntax.Value).Symbol!; + Assert.Equal("R X", initializer.ToTestDisplayString()); + Assert.Equal(SymbolKind.Parameter, initializer.Kind); + Assert.Equal("R..ctor(R X)", initializer.ContainingSymbol.ToTestDisplayString()); + + var src2 = @" +record struct R(R X); +"; + var comp2 = CreateCompilation(src2); + comp2.VerifyEmitDiagnostics( + // (2,19): error CS0523: Struct member 'R.X' of type 'R' causes a cycle in the struct layout + // record struct R(R X); + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "X").WithArguments("R.X", "R").WithLocation(2, 19) + ); + } + + [Fact] + public void GetDeclaredSymbolOnAnOutLocalInPropertyInitializer() + { + var src = @" +record struct R(int I) +{ + public int I { get; init; } = M(out int i); + static int M(out int i) => throw null; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS8907: Parameter 'I' is unread. Did you forget to use it to initialize the property with that name? + // record struct R(int I) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "I").WithArguments("I").WithLocation(2, 21) + ); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var outVarSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); + var outVar = model.GetDeclaredSymbol(outVarSyntax)!; + Assert.Equal("System.Int32 i", outVar.ToTestDisplayString()); + Assert.Equal(SymbolKind.Local, outVar.Kind); + Assert.Equal("System.Int32 R.k__BackingField", outVar.ContainingSymbol.ToTestDisplayString()); + } + + [Fact] + public void AnalyzerActions_01() + { + // Test RegisterSyntaxNodeAction + var text1 = @" +record struct A([Attr1]int X = 0) : I1 +{ + private int M() => 3; + A(string S) : this(4) => throw null; +} + +interface I1 {} + +class Attr1 : System.Attribute {} +"; + var analyzer = new AnalyzerActions_01_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount0); + Assert.Equal(1, analyzer.FireCountRecordStructDeclarationA); + Assert.Equal(1, analyzer.FireCountRecordStructDeclarationACtor); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCountSimpleBaseTypeI1onA); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCountParameterListAPrimaryCtor); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCountConstructorDeclaration); + Assert.Equal(1, analyzer.FireCountStringParameterList); + Assert.Equal(1, analyzer.FireCountThisConstructorInitializer); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + } + + private class AnalyzerActions_01_Analyzer : DiagnosticAnalyzer + { + public int FireCount0; + public int FireCountRecordStructDeclarationA; + public int FireCountRecordStructDeclarationACtor; + public int FireCount3; + public int FireCountSimpleBaseTypeI1onA; + public int FireCount5; + public int FireCountParameterListAPrimaryCtor; + public int FireCount7; + public int FireCountConstructorDeclaration; + public int FireCountStringParameterList; + public int FireCountThisConstructorInitializer; + public int FireCount11; + public int FireCount12; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(Handle1, SyntaxKind.NumericLiteralExpression); + context.RegisterSyntaxNodeAction(Handle2, SyntaxKind.EqualsValueClause); + context.RegisterSyntaxNodeAction(Fail, SyntaxKind.BaseConstructorInitializer); + context.RegisterSyntaxNodeAction(Handle3, SyntaxKind.ThisConstructorInitializer); + context.RegisterSyntaxNodeAction(Handle4, SyntaxKind.ConstructorDeclaration); + context.RegisterSyntaxNodeAction(Fail, SyntaxKind.PrimaryConstructorBaseType); + context.RegisterSyntaxNodeAction(Handle6, SyntaxKind.RecordStructDeclaration); + context.RegisterSyntaxNodeAction(Handle7, SyntaxKind.IdentifierName); + context.RegisterSyntaxNodeAction(Handle8, SyntaxKind.SimpleBaseType); + context.RegisterSyntaxNodeAction(Handle9, SyntaxKind.ParameterList); + context.RegisterSyntaxNodeAction(Handle10, SyntaxKind.ArgumentList); + } + + protected void Handle1(SyntaxNodeAnalysisContext context) + { + var literal = (LiteralExpressionSyntax)context.Node; + + switch (literal.ToString()) + { + case "0": + Interlocked.Increment(ref FireCount0); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "3": + Interlocked.Increment(ref FireCount7); + Assert.Equal("System.Int32 A.M()", context.ContainingSymbol.ToTestDisplayString()); + break; + case "4": + Interlocked.Increment(ref FireCount12); + Assert.Equal("A..ctor(System.String S)", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(literal.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle2(SyntaxNodeAnalysisContext context) + { + var equalsValue = (EqualsValueClauseSyntax)context.Node; + + switch (equalsValue.ToString()) + { + case "= 0": + Interlocked.Increment(ref FireCount3); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(equalsValue.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle3(SyntaxNodeAnalysisContext context) + { + var initializer = (ConstructorInitializerSyntax)context.Node; + + switch (initializer.ToString()) + { + case ": this(4)": + Interlocked.Increment(ref FireCountThisConstructorInitializer); + Assert.Equal("A..ctor(System.String S)", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(initializer.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle4(SyntaxNodeAnalysisContext context) + { + Interlocked.Increment(ref FireCountConstructorDeclaration); + Assert.Equal("A..ctor(System.String S)", context.ContainingSymbol.ToTestDisplayString()); + } + + protected void Fail(SyntaxNodeAnalysisContext context) + { + Assert.True(false); + } + + protected void Handle6(SyntaxNodeAnalysisContext context) + { + var record = (RecordDeclarationSyntax)context.Node; + Assert.Equal(SyntaxKind.RecordStructDeclaration, record.Kind()); + + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "A": + Interlocked.Increment(ref FireCountRecordStructDeclarationA); + break; + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCountRecordStructDeclarationACtor); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(record.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle7(SyntaxNodeAnalysisContext context) + { + var identifier = (IdentifierNameSyntax)context.Node; + + switch (identifier.Identifier.ValueText) + { + case "Attr1": + Interlocked.Increment(ref FireCount5); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + } + } + + protected void Handle8(SyntaxNodeAnalysisContext context) + { + var baseType = (SimpleBaseTypeSyntax)context.Node; + + switch (baseType.ToString()) + { + case "I1": + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "A": + Interlocked.Increment(ref FireCountSimpleBaseTypeI1onA); + break; + default: + Assert.True(false); + break; + } + break; + + case "System.Attribute": + break; + + default: + Assert.True(false); + break; + } + } + + protected void Handle9(SyntaxNodeAnalysisContext context) + { + var parameterList = (ParameterListSyntax)context.Node; + + switch (parameterList.ToString()) + { + case "([Attr1]int X = 0)": + Interlocked.Increment(ref FireCountParameterListAPrimaryCtor); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "(string S)": + Interlocked.Increment(ref FireCountStringParameterList); + Assert.Equal("A..ctor(System.String S)", context.ContainingSymbol.ToTestDisplayString()); + break; + case "()": + break; + default: + Assert.True(false); + break; + } + } + + protected void Handle10(SyntaxNodeAnalysisContext context) + { + var argumentList = (ArgumentListSyntax)context.Node; + + switch (argumentList.ToString()) + { + case "(4)": + Interlocked.Increment(ref FireCount11); + Assert.Equal("A..ctor(System.String S)", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_02() + { + // Test RegisterSymbolAction + var text1 = @" +record struct A(int X = 0) +{} + +record struct C +{ + C(int Z = 4) + {} +} +"; + + var analyzer = new AnalyzerActions_02_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + } + + private class AnalyzerActions_02_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(Handle, SymbolKind.Method); + context.RegisterSymbolAction(Handle, SymbolKind.Property); + context.RegisterSymbolAction(Handle, SymbolKind.Parameter); + context.RegisterSymbolAction(Handle, SymbolKind.NamedType); + } + + private void Handle(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + break; + case "System.Int32 A.X { get; set; }": + Interlocked.Increment(ref FireCount2); + break; + case "[System.Int32 X = 0]": + Interlocked.Increment(ref FireCount3); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount4); + break; + case "[System.Int32 Z = 4]": + Interlocked.Increment(ref FireCount5); + break; + case "A": + Interlocked.Increment(ref FireCount6); + break; + case "C": + Interlocked.Increment(ref FireCount7); + break; + case "System.Runtime.CompilerServices.IsExternalInit": + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_03() + { + // Test RegisterSymbolStartAction + var text1 = @" +readonly record struct A(int X = 0) +{} + +readonly record struct C +{ + C(int Z = 4) + {} +} +"; + + var analyzer = new AnalyzerActions_03_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(0, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(0, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + Assert.Equal(1, analyzer.FireCount10); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + } + + private class AnalyzerActions_03_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + public int FireCount8; + public int FireCount9; + public int FireCount10; + public int FireCount11; + public int FireCount12; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolStartAction(Handle1, SymbolKind.Method); + context.RegisterSymbolStartAction(Handle1, SymbolKind.Property); + context.RegisterSymbolStartAction(Handle1, SymbolKind.Parameter); + context.RegisterSymbolStartAction(Handle1, SymbolKind.NamedType); + } + + private void Handle1(SymbolStartAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + context.RegisterSymbolEndAction(Handle2); + break; + case "System.Int32 A.X { get; init; }": + Interlocked.Increment(ref FireCount2); + context.RegisterSymbolEndAction(Handle3); + break; + case "[System.Int32 X = 0]": + Interlocked.Increment(ref FireCount3); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount4); + context.RegisterSymbolEndAction(Handle4); + break; + case "[System.Int32 Z = 4]": + Interlocked.Increment(ref FireCount5); + break; + case "A": + Interlocked.Increment(ref FireCount9); + + Assert.Equal(0, FireCount1); + Assert.Equal(0, FireCount2); + Assert.Equal(0, FireCount6); + Assert.Equal(0, FireCount7); + + context.RegisterSymbolEndAction(Handle5); + break; + case "C": + Interlocked.Increment(ref FireCount10); + + Assert.Equal(0, FireCount4); + Assert.Equal(0, FireCount8); + + context.RegisterSymbolEndAction(Handle6); + break; + case "System.Runtime.CompilerServices.IsExternalInit": + break; + default: + Assert.True(false); + break; + } + } + + private void Handle2(SymbolAnalysisContext context) + { + Assert.Equal("A..ctor([System.Int32 X = 0])", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount6); + } + + private void Handle3(SymbolAnalysisContext context) + { + Assert.Equal("System.Int32 A.X { get; init; }", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount7); + } + + private void Handle4(SymbolAnalysisContext context) + { + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount8); + } + + private void Handle5(SymbolAnalysisContext context) + { + Assert.Equal("A", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount11); + + Assert.Equal(1, FireCount1); + Assert.Equal(1, FireCount2); + Assert.Equal(1, FireCount6); + Assert.Equal(1, FireCount7); + } + + private void Handle6(SymbolAnalysisContext context) + { + Assert.Equal("C", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount12); + + Assert.Equal(1, FireCount4); + Assert.Equal(1, FireCount8); + } + } + + [Fact] + public void AnalyzerActions_04() + { + // Test RegisterOperationAction + var text1 = @" +record struct A([Attr1(100)]int X = 0) : I1 +{} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_04_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(0, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount14); + } + + private class AnalyzerActions_04_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount6; + public int FireCount7; + public int FireCount14; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationAction(HandleConstructorBody, OperationKind.ConstructorBody); + context.RegisterOperationAction(HandleInvocation, OperationKind.Invocation); + context.RegisterOperationAction(HandleLiteral, OperationKind.Literal); + context.RegisterOperationAction(HandleParameterInitializer, OperationKind.ParameterInitializer); + context.RegisterOperationAction(Fail, OperationKind.PropertyInitializer); + context.RegisterOperationAction(Fail, OperationKind.FieldInitializer); + } + + protected void HandleConstructorBody(OperationAnalysisContext context) + { + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + Assert.Equal(SyntaxKind.RecordDeclaration, context.Operation.Syntax.Kind()); + VerifyOperationTree((CSharpCompilation)context.Compilation, context.Operation, @""); + + break; + default: + Assert.True(false); + break; + } + } + + protected void HandleInvocation(OperationAnalysisContext context) + { + Assert.True(false); + } + + protected void HandleLiteral(OperationAnalysisContext context) + { + switch (context.Operation.Syntax.ToString()) + { + case "100": + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount6); + break; + case "0": + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount7); + break; + default: + Assert.True(false); + break; + } + } + + protected void HandleParameterInitializer(OperationAnalysisContext context) + { + switch (context.Operation.Syntax.ToString()) + { + case "= 0": + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount14); + break; + default: + Assert.True(false); + break; + } + } + + protected void Fail(OperationAnalysisContext context) + { + Assert.True(false); + } + } + + [Fact] + public void AnalyzerActions_05() + { + // Test RegisterOperationBlockAction + var text1 = @" +record struct A([Attr1(100)]int X = 0) : I1 +{} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_05_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + } + + private class AnalyzerActions_05_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationBlockAction(Handle); + } + + private void Handle(OperationBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + Assert.Equal(2, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 0", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr1(100)", context.OperationBlocks[1].Syntax.ToString()); + + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_07() + { + // Test RegisterCodeBlockAction + var text1 = @" +record struct A([Attr1(100)]int X = 0) : I1 +{ + int M() => 3; +} + +interface I1 {} +"; + var analyzer = new AnalyzerActions_07_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount4); + } + + private class AnalyzerActions_07_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount4; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterCodeBlockAction(Handle); + } + + private void Handle(CodeBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount1); + break; + default: + Assert.True(false); + break; + } + break; + case "System.Int32 A.M()": + switch (context.CodeBlock) + { + case MethodDeclarationSyntax { Identifier: { ValueText: "M" } }: + Interlocked.Increment(ref FireCount4); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_08() + { + // Test RegisterCodeBlockStartAction + var text1 = @" +record struct A([Attr1]int X = 0) : I1 +{ + private int M() => 3; + A(string S) : this(4) => throw null; +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_08_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount100); + Assert.Equal(1, analyzer.FireCount400); + Assert.Equal(1, analyzer.FireCount500); + + Assert.Equal(1, analyzer.FireCount0); + Assert.Equal(0, analyzer.FireCountRecordStructDeclarationA); + Assert.Equal(0, analyzer.FireCountRecordStructDeclarationACtor); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(0, analyzer.FireCountSimpleBaseTypeI1onA); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(0, analyzer.FireCountParameterListAPrimaryCtor); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(0, analyzer.FireCountConstructorDeclaration); + Assert.Equal(0, analyzer.FireCountStringParameterList); + Assert.Equal(1, analyzer.FireCountThisConstructorInitializer); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + Assert.Equal(1, analyzer.FireCount1000); + Assert.Equal(1, analyzer.FireCount4000); + Assert.Equal(1, analyzer.FireCount5000); + } + + private class AnalyzerActions_08_Analyzer : AnalyzerActions_01_Analyzer + { + public int FireCount100; + public int FireCount400; + public int FireCount500; + public int FireCount1000; + public int FireCount4000; + public int FireCount5000; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterCodeBlockStartAction(Handle); + } + + private void Handle(CodeBlockStartAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount100); + break; + default: + Assert.True(false); + break; + } + break; + case "System.Int32 A.M()": + switch (context.CodeBlock) + { + case MethodDeclarationSyntax { Identifier: { ValueText: "M" } }: + Interlocked.Increment(ref FireCount400); + break; + default: + Assert.True(false); + break; + } + break; + case "A..ctor(System.String S)": + switch (context.CodeBlock) + { + case ConstructorDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount500); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + + context.RegisterSyntaxNodeAction(Handle1, SyntaxKind.NumericLiteralExpression); + context.RegisterSyntaxNodeAction(Handle2, SyntaxKind.EqualsValueClause); + context.RegisterSyntaxNodeAction(Fail, SyntaxKind.BaseConstructorInitializer); + context.RegisterSyntaxNodeAction(Handle3, SyntaxKind.ThisConstructorInitializer); + context.RegisterSyntaxNodeAction(Handle4, SyntaxKind.ConstructorDeclaration); + context.RegisterSyntaxNodeAction(Fail, SyntaxKind.PrimaryConstructorBaseType); + context.RegisterSyntaxNodeAction(Handle6, SyntaxKind.RecordStructDeclaration); + context.RegisterSyntaxNodeAction(Handle7, SyntaxKind.IdentifierName); + context.RegisterSyntaxNodeAction(Handle8, SyntaxKind.SimpleBaseType); + context.RegisterSyntaxNodeAction(Handle9, SyntaxKind.ParameterList); + context.RegisterSyntaxNodeAction(Handle10, SyntaxKind.ArgumentList); + + context.RegisterCodeBlockEndAction(Handle11); + } + + private void Handle11(CodeBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount1000); + break; + default: + Assert.True(false); + break; + } + break; + case "System.Int32 A.M()": + switch (context.CodeBlock) + { + case MethodDeclarationSyntax { Identifier: { ValueText: "M" } }: + Interlocked.Increment(ref FireCount4000); + break; + default: + Assert.True(false); + break; + } + break; + case "A..ctor(System.String S)": + switch (context.CodeBlock) + { + case ConstructorDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount5000); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_09() + { + var text1 = @" +record A([Attr1(100)]int X = 0) : I1 +{} + +record B([Attr2(200)]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3(300)]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_09_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + } + + private class AnalyzerActions_09_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + public int FireCount8; + public int FireCount9; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(Handle1, SymbolKind.Method); + context.RegisterSymbolAction(Handle2, SymbolKind.Property); + context.RegisterSymbolAction(Handle3, SymbolKind.Parameter); + } + + private void Handle1(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + break; + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount2); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount3); + break; + case "System.Int32 B.M()": + Interlocked.Increment(ref FireCount4); + break; + default: + Assert.True(false); + break; + } + } + + private void Handle2(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "System.Int32 A.X { get; init; }": + Interlocked.Increment(ref FireCount5); + break; + case "System.Int32 B.Y { get; init; }": + Interlocked.Increment(ref FireCount6); + break; + default: + Assert.True(false); + break; + } + } + + private void Handle3(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "[System.Int32 X = 0]": + Interlocked.Increment(ref FireCount7); + break; + case "[System.Int32 Y = 1]": + Interlocked.Increment(ref FireCount8); + break; + case "[System.Int32 Z = 4]": + Interlocked.Increment(ref FireCount9); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void WithExprOnStruct_LangVersion() + { + var src = @" +var b = new B() { X = 1 }; +var b2 = b.M(); +System.Console.Write(b2.X); +System.Console.Write("" ""); +System.Console.Write(b.X); + +public struct B +{ + public int X { get; set; } + public B M() + /**/{ + return this with { X = 42 }; + }/**/ +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (13,16): error CS8652: The feature 'with on structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // return this with { X = 42 }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "this with { X = 42 }").WithArguments("with on structs").WithLocation(13, 16) + ); + + comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42 1"); + verifier.VerifyIL("B.M", @" +{ + // Code size 18 (0x12) + .maxstack 2 + .locals init (B V_0) + IL_0000: ldarg.0 + IL_0001: ldobj ""B"" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: ldc.i4.s 42 + IL_000b: call ""void B.X.set"" + IL_0010: ldloc.0 + IL_0011: ret +}"); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var with = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetTypeInfo(with); + Assert.Equal("B", type.Type.ToTestDisplayString()); + + var operation = model.GetOperation(with); + + VerifyOperationTree(comp, operation, @" +IWithOperation (OperationKind.With, Type: B) (Syntax: 'this with { X = 42 }') + Operand: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + CloneMethod: null + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: B) (Syntax: '{ X = 42 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 42') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'X') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'this') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 42') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') + Next (Return) Block[B2] + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExprOnStruct_ControlFlow_DuplicateInitialization() + { + var src = @" +public struct B +{ + public int X { get; set; } + + public B M() + /**/{ + return this with { X = 42, X = 43 }; + }/**/ +}"; + var expectedDiagnostics = new[] + { + // (8,36): error CS1912: Duplicate initialization of member 'X' + // return this with { X = 42, X = 43 }; + Diagnostic(ErrorCode.ERR_MemberAlreadyInitialized, "X").WithArguments("X").WithLocation(8, 36) + }; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'this') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 42') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid) (Syntax: 'X = 43') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 43) (Syntax: '43') + Next (Return) Block[B2] + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExprOnStruct_ControlFlow_NestedInitializer() + { + var src = @" +public struct C +{ + public int Y { get; set; } +} +public struct B +{ + public C X { get; set; } + + public B M() + /**/{ + return this with { X = { Y = 1 } }; + }/**/ +}"; + + var expectedDiagnostics = new[] + { + // (12,32): error CS1525: Invalid expression term '{' + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "{").WithArguments("{").WithLocation(12, 32), + // (12,32): error CS1513: } expected + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.ERR_RbraceExpected, "{").WithLocation(12, 32), + // (12,32): error CS1002: ; expected + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "{").WithLocation(12, 32), + // (12,34): error CS0103: The name 'Y' does not exist in the current context + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.ERR_NameNotInContext, "Y").WithArguments("Y").WithLocation(12, 34), + // (12,34): warning CS0162: Unreachable code detected + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.WRN_UnreachableCode, "Y").WithLocation(12, 34), + // (12,40): error CS1002: ; expected + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "}").WithLocation(12, 40), + // (12,43): error CS1597: Semicolon after method or accessor block is not valid + // return this with { X = { Y = 1 } }; + Diagnostic(ErrorCode.ERR_UnexpectedSemicolon, ";").WithLocation(12, 43), + // (14,1): error CS1022: Type or namespace definition, or end-of-file expected + // } + Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(14, 1) + }; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'this') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsInvalid) (Syntax: 'X = ') + Left: + IPropertyReferenceOperation: C B.X { get; set; } (OperationKind.PropertyReference, Type: C) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Right: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: '') + Children(0) + Next (Return) Block[B3] + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Leaving: {R1} +} +Block[B2] - Block [UnReachable] + Predecessors (0) + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 'Y = 1 ') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: 'Y = 1') + Left: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'Y') + Children(0) + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Next (Regular) Block[B3] +Block[B3] - Exit + Predecessors: [B1] [B2] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExprOnStruct_ControlFlow_NonAssignmentExpression() + { + var src = @" +public struct B +{ + public int X { get; set; } + + public B M(int i, int j) + /**/{ + return this with { i, j++, M2(), X = 2}; + }/**/ + + static int M2() => 0; +}"; + var expectedDiagnostics = new[] + { + // (8,28): error CS0747: Invalid initializer member declarator + // return this with { i, j++, M2(), X = 2}; + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "i").WithLocation(8, 28), + // (8,31): error CS0747: Invalid initializer member declarator + // return this with { i, j++, M2(), X = 2}; + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "j++").WithLocation(8, 31), + // (8,36): error CS0747: Invalid initializer member declarator + // return this with { i, j++, M2(), X = 2}; + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "M2()").WithLocation(8, 36) + }; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'this') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'i') + Children(1): + IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'i') + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'j++') + Children(1): + IIncrementOrDecrementOperation (Postfix) (OperationKind.Increment, Type: System.Int32, IsInvalid) (Syntax: 'j++') + Target: + IParameterReferenceOperation: j (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'j') + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'M2()') + Children(1): + IInvocationOperation (System.Int32 B.M2()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'M2()') + Instance Receiver: + null + Arguments(0) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 2') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + Next (Return) Block[B2] + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void ObjectCreationInitializer_ControlFlow_WithCoalescingExpressionForValue() + { + var src = @" +public struct B +{ + public string X; + + public void M(string hello) + /**/{ + var x = new B() { X = Identity((string)null) ?? Identity(hello) }; + }/**/ + + T Identity(T t) => t; +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [B x] + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'new B() { X ... ty(hello) }') + Value: + IObjectCreationOperation (Constructor: B..ctor()) (OperationKind.ObjectCreation, Type: B) (Syntax: 'new B() { X ... ty(hello) }') + Arguments(0) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} {R3} + .locals {R2} + { + CaptureIds: [2] + .locals {R3} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity((string)null)') + Value: + IInvocationOperation ( System.String B.Identity(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity((string)null)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Identity') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '(string)null') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null) (Syntax: '(string)null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + 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) + Jump if True (Regular) to Block[B4] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'Identity((string)null)') + Operand: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity((string)null)') + Leaving: {R3} + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity((string)null)') + Value: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity((string)null)') + Next (Regular) Block[B5] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(hello)') + Value: + IInvocationOperation ( System.String B.Identity(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity(hello)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Identity') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'hello') + IParameterReferenceOperation: hello (OperationKind.ParameterReference, Type: System.String) (Syntax: 'hello') + 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) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'X = Identit ... tity(hello)') + Left: + IFieldReferenceOperation: System.String B.X (OperationKind.FieldReference, Type: System.String) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'new B() { X ... ty(hello) }') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity((s ... tity(hello)') + Next (Regular) Block[B6] + Leaving: {R2} + } + Block[B6] - Block + Predecessors: [B5] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: B, IsImplicit) (Syntax: 'x = new B() ... ty(hello) }') + Left: + ILocalReferenceOperation: x (IsDeclaration: True) (OperationKind.LocalReference, Type: B, IsImplicit) (Syntax: 'x = new B() ... ty(hello) }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'new B() { X ... ty(hello) }') + Next (Regular) Block[B7] + Leaving: {R1} +} +Block[B7] - Exit + Predecessors: [B6] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void WithExprOnStruct_ControlFlow_WithCoalescingExpressionForValue() + { + var src = @" +var b = new B() { X = string.Empty }; +var b2 = b.M(""hello""); +System.Console.Write(b2.X); + +public struct B +{ + public string X; + + public B M(string hello) + /**/{ + return Identity(this) with { X = Identity((string)null) ?? Identity(hello) }; + }/**/ + + T Identity(T t) => t; +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "hello"); + 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: 'Identity(this)') + Value: + IInvocationOperation ( B B.Identity(B t)) (OperationKind.Invocation, Type: B) (Syntax: 'Identity(this)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Identity') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'this') + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + 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) + + Next (Regular) Block[B2] + Entering: {R2} {R3} + + .locals {R2} + { + CaptureIds: [2] + .locals {R3} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity((string)null)') + Value: + IInvocationOperation ( System.String B.Identity(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity((string)null)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Identity') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '(string)null') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null) (Syntax: '(string)null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + 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) + + Jump if True (Regular) to Block[B4] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'Identity((string)null)') + Operand: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity((string)null)') + Leaving: {R3} + + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity((string)null)') + Value: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity((string)null)') + + Next (Regular) Block[B5] + Leaving: {R3} + } + + Block[B4] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(hello)') + Value: + IInvocationOperation ( System.String B.Identity(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity(hello)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Identity') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'hello') + IParameterReferenceOperation: hello (OperationKind.ParameterReference, Type: System.String) (Syntax: 'hello') + 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) + + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'X = Identit ... tity(hello)') + Left: + IFieldReferenceOperation: System.String B.X (OperationKind.FieldReference, Type: System.String) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'Identity(this)') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity((s ... tity(hello)') + + Next (Regular) Block[B6] + Leaving: {R2} + } + + Block[B6] - Block + Predecessors: [B5] + Statements (0) + Next (Return) Block[B7] + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'Identity(this)') + Leaving: {R1} +} + +Block[B7] - Exit + Predecessors: [B6] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExprOnStruct_OnParameter() + { + var src = @" +var b = new B() { X = 1 }; +var b2 = B.M(b); +System.Console.Write(b2.X); + +public struct B +{ + public int X { get; set; } + public static B M(B b) + { + return b with { X = 42 }; + } +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("B.M", @" +{ + // Code size 13 (0xd) + .maxstack 2 + .locals init (B V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: call ""void B.X.set"" + IL_000b: ldloc.0 + IL_000c: ret +}"); + } + + [Fact] + public void WithExprOnStruct_OnThis() + { + var src = @" +record struct C +{ + public int X { get; set; } + + C(string ignored) + { + _ = this with { X = 42 }; // 1 + this = default; + } +} +"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (8,13): error CS0188: The 'this' object cannot be used before all of its fields have been assigned + // _ = this with { X = 42 }; // 1 + Diagnostic(ErrorCode.ERR_UseDefViolationThis, "this").WithArguments("this").WithLocation(8, 13) + ); + } + + [Fact] + public void WithExprOnStruct_OnTStructParameter() + { + var src = @" +var b = new B() { X = 1 }; +var b2 = B.M(b); +System.Console.Write(b2.X); + +public interface I +{ + int X { get; set; } +} + +public struct B : I +{ + public int X { get; set; } + public static T M(T b) where T : struct, I + { + return b with { X = 42 }; + } +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("B.M(T)", @" +{ + // Code size 19 (0x13) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: constrained. ""T"" + IL_000c: callvirt ""void I.X.set"" + IL_0011: ldloc.0 + IL_0012: ret +}"); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var with = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetTypeInfo(with); + Assert.Equal("T", type.Type.ToTestDisplayString()); + } + + [Fact] + public void WithExprOnStruct_OnRecordStructParameter() + { + var src = @" +var b = new B(1); +var b2 = B.M(b); +System.Console.Write(b2.X); + +public record struct B(int X) +{ + public static B M(B b) + { + return b with { X = 42 }; + } +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("B.M", @" +{ + // Code size 13 (0xd) + .maxstack 2 + .locals init (B V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: call ""void B.X.set"" + IL_000b: ldloc.0 + IL_000c: ret +}"); + } + + [Fact] + public void WithExprOnStruct_OnRecordStructParameter_Readonly() + { + var src = @" +var b = new B(1, 2); +var b2 = B.M(b); +System.Console.Write(b2.X); +System.Console.Write(b2.Y); + +public readonly record struct B(int X, int Y) +{ + public static B M(B b) + { + return b with { X = 42, Y = 43 }; + } +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "4243", verify: Verification.Skipped /* init-only */); + verifier.VerifyIL("B.M", @" +{ + // Code size 22 (0x16) + .maxstack 2 + .locals init (B V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: call ""void B.X.init"" + IL_000b: ldloca.s V_0 + IL_000d: ldc.i4.s 43 + IL_000f: call ""void B.Y.init"" + IL_0014: ldloc.0 + IL_0015: ret +}"); + } + + [Fact] + public void WithExprOnStruct_OnTuple() + { + var src = @" +class C +{ + static void Main() + { + var b = (1, 2); + var b2 = M(b); + System.Console.Write(b2.Item1); + System.Console.Write(b2.Item2); + } + + static (int, int) M((int, int) b) + { + return b with { Item1 = 42, Item2 = 43 }; + } +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "4243"); + verifier.VerifyIL("C.M", @" +{ + // Code size 22 (0x16) + .maxstack 2 + .locals init (System.ValueTuple V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: stfld ""int System.ValueTuple.Item1"" + IL_000b: ldloca.s V_0 + IL_000d: ldc.i4.s 43 + IL_000f: stfld ""int System.ValueTuple.Item2"" + IL_0014: ldloc.0 + IL_0015: ret +}"); + } + + [Fact] + public void WithExprOnStruct_OnTuple_WithNames() + { + var src = @" +var b = (1, 2); +var b2 = M(b); +System.Console.Write(b2.Item1); +System.Console.Write(b2.Item2); + +static (int, int) M((int X, int Y) b) +{ + return b with { X = 42, Y = 43 }; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "4243"); + verifier.VerifyIL("$.<
$>g__M|0_0(System.ValueTuple)", @" +{ + // Code size 22 (0x16) + .maxstack 2 + .locals init (System.ValueTuple V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: stfld ""int System.ValueTuple.Item1"" + IL_000b: ldloca.s V_0 + IL_000d: ldc.i4.s 43 + IL_000f: stfld ""int System.ValueTuple.Item2"" + IL_0014: ldloc.0 + IL_0015: ret +}"); + } + + [Fact] + public void WithExprOnStruct_OnTuple_LongTuple() + { + var src = @" +var b = (1, 2, 3, 4, 5, 6, 7, 8); +var b2 = M(b); +System.Console.Write(b2.Item7); +System.Console.Write(b2.Item8); + +static (int, int, int, int, int, int, int, int) M((int, int, int, int, int, int, int, int) b) +{ + return b with { Item7 = 42, Item8 = 43 }; +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "4243"); + verifier.VerifyIL("$.<
$>g__M|0_0(System.ValueTuple>)", @" +{ + // Code size 27 (0x1b) + .maxstack 2 + .locals init (System.ValueTuple> V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: ldc.i4.s 42 + IL_0006: stfld ""int System.ValueTuple>.Item7"" + IL_000b: ldloca.s V_0 + IL_000d: ldflda ""System.ValueTuple System.ValueTuple>.Rest"" + IL_0012: ldc.i4.s 43 + IL_0014: stfld ""int System.ValueTuple.Item1"" + IL_0019: ldloc.0 + IL_001a: ret +}"); + } + + [Fact] + public void WithExprOnStruct_OnReadonlyField() + { + var src = @" +var b = new B { X = 1 }; // 1 + +public struct B +{ + public readonly int X; + public B M() + { + return this with { X = 42 }; // 2 + } + public static B M2(B b) + { + return b with { X = 42 }; // 3 + } + public B(int i) + { + this = default; + _ = this with { X = 42 }; // 4 + } +}"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,17): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // var b = new B { X = 1 }; // 1 + Diagnostic(ErrorCode.ERR_AssgReadonly, "X").WithLocation(2, 17), + // (9,28): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // return this with { X = 42 }; // 2 + Diagnostic(ErrorCode.ERR_AssgReadonly, "X").WithLocation(9, 28), + // (13,25): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // return b with { X = 42 }; // 3 + Diagnostic(ErrorCode.ERR_AssgReadonly, "X").WithLocation(13, 25), + // (18,25): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // _ = this with { X = 42 }; // 4 + Diagnostic(ErrorCode.ERR_AssgReadonly, "X").WithLocation(18, 25) + ); + } + + [Fact] + public void WithExprOnStruct_OnEnum() + { + var src = @" +public enum E { } +class C +{ + static E M(E e) + { + return e with { }; + } +}"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void WithExprOnStruct_OnPointer() + { + var src = @" +unsafe class C +{ + static int* M(int* i) + { + return i with { }; + } +}"; + var comp = CreateCompilation(src, options: TestOptions.UnsafeReleaseDll, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (6,16): error CS8858: The receiver type 'int*' is not a valid record type and is not a struct type. + // return i with { }; + Diagnostic(ErrorCode.ERR_CannotClone, "i").WithArguments("int*").WithLocation(6, 16) + ); + } + + [Fact] + public void WithExprOnStruct_OnInterface() + { + var src = @" +public interface I +{ + int X { get; set; } +} +class C +{ + static I M(I i) + { + return i with { X = 42 }; + } +}"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (10,16): error CS8858: The receiver type 'I' is not a valid record type and is not a value type. + // return i with { X = 42 }; + Diagnostic(ErrorCode.ERR_CannotClone, "i").WithArguments("I").WithLocation(10, 16) + ); + } + + [Fact] + public void WithExprOnStruct_OnRefStruct() + { + // Similar to test RefLikeObjInitializers but with `with` expressions + var text = @" +using System; + +class Program +{ + static S2 Test1() + { + S1 outer = default; + S1 inner = stackalloc int[1]; + + // error + return new S2() with { Field1 = outer, Field2 = inner }; + } + + static S2 Test2() + { + S1 outer = default; + S1 inner = stackalloc int[1]; + + S2 result; + + // error + result = new S2() with { Field1 = inner, Field2 = outer }; + + return result; + } + + static S2 Test3() + { + S1 outer = default; + S1 inner = stackalloc int[1]; + + return new S2() with { Field1 = outer, Field2 = outer }; + } + + public ref struct S1 + { + public static implicit operator S1(Span o) => default; + } + + public ref struct S2 + { + public S1 Field1; + public S1 Field2; + } +} +"; + CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (12,48): error CS8352: Cannot use local 'inner' in this context because it may expose referenced variables outside of their declaration scope + // return new S2() with { Field1 = outer, Field2 = inner }; + Diagnostic(ErrorCode.ERR_EscapeLocal, "Field2 = inner").WithArguments("inner").WithLocation(12, 48), + // (23,34): error CS8352: Cannot use local 'inner' in this context because it may expose referenced variables outside of their declaration scope + // result = new S2() with { Field1 = inner, Field2 = outer }; + Diagnostic(ErrorCode.ERR_EscapeLocal, "Field1 = inner").WithArguments("inner").WithLocation(23, 34) + ); + } + + [Fact] + public void WithExprOnStruct_OnRefStruct_ReceiverMayWrap() + { + // Similar to test LocalWithNoInitializerEscape but wrapping method is used as receiver for `with` expression + var text = @" +using System; +class Program +{ + static void Main() + { + S1 sp; + Span local = stackalloc int[1]; + sp = MayWrap(ref local) with { }; // 1, 2 + } + + static S1 MayWrap(ref Span arg) + { + return default; + } + + ref struct S1 + { + public ref int this[int i] => throw null; + } +} +"; + CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (9,26): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // sp = MayWrap(ref local) with { }; // 1, 2 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(9, 26), + // (9,14): error CS8347: Cannot use a result of 'Program.MayWrap(ref Span)' in this context because it may expose variables referenced by parameter 'arg' outside of their declaration scope + // sp = MayWrap(ref local) with { }; // 1, 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "MayWrap(ref local)").WithArguments("Program.MayWrap(ref System.Span)", "arg").WithLocation(9, 14) + ); + } + + [Fact] + public void WithExprOnStruct_OnRefStruct_ReceiverMayWrap_02() + { + var text = @" +using System; +class Program +{ + static void Main() + { + Span local = stackalloc int[1]; + S1 sp = MayWrap(ref local) with { }; + } + + static S1 MayWrap(ref Span arg) + { + return default; + } + + ref struct S1 + { + public ref int this[int i] => throw null; + } +} +"; + CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(); + } + + [Fact] + public void WithExpr_NullableAnalysis_01() + { + var src = @" +#nullable enable +record struct B(int X) +{ + static void M(B b) + { + string? s = null; + _ = b with { X = M(out s) }; + s.ToString(); + } + + static int M(out string s) { s = ""a""; return 42; } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + } + + [Fact] + public void WithExpr_NullableAnalysis_02() + { + var src = @" +#nullable enable +record struct B(string X) +{ + static void M(B b, string? s) + { + b.X.ToString(); + _ = b with { X = s }; // 1 + b.X.ToString(); // 2 + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (8,26): warning CS8601: Possible null reference assignment. + // _ = b with { X = s }; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "s").WithLocation(8, 26)); + } + + [Fact] + public void WithExpr_NullableAnalysis_03() + { + var src = @" +#nullable enable +record struct B(string? X) +{ + static void M1(B b, string s, bool flag) + { + if (flag) { b.X.ToString(); } // 1 + _ = b with { X = s }; + if (flag) { b.X.ToString(); } // 2 + } + + static void M2(B b, string s, bool flag) + { + if (flag) { b.X.ToString(); } // 3 + b = b with { X = s }; + if (flag) { b.X.ToString(); } + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (7,21): warning CS8602: Dereference of a possibly null reference. + // if (flag) { b.X.ToString(); } // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.X").WithLocation(7, 21), + // (9,21): warning CS8602: Dereference of a possibly null reference. + // if (flag) { b.X.ToString(); } // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.X").WithLocation(9, 21), + // (14,21): warning CS8602: Dereference of a possibly null reference. + // if (flag) { b.X.ToString(); } // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.X").WithLocation(14, 21)); + } + + [Fact, WorkItem(44763, "https://github.com/dotnet/roslyn/issues/44763")] + public void WithExpr_NullableAnalysis_05() + { + var src = @" +#nullable enable +record struct B(string? X, string? Y) +{ + static void M1(bool flag) + { + B b = new B(""hello"", null); + if (flag) + { + b.X.ToString(); // shouldn't warn + b.Y.ToString(); // 1 + } + + b = b with { Y = ""world"" }; + b.X.ToString(); // shouldn't warn + b.Y.ToString(); + } +}"; + // records should propagate the nullability of the + // constructor arguments to the corresponding properties. + // https://github.com/dotnet/roslyn/issues/44763 + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (10,13): warning CS8602: Dereference of a possibly null reference. + // b.X.ToString(); // shouldn't warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.X").WithLocation(10, 13), + // (11,13): warning CS8602: Dereference of a possibly null reference. + // b.Y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.Y").WithLocation(11, 13), + // (15,9): warning CS8602: Dereference of a possibly null reference. + // b.X.ToString(); // shouldn't warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.X").WithLocation(15, 9)); + } + + [Fact] + public void WithExpr_NullableAnalysis_06() + { + var src = @" +#nullable enable +struct B +{ + public string? X { get; init; } + public string? Y { get; init; } + + static void M1(bool flag) + { + B b = new B { X = ""hello"", Y = null }; + if (flag) + { + b.X.ToString(); + b.Y.ToString(); // 1 + } + + b = b with { Y = ""world"" }; + + b.X.ToString(); + b.Y.ToString(); + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (14,13): warning CS8602: Dereference of a possibly null reference. + // b.Y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.Y").WithLocation(14, 13) + ); + } + + [Fact] + public void WithExprAssignToRef1() + { + var src = @" +using System; +record struct C(int Y) +{ + private readonly int[] _a = new[] { 0 }; + public ref int X => ref _a[0]; + + public static void Main() + { + var c = new C(0) { X = 5 }; + Console.WriteLine(c.X); + c = c with { X = 1 }; + Console.WriteLine(c.X); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @" +5 +1").VerifyDiagnostics(); + + verifier.VerifyIL("C.Main", @" +{ + // Code size 59 (0x3b) + .maxstack 2 + .locals init (C V_0, //c + C V_1) + IL_0000: ldloca.s V_1 + IL_0002: ldc.i4.0 + IL_0003: call ""C..ctor(int)"" + IL_0008: ldloca.s V_1 + IL_000a: call ""ref int C.X.get"" + IL_000f: ldc.i4.5 + IL_0010: stind.i4 + IL_0011: ldloc.1 + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: call ""ref int C.X.get"" + IL_001a: ldind.i4 + IL_001b: call ""void System.Console.WriteLine(int)"" + IL_0020: ldloc.0 + IL_0021: stloc.1 + IL_0022: ldloca.s V_1 + IL_0024: call ""ref int C.X.get"" + IL_0029: ldc.i4.1 + IL_002a: stind.i4 + IL_002b: ldloc.1 + IL_002c: stloc.0 + IL_002d: ldloca.s V_0 + IL_002f: call ""ref int C.X.get"" + IL_0034: ldind.i4 + IL_0035: call ""void System.Console.WriteLine(int)"" + IL_003a: ret +}"); + } + + [Fact] + public void WithExpressionSameLHS() + { + var comp = CreateCompilation(@" +record struct C(int X) +{ + public static void Main() + { + var c = new C(0); + c = c with { X = 1, X = 2}; + } +}"); + comp.VerifyDiagnostics( + // (7,29): error CS1912: Duplicate initialization of member 'X' + // c = c with { X = 1, X = 2}; + Diagnostic(ErrorCode.ERR_MemberAlreadyInitialized, "X").WithArguments("X").WithLocation(7, 29) + ); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeAllProperties() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = Identity(a) with { A = Identity(30), B = Identity(40) }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) + { + System.Console.Write($""Identity({t}) ""); + return t; + } +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (9,17): error CS8652: The feature 'with on anonymous types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var b = Identity(a) with { A = Identity(30), B = Identity(40) }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Identity(a) with { A = Identity(30), B = Identity(40) }").WithArguments("with on anonymous types").WithLocation(9, 17) + ); + + comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "Identity({ A = 10, B = 20 }) Identity(30) Identity(40) { A = 30, B = 40 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 42 (0x2a) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: call "" C.Identity<>()"" + IL_000e: pop + IL_000f: ldc.i4.s 30 + IL_0011: call ""int C.Identity(int)"" + IL_0016: ldc.i4.s 40 + IL_0018: call ""int C.Identity(int)"" + IL_001d: stloc.0 + IL_001e: ldloc.0 + IL_001f: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0024: call ""void System.Console.Write(object)"" + IL_0029: ret +} +"); + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} + } + .locals {R3} + { + CaptureIds: [2] [3] + Block[B2] - Block + Predecessors: [B1] + Statements (4) + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(30)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(40)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(40)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '40') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 40) (Syntax: '40') + 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) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = Identit ... ntity(40) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = Identit ... ntity(40) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'Identity(a) ... ntity(40) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B3] + Leaving: {R3} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeAllProperties_ReverseOrder() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = Identity(a) with { B = Identity(40), A = Identity(30) }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) + { + System.Console.Write($""Identity({t}) ""); + return t; + } +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "Identity({ A = 10, B = 20 }) Identity(40) Identity(30) { A = 30, B = 40 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 42 (0x2a) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: call "" C.Identity<>()"" + IL_000e: pop + IL_000f: ldc.i4.s 40 + IL_0011: call ""int C.Identity(int)"" + IL_0016: stloc.0 + IL_0017: ldc.i4.s 30 + IL_0019: call ""int C.Identity(int)"" + IL_001e: ldloc.0 + IL_001f: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0024: call ""void System.Console.Write(object)"" + IL_0029: ret +} +"); + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} + } + .locals {R3} + { + CaptureIds: [2] [3] + Block[B2] - Block + Predecessors: [B1] + Statements (4) + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(40)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(40)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '40') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 40) (Syntax: '40') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(30)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = Identit ... ntity(30) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = Identit ... ntity(30) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'Identity(a) ... ntity(30) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B3] + Leaving: {R3} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeNoProperty() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = M2(a) with { }; + System.Console.Write(b); + }/**/ + + static T M2(T t) + { + System.Console.Write(""M2 ""); + return t; + } +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "M2 { A = 10, B = 20 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 38 (0x26) + .maxstack 2 + .locals init (<>f__AnonymousType0 V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: call "" C.M2<>()"" + IL_000e: stloc.0 + IL_000f: ldloc.0 + IL_0010: callvirt ""int <>f__AnonymousType0.A.get"" + IL_0015: ldloc.0 + IL_0016: callvirt ""int <>f__AnonymousType0.B.get"" + IL_001b: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0020: call ""void System.Console.Write(object)"" + IL_0025: ret +} +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'M2(a)') + Value: + IInvocationOperation ( C.M2<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'M2(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'M2(a) with { }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'M2(a) with { }') + Value: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = M2(a) with { }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = M2(a) with { }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'M2(a) with { }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'M2(a) with { }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'M2(a) with { }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = a with { B = Identity(30) }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "{ A = 10, B = 30 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 34 (0x22) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: ldc.i4.s 30 + IL_000b: call ""int C.Identity(int)"" + IL_0010: stloc.0 + IL_0011: callvirt ""int <>f__AnonymousType0.A.get"" + IL_0016: ldloc.0 + IL_0017: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_001c: call ""void System.Console.Write(object)"" + IL_0021: ret +} +"); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var withExpr = tree.GetRoot().DescendantNodes().OfType().Single(); + var operation = model.GetOperation(withExpr); + + VerifyOperationTree(comp, operation, @" +IWithOperation (OperationKind.With, Type: ) (Syntax: 'a with { B ... ntity(30) }') + Operand: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + CloneMethod: null + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: ) (Syntax: '{ B = Identity(30) }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'B = Identity(30)') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'B') + Right: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a') + Value: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(30)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = a with ... ntity(30) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = a with ... ntity(30) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'a with { B ... ntity(30) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty_WithMethodCallForTarget() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = Identity(a) with { B = 30 }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "{ A = 10, B = 30 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 34 (0x22) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: call "" C.Identity<>()"" + IL_000e: ldc.i4.s 30 + IL_0010: stloc.0 + IL_0011: callvirt ""int <>f__AnonymousType0.A.get"" + IL_0016: ldloc.0 + IL_0017: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_001c: call ""void System.Console.Write(object)"" + IL_0021: ret +} +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '30') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = Identit ... { B = 30 }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = Identit ... { B = 30 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'Identity(a) ... { B = 30 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty_WithCoalescingExpressionForTarget() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = (Identity(a) ?? Identity2(a)) with { B = 30 }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; + static T Identity2(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "{ A = 10, B = 30 }"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} {R5} + } + .locals {R3} + { + CaptureIds: [4] [5] + .locals {R4} + { + CaptureIds: [2] + .locals {R5} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + Jump if True (Regular) to Block[B4] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'Identity(a)') + Operand: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Leaving: {R5} + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B5] + Leaving: {R5} + } + Block[B4] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity2(a)') + Value: + IInvocationOperation ( C.Identity2<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity2(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (2) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '30') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + IFlowCaptureOperation: 5 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... dentity2(a)') + Next (Regular) Block[B6] + Leaving: {R4} + } + Block[B6] - Block + Predecessors: [B5] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = (Identi ... { B = 30 }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = (Identi ... { B = 30 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: '(Identity(a ... { B = 30 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... dentity2(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... dentity2(a)') + Next (Regular) Block[B7] + Leaving: {R3} + } + Block[B7] - Block + Predecessors: [B6] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B8] + Leaving: {R1} + } + Block[B8] - Exit + Predecessors: [B7] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty_WithCoalescingExpressionForValue() + { + var src = @" +C.M(""hello"", ""world""); + +public class C +{ + public static void M(string hello, string world) + /**/{ + var x = new { A = hello, B = string.Empty }; + var y = x with { B = Identity(null) ?? Identity2(world) }; + System.Console.Write(y); + }/**/ + + static string Identity(string t) => t; + static string Identity2(string t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "{ A = hello, B = world }"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ x] [ y] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'hello') + Value: + IParameterReferenceOperation: hello (OperationKind.ParameterReference, Type: System.String) (Syntax: 'hello') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'string.Empty') + Value: + IFieldReferenceOperation: System.String System.String.Empty (Static) (OperationKind.FieldReference, Type: System.String) (Syntax: 'string.Empty') + Instance Receiver: + null + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'x = new { A ... ing.Empty }') + Left: + ILocalReferenceOperation: x (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'x = new { A ... ing.Empty }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = h ... ing.Empty }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'A = hello') + Left: + IPropertyReferenceOperation: System.String .A { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = h ... ing.Empty }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'hello') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'B = string.Empty') + Left: + IPropertyReferenceOperation: System.String .B { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = h ... ing.Empty }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'string.Empty') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [5] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'x') + Value: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: ) (Syntax: 'x') + Next (Regular) Block[B3] + Entering: {R5} + .locals {R5} + { + CaptureIds: [4] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(null)') + Value: + IInvocationOperation (System.String C.Identity(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity(null)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'null') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + 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) + Jump if True (Regular) to Block[B5] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'Identity(null)') + Operand: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity(null)') + Leaving: {R5} + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(null)') + Value: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity(null)') + Next (Regular) Block[B6] + Leaving: {R5} + } + Block[B5] - Block + Predecessors: [B3] + Statements (1) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity2(world)') + Value: + IInvocationOperation (System.String C.Identity2(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity2(world)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'world') + IParameterReferenceOperation: world (OperationKind.ParameterReference, Type: System.String) (Syntax: 'world') + 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) + Next (Regular) Block[B6] + Block[B6] - Block + Predecessors: [B4] [B5] + Statements (1) + IFlowCaptureOperation: 5 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Value: + IPropertyReferenceOperation: System.String .A { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'x') + Next (Regular) Block[B7] + Leaving: {R4} + } + Block[B7] - Block + Predecessors: [B6] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'y = x with ... y2(world) }') + Left: + ILocalReferenceOperation: y (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'y = x with ... y2(world) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'x with { B ... y2(world) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Left: + IPropertyReferenceOperation: System.String .A { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Right: + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'x') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Left: + IPropertyReferenceOperation: System.String .B { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'x') + Next (Regular) Block[B8] + Leaving: {R3} + } + Block[B8] - Block + Predecessors: [B7] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(y);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(y)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'y') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'y') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: ) (Syntax: 'y') + 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) + Next (Regular) Block[B9] + Leaving: {R1} +} +Block[B9] - Exit + Predecessors: [B8] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ErrorMember() + { + var src = @" +public class C +{ + public static void M() + /**/{ + var a = new { A = 10 }; + var b = a with { Error = Identity(20) }; + }/**/ + + static T Identity(T t) => t; +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + var expectedDiagnostics = new[] + { + // (7,26): error CS0117: '' does not contain a definition for 'Error' + // var b = a with { Error = Identity(20) }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("", "Error").WithLocation(7, 26) + }; + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [2] + .locals {R4} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a') + Value: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(20)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '20') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + 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) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'a with { Er ... ntity(20) }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { Er ... ntity(20) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with ... ntity(20) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with ... ntity(20) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: , IsInvalid) (Syntax: 'a with { Er ... ntity(20) }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { Er ... ntity(20) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { Er ... ntity(20) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'a with { Er ... ntity(20) }') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B4] + Leaving: {R3} {R1} + } +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ToString() + { + var src = @" +public class C +{ + public static void M() + /**/{ + var a = new { A = 10 }; + var b = a with { ToString = Identity(20) }; + }/**/ + + static T Identity(T t) => t; +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + var expectedDiagnostics = new[] + { + // (7,26): error CS1913: Member 'ToString' cannot be initialized. It is not a field or property. + // var b = a with { ToString = Identity(20) }; + Diagnostic(ErrorCode.ERR_MemberCannotBeInitialized, "ToString").WithArguments("ToString").WithLocation(7, 26) + }; + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [2] + .locals {R4} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a') + Value: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(20)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '20') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + 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) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'a with { To ... ntity(20) }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { To ... ntity(20) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with ... ntity(20) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with ... ntity(20) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: , IsInvalid) (Syntax: 'a with { To ... ntity(20) }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { To ... ntity(20) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { To ... ntity(20) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'a with { To ... ntity(20) }') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B4] + Leaving: {R3} {R1} + } +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_NestedInitializer() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var nested = new { A = 10 }; + var a = new { Nested = nested }; + var b = a with { Nested = { A = 20 } }; + System.Console.Write(b); + }/**/ +}"; + var expectedDiagnostics = new[] + { + // (10,35): error CS1525: Invalid expression term '{' + // var b = a with { Nested = { A = 20 } }; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "{").WithArguments("{").WithLocation(10, 35), + // (10,35): error CS1513: } expected + // var b = a with { Nested = { A = 20 } }; + Diagnostic(ErrorCode.ERR_RbraceExpected, "{").WithLocation(10, 35), + // (10,35): error CS1002: ; expected + // var b = a with { Nested = { A = 20 } }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "{").WithLocation(10, 35), + // (10,37): error CS0103: The name 'A' does not exist in the current context + // var b = a with { Nested = { A = 20 } }; + Diagnostic(ErrorCode.ERR_NameNotInContext, "A").WithArguments("A").WithLocation(10, 37), + // (10,44): error CS1002: ; expected + // var b = a with { Nested = { A = 20 } }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "}").WithLocation(10, 44), + // (10,47): error CS1597: Semicolon after method or accessor block is not valid + // var b = a with { Nested = { A = 20 } }; + Diagnostic(ErrorCode.ERR_UnexpectedSemicolon, ";").WithLocation(10, 47), + // (11,29): error CS1519: Invalid token '(' in class, record, struct, or interface member declaration + // System.Console.Write(b); + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "(").WithArguments("(").WithLocation(11, 29), + // (11,31): error CS8124: Tuple must contain at least two elements. + // System.Console.Write(b); + Diagnostic(ErrorCode.ERR_TupleTooFewElements, ")").WithLocation(11, 31), + // (11,32): error CS1519: Invalid token ';' in class, record, struct, or interface member declaration + // System.Console.Write(b); + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(11, 32), + // (13,1): error CS1022: Type or namespace definition, or end-of-file expected + // } + Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(13, 1) + }; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ nested] [ Nested> a] [ Nested> b] + .locals {R2} + { + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'nested = new { A = 10 }') + Left: + ILocalReferenceOperation: nested (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'nested = new { A = 10 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} + } + .locals {R3} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (2) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'nested') + Value: + ILocalReferenceOperation: nested (OperationKind.LocalReference, Type: ) (Syntax: 'nested') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: Nested>, IsImplicit) (Syntax: 'a = new { N ... = nested }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: Nested>, IsImplicit) (Syntax: 'a = new { N ... = nested }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: Nested>) (Syntax: 'new { Nested = nested }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ) (Syntax: 'Nested = nested') + Left: + IPropertyReferenceOperation: Nested>.Nested { get; } (OperationKind.PropertyReference, Type: ) (Syntax: 'Nested') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: Nested>, IsImplicit) (Syntax: 'new { Nested = nested }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'nested') + Next (Regular) Block[B3] + Leaving: {R3} + Entering: {R4} + } + .locals {R4} + { + CaptureIds: [2] + Block[B3] - Block + Predecessors: [B2] + Statements (3) + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: Nested>) (Syntax: 'a') + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '') + Value: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: '') + Children(0) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: Nested>, IsInvalid, IsImplicit) (Syntax: 'b = a with { Nested = ') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: Nested>, IsInvalid, IsImplicit) (Syntax: 'b = a with { Nested = ') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: Nested>, IsInvalid) (Syntax: 'a with { Nested = ') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'a with { Nested = ') + Left: + IPropertyReferenceOperation: Nested>.Nested { get; } (OperationKind.PropertyReference, Type: , IsInvalid, IsImplicit) (Syntax: 'a with { Nested = ') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: Nested>, IsInvalid, IsImplicit) (Syntax: 'a with { Nested = ') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: Nested>, IsImplicit) (Syntax: 'a') + Next (Regular) Block[B4] + Leaving: {R4} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 'A = 20 ') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: 'A = 20') + Left: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'A') + Children(0) + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_NonAssignmentExpression() + { + var src = @" +public class C +{ + public static void M(int i, int j) + /**/{ + var a = new { A = 10 }; + var b = a with { i, j++, M2(), A = 20 }; + }/**/ + + static int M2() => 0; +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + var expectedDiagnostics = new[] + { + // (7,26): error CS0747: Invalid initializer member declarator + // var b = a with { i, j++, M2(), A = 20 }; + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "i").WithLocation(7, 26), + // (7,29): error CS0747: Invalid initializer member declarator + // var b = a with { i, j++, M2(), A = 20 }; + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "j++").WithLocation(7, 29), + // (7,34): error CS0747: Invalid initializer member declarator + // var b = a with { i, j++, M2(), A = 20 }; + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "M2()").WithLocation(7, 34) + }; + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} + } + .locals {R3} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (6) + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'i') + Children(1): + IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'i') + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'j++') + Children(1): + IIncrementOrDecrementOperation (Postfix) (OperationKind.Increment, Type: System.Int32, IsInvalid) (Syntax: 'j++') + Target: + IParameterReferenceOperation: j (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'j') + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'M2()') + Children(1): + IInvocationOperation (System.Int32 C.M2()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'M2()') + Instance Receiver: + null + Arguments(0) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with ... ), A = 20 }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with ... ), A = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: , IsInvalid) (Syntax: 'a with { i, ... ), A = 20 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { i, ... ), A = 20 }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { i, ... ), A = 20 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'a with { i, ... ), A = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B3] + Leaving: {R3} {R1} + } +} +Block[B3] - Exit + Predecessors: [B2] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_IndexerAccess() + { + var src = @" +public class C +{ + public static void M() + /**/{ + var a = new { A = 10 }; + var b = a with { [0] = 20 }; + }/**/ +}"; + + var expectedDiagnostics = new[] + { + // (7,26): error CS1513: } expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_RbraceExpected, "[").WithLocation(7, 26), + // (7,26): error CS1002: ; expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "[").WithLocation(7, 26), + // (7,26): error CS7014: Attributes are not valid in this context. + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[").WithLocation(7, 26), + // (7,27): error CS1001: Identifier expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_IdentifierExpected, "0").WithLocation(7, 27), + // (7,27): error CS1003: Syntax error, ']' expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_SyntaxError, "0").WithArguments("]", "").WithLocation(7, 27), + // (7,28): error CS1002: ; expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "]").WithLocation(7, 28), + // (7,28): error CS1513: } expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_RbraceExpected, "]").WithLocation(7, 28), + // (7,30): error CS1525: Invalid expression term '=' + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "=").WithArguments("=").WithLocation(7, 30), + // (7,35): error CS1002: ; expected + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "}").WithLocation(7, 35), + // (7,36): error CS1597: Semicolon after method or accessor block is not valid + // var b = a with { [0] = 20 }; + Diagnostic(ErrorCode.ERR_UnexpectedSemicolon, ";").WithLocation(7, 36), + // (9,1): error CS1022: Type or namespace definition, or end-of-file expected + // } + Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(9, 1) + }; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} + +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [2] + .locals {R4} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (2) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a') + Value: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'a with { ') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { ') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + + Next (Regular) Block[B3] + Leaving: {R4} + } + + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with { ') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsInvalid, IsImplicit) (Syntax: 'b = a with { ') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: , IsInvalid) (Syntax: 'a with { ') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { ') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'a with { ') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'a with { ') + Right: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + + Next (Regular) Block[B4] + Leaving: {R3} + } + + Block[B4] - Block + Predecessors: [B3] + Statements (2) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '[0') + Expression: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid) (Syntax: '0') + + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '= 20 ') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: '= 20') + Left: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: '') + Children(0) + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + + Next (Regular) Block[B5] + Leaving: {R1} +} + +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_CannotSet() + { + var src = @" +public class C +{ + public static void M() + { + var a = new { A = 10 }; + a.A = 20; + + var b = new { B = a }; + b.B.A = 30; + } +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (7,9): error CS0200: Property or indexer '.A' cannot be assigned to -- it is read only + // a.A = 20; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "a.A").WithArguments(".A").WithLocation(7, 9), + // (10,9): error CS0200: Property or indexer '.A' cannot be assigned to -- it is read only + // b.B.A = 30; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "b.B.A").WithArguments(".A").WithLocation(10, 9) + ); + } + + [Fact] + public void WithExpr_AnonymousType_DuplicateMemberInDeclaration() + { + var src = @" +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, A = 20 }; + var b = Identity(a) with { A = Identity(30) }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; +}"; + var expectedDiagnostics = new[] + { + // (6,31): error CS0833: An anonymous type cannot have multiple properties with the same name + // var a = new { A = 10, A = 20 }; + Diagnostic(ErrorCode.ERR_AnonymousTypeDuplicatePropertyName, "A = 20").WithLocation(6, 31) + }; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + // TODO2 + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20, IsInvalid) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'a = new { A ... 0, A = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsInvalid, IsImplicit) (Syntax: 'a = new { A ... 0, A = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: , IsInvalid) (Syntax: 'new { A = 10, A = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'new { A = 10, A = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20, IsInvalid) (Syntax: 'A = 20') + Left: + IPropertyReferenceOperation: System.Int32 .$1 { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'new { A = 10, A = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsInvalid, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(30)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Value: + IPropertyReferenceOperation: System.Int32 .$1 { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = Identit ... ntity(30) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = Identit ... ntity(30) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'Identity(a) ... ntity(30) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .$1 { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_DuplicateInitialization() + { + var src = @" +public class C +{ + public static void M() + /**/{ + var a = new { A = 10 }; + var b = Identity(a) with { A = Identity(30), A = Identity(40) }; + }/**/ + + static T Identity(T t) => t; +}"; + var expectedDiagnostics = new[] + { + // (7,54): error CS1912: Duplicate initialization of member 'A' + // var b = Identity(a) with { A = Identity(30), A = Identity(40) }; + Diagnostic(ErrorCode.ERR_MemberAlreadyInitialized, "A").WithArguments("A").WithLocation(7, 54) + }; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A = 10 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} + } + .locals {R3} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (4) + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(30)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(40)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '40') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 40) (Syntax: '40') + 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) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsInvalid, IsImplicit) (Syntax: 'b = Identit ... ntity(40) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsInvalid, IsImplicit) (Syntax: 'b = Identit ... ntity(40) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: , IsInvalid) (Syntax: 'Identity(a) ... ntity(40) }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'Identity(a) ... ntity(40) }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B3] + Leaving: {R3} {R1} + } +} +Block[B3] - Exit + Predecessors: [B2] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_01() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true) ] +public class A : System.Attribute +{ +} +[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public readonly record struct Test( + [field: A] + [property: B] + [param: C] + [D] + int P1) +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + var prop1 = @class.GetMember("P1"); + AssertEx.SetEqual(new[] { "B" }, getAttributeStrings(prop1)); + + var field1 = @class.GetMember("k__BackingField"); + AssertEx.SetEqual(new[] { "A" }, getAttributeStrings(field1)); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics(); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => a.AttributeClass!.Name is "A" or "B" or "C" or "D")); + } + } + + [Fact] + public void FieldAsPositionalMember() + { + var source = @" +var a = new A(42); +System.Console.Write(a.X); +System.Console.Write("" - ""); +a.Deconstruct(out int x); +System.Console.Write(x); + +record struct A(int X) +{ + public int X = X; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (8,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct A(int X) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(8, 8), + // (8,17): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct A(int X) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "int X").WithArguments("positional fields in records").WithLocation(8, 17) + ); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42 - 42"); + verifier.VerifyIL("A.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: ldfld ""int A.X"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_Readonly() + { + var source = @" +readonly record struct A(int X) +{ + public int X = X; // 1 +} +readonly record struct B(int X) +{ + public readonly int X = X; +} +"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,16): error CS8340: Instance fields of readonly structs must be readonly. + // public int X = X; // 1 + Diagnostic(ErrorCode.ERR_FieldsInRoStruct, "X").WithLocation(4, 16) + ); + } + + [Fact] + public void FieldAsPositionalMember_Fixed() + { + var src = @" +unsafe record struct C(int[] P) +{ + public fixed int P[2]; + public int[] X = P; +}"; + var comp = CreateCompilation(src, options: TestOptions.UnsafeReleaseDll, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (2,30): error CS8866: Record member 'C.P' must be a readable instance property or field of type 'int[]' to match positional parameter 'P'. + // unsafe record struct C(int[] P) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("C.P", "int[]", "P").WithLocation(2, 30), + // (4,22): error CS8908: The type 'int*' may not be used for a field of a record. + // public fixed int P[2]; + Diagnostic(ErrorCode.ERR_BadFieldTypeInRecord, "P").WithArguments("int*").WithLocation(4, 22) + ); + } + + [Fact] + public void FieldAsPositionalMember_WrongType() + { + var source = @" +record struct A(int X) +{ + public string X = null; + public int Y = X; +} +"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (2,21): error CS8866: Record member 'A.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. + // record struct A(int X) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("A.X", "int", "X").WithLocation(2, 21) + ); + } + + [Fact] + public void FieldAsPositionalMember_DuplicateFields() + { + var source = @" +record struct A(int X) +{ + public int X = 0; + public int X = 0; + public int Y = X; +} +"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,16): error CS0102: The type 'A' already contains a definition for 'X' + // public int X = 0; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "X").WithArguments("A", "X").WithLocation(5, 16) + ); + } + + [Fact] + public void SyntaxFactory_TypeDeclaration() + { + var expected = @"record struct Point +{ +}"; + AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, SyntaxFactory.TypeDeclaration(SyntaxKind.RecordStructDeclaration, "Point").NormalizeWhitespace().ToString()); + } + + [Fact] + public void InterfaceWithParameters() + { + var src = @" +public interface I +{ +} + +record struct R(int X) : I() +{ +} + +record struct R2(int X) : I(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,27): error CS8861: Unexpected argument list. + // record struct R(int X) : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 27), + // (10,28): error CS8861: Unexpected argument list. + // record struct R2(int X) : I(X) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X)").WithLocation(10, 28) + ); + } + + [Fact] + public void InterfaceWithParameters_NoPrimaryConstructor() + { + var src = @" +public interface I +{ +} + +record struct R : I() +{ +} + +record struct R2 : I(0) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,20): error CS8861: Unexpected argument list. + // record struct R : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 20), + // (10,21): error CS8861: Unexpected argument list. + // record struct R2 : I(0) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(0)").WithLocation(10, 21) + ); + } + + [Fact] + public void InterfaceWithParameters_Struct() + { + var src = @" +public interface I +{ +} + +struct C : I() +{ +} + +struct C2 : I(0) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,13): error CS8861: Unexpected argument list. + // struct C : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 13), + // (10,14): error CS8861: Unexpected argument list. + // struct C2 : I(0) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(0)").WithLocation(10, 14) + ); + } + + [Fact] + public void BaseArguments_Speculation() + { + var src = @" +record struct R1(int X) : Error1(0, 1) +{ +} +record struct R2(int X) : Error2() +{ +} +record struct R3(int X) : Error3 +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,27): error CS0246: The type or namespace name 'Error1' could not be found (are you missing a using directive or an assembly reference?) + // record struct R1(int X) : Error1(0, 1) + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error1").WithArguments("Error1").WithLocation(2, 27), + // (2,33): error CS8861: Unexpected argument list. + // record struct R1(int X) : Error1(0, 1) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(0, 1)").WithLocation(2, 33), + // (5,27): error CS0246: The type or namespace name 'Error2' could not be found (are you missing a using directive or an assembly reference?) + // record struct R2(int X) : Error2() + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error2").WithArguments("Error2").WithLocation(5, 27), + // (5,33): error CS8861: Unexpected argument list. + // record struct R2(int X) : Error2() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(5, 33), + // (8,27): error CS0246: The type or namespace name 'Error3' could not be found (are you missing a using directive or an assembly reference?) + // record struct R3(int X) : Error3 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error3").WithArguments("Error3").WithLocation(8, 27) + ); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var baseWithargs = + tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("Error1(0, 1)", baseWithargs.ToString()); + + var speculativeBase = + baseWithargs.WithArgumentList(baseWithargs.ArgumentList.WithArguments(baseWithargs.ArgumentList.Arguments.RemoveAt(1))); + Assert.Equal("Error1(0)", speculativeBase.ToString()); + + Assert.False(model.TryGetSpeculativeSemanticModel(baseWithargs.ArgumentList.OpenParenToken.SpanStart, speculativeBase, out _)); + + var baseWithoutargs = + tree.GetRoot().DescendantNodes().OfType().Skip(1).First(); + Assert.Equal("Error2()", baseWithoutargs.ToString()); + + Assert.False(model.TryGetSpeculativeSemanticModel(baseWithoutargs.ArgumentList.OpenParenToken.SpanStart, speculativeBase, out _)); + + var baseWithoutParens = tree.GetRoot().DescendantNodes().OfType().Single(); + Assert.Equal("Error3", baseWithoutParens.ToString()); + + Assert.False(model.TryGetSpeculativeSemanticModel(baseWithoutParens.SpanStart + 2, speculativeBase, out _)); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 07ccbfca40513..9d48894caf363 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -24,7 +24,7 @@ public class RecordTests : CompilingTestBase { private static CSharpCompilation CreateCompilation(CSharpTestSource source) => CSharpTestBase.CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, - parseOptions: TestOptions.Regular9); + parseOptions: TestOptions.RegularPreview); private CompilationVerifier CompileAndVerify( CSharpTestSource src, @@ -33,7 +33,7 @@ private CompilationVerifier CompileAndVerify( => base.CompileAndVerify( new[] { src, IsExternalInitTypeDefinition }, expectedOutput: expectedOutput, - parseOptions: TestOptions.Regular9, + parseOptions: TestOptions.RegularPreview, references: references, // init-only is unverifiable verify: Verification.Skipped); @@ -149,6 +149,12 @@ record Point(int x, int y); comp = CreateCompilation(src3); comp.VerifyDiagnostics(); + + var point = comp.GlobalNamespace.GetTypeMember("Point"); + Assert.True(point.IsReferenceType); + Assert.False(point.IsValueType); + Assert.Equal(TypeKind.Class, point.TypeKind); + Assert.Equal(SpecialType.System_Object, point.BaseTypeNoUseSiteDiagnostics.SpecialType); } [Fact, WorkItem(45900, "https://github.com/dotnet/roslyn/issues/45900")] @@ -231,6 +237,60 @@ record Point(int x, int y); comp.VerifyDiagnostics(); } + [Fact, WorkItem(45900, "https://github.com/dotnet/roslyn/issues/45900")] + public void RecordClassLanguageVersion() + { + var src = @" +record class Point(int x, int y); +"; + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular8, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics( + // (2,1): error CS0116: A namespace cannot directly contain members such as fields or methods + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "record").WithLocation(2, 1), + // (2,19): error CS1514: { expected + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(2, 19), + // (2,19): error CS1513: } expected + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(2, 19), + // (2,19): error CS8400: Feature 'top-level statements' is not available in C# 8.0. Please use language version 9.0 or greater. + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "(int x, int y);").WithArguments("top-level statements", "9.0").WithLocation(2, 19), + // (2,19): error CS8803: Top-level statements must precede namespace and type declarations. + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "(int x, int y);").WithLocation(2, 19), + // (2,19): error CS8805: Program using top-level statements must be an executable. + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_SimpleProgramNotAnExecutable, "(int x, int y);").WithLocation(2, 19), + // (2,19): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_IllegalStatement, "(int x, int y)").WithLocation(2, 19), + // (2,20): error CS8185: A declaration is not allowed in this context. + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int x").WithLocation(2, 20), + // (2,20): error CS0165: Use of unassigned local variable 'x' + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_UseDefViolation, "int x").WithArguments("x").WithLocation(2, 20), + // (2,27): error CS8185: A declaration is not allowed in this context. + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int y").WithLocation(2, 27), + // (2,27): error CS0165: Use of unassigned local variable 'y' + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_UseDefViolation, "int y").WithArguments("y").WithLocation(2, 27) + ); + + comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics( + // (2,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record class Point(int x, int y); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "class").WithArguments("record structs").WithLocation(2, 8) + ); + + comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics(); + } + [CombinatorialData] [Theory, WorkItem(49302, "https://github.com/dotnet/roslyn/issues/49302")] public void GetSimpleNonTypeMembers(bool useCompilationReference) @@ -423,7 +483,7 @@ record R3(R3 x) : Base } [Fact, WorkItem(49628, "https://github.com/dotnet/roslyn/issues/49628")] - public void AmbigCtor_WithFieldInitializer() + public void AmbigCtor_WithPropertyInitializer() { var src = @" record R(R X) @@ -453,6 +513,32 @@ record R(R X) Assert.Equal("R..ctor(R X)", initializer.ContainingSymbol.ToTestDisplayString()); } + [Fact] + public void GetDeclaredSymbolOnAnOutLocalInPropertyInitializer() + { + var src = @" +record R(int I) +{ + public int I { get; init; } = M(out int i) ? i : 0; + static bool M(out int i) => throw null; +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,14): warning CS8907: Parameter 'I' is unread. Did you forget to use it to initialize the property with that name? + // record R(int I) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "I").WithArguments("I").WithLocation(2, 14) + ); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var outVarSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); + var outVar = model.GetDeclaredSymbol(outVarSyntax)!; + Assert.Equal("System.Int32 i", outVar.ToTestDisplayString()); + Assert.Equal(SymbolKind.Local, outVar.Kind); + Assert.Equal("System.Int32 R.k__BackingField", outVar.ContainingSymbol.ToTestDisplayString()); + } + [Fact, WorkItem(46123, "https://github.com/dotnet/roslyn/issues/46123")] public void IncompletePositionalRecord() { @@ -691,7 +777,7 @@ record A(x) } [Fact] - public void TestInExpressionTree() + public void TestWithInExpressionTree() { var source = @" using System; @@ -724,7 +810,7 @@ partial class C "; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (5,15): error CS0261: Partial declarations of 'C' must be all classes, all records, all structs, or all interfaces + // (5,15): error CS0261: Partial declarations of 'C' must be all classes, all record classes, all structs, all record structs, or all interfaces // partial class C Diagnostic(ErrorCode.ERR_PartialTypeKindConflict, "C").WithArguments("C").WithLocation(5, 15) ); @@ -750,6 +836,26 @@ public partial record C CompileAndVerify(comp, expectedOutput: "(2, 2)", verify: Verification.Skipped /* init-only */).VerifyDiagnostics(); } + [Fact] + public void PartialRecord_ParametersInScopeOfBothParts_RecordClass() + { + var src = @" +var c = new C(2); +System.Console.Write((c.P1, c.P2)); + +public partial record C(int X) +{ + public int P1 { get; set; } = X; +} +public partial record class C +{ + public int P2 { get; set; } = X; +} +"; + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(comp, expectedOutput: "(2, 2)", verify: Verification.Skipped /* init-only */).VerifyDiagnostics(); + } + [Fact] public void PartialRecord_DuplicateMemberNames() { @@ -845,6 +951,16 @@ .maxstack 2 IL_001c: ret } "); + + var c = verifier.Compilation.GlobalNamespace.GetTypeMember("C"); + var x = (IPropertySymbol)c.GetMember("X"); + Assert.Equal("System.Int32 C.X.get", x.GetMethod.ToTestDisplayString()); + Assert.Equal("void modreq(System.Runtime.CompilerServices.IsExternalInit) C.X.init", x.SetMethod.ToTestDisplayString()); + Assert.True(x.SetMethod!.IsInitOnly); + + var xBackingField = (IFieldSymbol)c.GetMember("k__BackingField"); + Assert.Equal("System.Int32 C.k__BackingField", xBackingField.ToTestDisplayString()); + Assert.True(xBackingField.IsReadOnly); } [Fact] @@ -983,6 +1099,58 @@ record C(int X, int X) AssertEx.Equal(expectedMemberNames, comp.GetMember("C").GetPublicSymbol().MemberNames); } + [Fact] + public void RecordProperties_05_RecordClass() + { + var src = @" +record class C(int X, int X) +{ +}"; + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (2,27): error CS0100: The parameter name 'X' is a duplicate + // record class C(int X, int X) + Diagnostic(ErrorCode.ERR_DuplicateParamName, "X").WithArguments("X").WithLocation(2, 27), + // (2,27): error CS0102: The type 'C' already contains a definition for 'X' + // record class C(int X, int X) + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "X").WithArguments("C", "X").WithLocation(2, 27) + ); + + var expectedMembers = new[] + { + "System.Type C.EqualityContract { get; }", + "System.Int32 C.X { get; init; }", + "System.Int32 C.X { get; init; }" + }; + AssertEx.Equal(expectedMembers, + comp.GetMember("C").GetMembers().OfType().ToTestDisplayStrings()); + + var expectedMemberNames = new[] { + ".ctor", + "get_EqualityContract", + "EqualityContract", + "k__BackingField", + "get_X", + "set_X", + "X", + "k__BackingField", + "get_X", + "set_X", + "X", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "$", + ".ctor", + "Deconstruct" + }; + AssertEx.Equal(expectedMemberNames, comp.GetMember("C").GetPublicSymbol().MemberNames); + } + [Fact] public void RecordProperties_06() { @@ -1074,14 +1242,17 @@ class P1 { } int P3(object o) => 3; int P4(T t) => 4; }"; - var comp = CreateCompilation(src); + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( // (1,17): error CS0102: The type 'C' already contains a definition for 'P1' // record C(object P1, object P2, object P3, object P4) Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P1").WithArguments("C", "P1").WithLocation(1, 17), - // (4,12): error CS0102: The type 'C' already contains a definition for 'P2' - // object P2 = 2; - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P2").WithArguments("C", "P2").WithLocation(4, 12), + // (1,21): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record C(object P1, object P2, object P3, object P4) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "object P2").WithArguments("positional fields in records").WithLocation(1, 21), + // (1,28): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? + // record C(object P1, object P2, object P3, object P4) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(1, 28), // (5,9): error CS0102: The type 'C' already contains a definition for 'P3' // int P3(object o) => 3; Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P3").WithArguments("C", "P3").WithLocation(5, 9), @@ -1089,21 +1260,21 @@ class P1 { } // int P4(T t) => 4; Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P4").WithArguments("C", "P4").WithLocation(6, 9) ); - } - [Fact] - public void RecordProperties_10() - { - var src = -@"record C(object P) -{ - const int P = 4; -}"; - var comp = CreateCompilation(src); + comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics( - // (3,15): error CS0102: The type 'C' already contains a definition for 'P' - // const int P = 4; - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(3, 15) + // (1,17): error CS0102: The type 'C' already contains a definition for 'P1' + // record C(object P1, object P2, object P3, object P4) + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P1").WithArguments("C", "P1").WithLocation(1, 17), + // (1,28): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? + // record C(object P1, object P2, object P3, object P4) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(1, 28), + // (5,9): error CS0102: The type 'C' already contains a definition for 'P3' + // int P3(object o) => 3; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P3").WithArguments("C", "P3").WithLocation(5, 9), + // (6,9): error CS0102: The type 'C' already contains a definition for 'P4' + // int P4(T t) => 4; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P4").WithArguments("C", "P4").WithLocation(6, 9) ); } @@ -1479,6 +1650,57 @@ .maxstack 1 AssertEx.Equal(expectedMembers, actualMembers); } + [Fact] + public void EmptyRecord_01_RecordClass() + { + var src = @" +record class C(); + +class Program +{ + static void Main() + { + var x = new C(); + var y = new C(); + System.Console.WriteLine(x == y); + } +} +"; + + var verifier = CompileAndVerify(src, expectedOutput: "True", parseOptions: TestOptions.RegularPreview); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C..ctor()", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ret +} +"); + + var comp = (CSharpCompilation)verifier.Compilation; + + var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); + var expectedMembers = new[] + { + "C..ctor()", + "System.Type C.EqualityContract.get", + "System.Type C.EqualityContract { get; }", + "System.String C.ToString()", + "System.Boolean C." + WellKnownMemberNames.PrintMembersMethodName + "(System.Text.StringBuilder builder)", + "System.Boolean C.op_Inequality(C? left, C? right)", + "System.Boolean C.op_Equality(C? left, C? right)", + "System.Int32 C.GetHashCode()", + "System.Boolean C.Equals(System.Object? obj)", + "System.Boolean C.Equals(C? other)", + "C C." + WellKnownMemberNames.CloneMethodName + "()", + "C..ctor(C original)" + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + [Fact] public void EmptyRecord_02() { @@ -1593,185 +1815,6 @@ static R(R r) { } ); } - [Fact(Skip = "record struct")] - public void StructRecord1() - { - var src = @" -data struct Point(int X, int Y);"; - - var verifier = CompileAndVerify(src).VerifyDiagnostics(); - verifier.VerifyIL("Point.Equals(object)", @" -{ - // Code size 26 (0x1a) - .maxstack 2 - .locals init (Point V_0) - IL_0000: ldarg.1 - IL_0001: isinst ""Point"" - IL_0006: brtrue.s IL_000a - IL_0008: ldc.i4.0 - IL_0009: ret - IL_000a: ldarg.0 - IL_000b: ldarg.1 - IL_000c: unbox.any ""Point"" - IL_0011: stloc.0 - IL_0012: ldloca.s V_0 - IL_0014: call ""bool Point.Equals(in Point)"" - IL_0019: ret -}"); - verifier.VerifyIL("Point.Equals(in Point)", @" -{ - // Code size 49 (0x31) - .maxstack 3 - IL_0000: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" - IL_0005: ldarg.0 - IL_0006: ldfld ""int Point.k__BackingField"" - IL_000b: ldarg.1 - IL_000c: ldfld ""int Point.k__BackingField"" - IL_0011: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" - IL_0016: brfalse.s IL_002f - IL_0018: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" - IL_001d: ldarg.0 - IL_001e: ldfld ""int Point.k__BackingField"" - IL_0023: ldarg.1 - IL_0024: ldfld ""int Point.k__BackingField"" - IL_0029: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" - IL_002e: ret - IL_002f: ldc.i4.0 - IL_0030: ret -}"); - } - - [Fact(Skip = "record struct")] - public void StructRecord2() - { - var src = @" -using System; -data struct S(int X, int Y) -{ - public static void Main() - { - var s1 = new S(0, 1); - var s2 = new S(0, 1); - Console.WriteLine(s1.X); - Console.WriteLine(s1.Y); - Console.WriteLine(s1.Equals(s2)); - Console.WriteLine(s1.Equals(new S(1, 0))); - } -}"; - var verifier = CompileAndVerify(src, expectedOutput: @"0 -1 -True -False").VerifyDiagnostics(); - } - - [Fact(Skip = "record struct")] - public void StructRecord3() - { - var src = @" -using System; -data struct S(int X, int Y) -{ - public bool Equals(S s) => false; - public static void Main() - { - var s1 = new S(0, 1); - Console.WriteLine(s1.Equals(s1)); - Console.WriteLine(s1.Equals(in s1)); - } -}"; - var verifier = CompileAndVerify(src, expectedOutput: @"False -True").VerifyDiagnostics(); - - verifier.VerifyIL("S.Main", @" -{ - // Code size 37 (0x25) - .maxstack 3 - .locals init (S V_0) //s1 - IL_0000: ldloca.s V_0 - IL_0002: ldc.i4.0 - IL_0003: ldc.i4.1 - IL_0004: call ""S..ctor(int, int)"" - IL_0009: ldloca.s V_0 - IL_000b: ldloc.0 - IL_000c: call ""bool S.Equals(S)"" - IL_0011: call ""void System.Console.WriteLine(bool)"" - IL_0016: ldloca.s V_0 - IL_0018: ldloca.s V_0 - IL_001a: call ""bool S.Equals(in S)"" - IL_001f: call ""void System.Console.WriteLine(bool)"" - IL_0024: ret -}"); - } - - [Fact(Skip = "record struct")] - public void StructRecord4() - { - var src = @" -using System; -data struct S(int X, int Y) -{ - public override bool Equals(object o) - { - Console.WriteLine(""obj""); - return true; - } - public bool Equals(in S s) - { - Console.WriteLine(""s""); - return true; - } - public static void Main() - { - var s1 = new S(0, 1); - s1.Equals((object)s1); - s1.Equals(s1); - } -}"; - var verifier = CompileAndVerify(src, expectedOutput: @"obj -s").VerifyDiagnostics(); - } - - [Fact(Skip = "record struct")] - public void StructRecord5() - { - var src = @" -using System; -data struct S(int X, int Y) -{ - public bool Equals(in S s) - { - Console.WriteLine(""s""); - return true; - } - public static void Main() - { - var s1 = new S(0, 1); - s1.Equals((object)s1); - s1.Equals(s1); - } -}"; - var verifier = CompileAndVerify(src, expectedOutput: @"s -s").VerifyDiagnostics(); - } - - [Fact(Skip = "record struct")] - public void StructRecordDefaultCtor() - { - const string src = @" -public data struct S(int X);"; - const string src2 = @" -class C -{ - public S M() => new S(); -}"; - var comp = CreateCompilation(src + src2); - comp.VerifyDiagnostics(); - - comp = CreateCompilation(src); - var comp2 = CreateCompilation(src2, references: new[] { comp.EmitToImageReference() }); - comp2.VerifyDiagnostics(); - } - [Fact] public void WithExpr1() { @@ -2202,7 +2245,7 @@ public static void Main() comp.VerifyDiagnostics( // (12,13): error CS8803: The 'with' expression requires the receiver type 'B' to have a single accessible non-inherited instance method named "Clone". // b = b with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "b").WithArguments("B").WithLocation(12, 13) + Diagnostic(ErrorCode.ERR_CannotClone, "b").WithArguments("B").WithLocation(12, 13) ); } @@ -2228,7 +2271,7 @@ public static void Main() comp.VerifyDiagnostics( // (12,13): error CS8803: The receiver type 'B' does not have an accessible parameterless instance method named "Clone". // b = b with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "b").WithArguments("B").WithLocation(12, 13) + Diagnostic(ErrorCode.ERR_CannotClone, "b").WithArguments("B").WithLocation(12, 13) ); } @@ -2253,7 +2296,7 @@ public static void Main() comp.VerifyDiagnostics( // (12,13): error CS8803: The 'with' expression requires the receiver type 'B' to have a single accessible non-inherited instance method named "Clone". // b = b with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "b").WithArguments("B").WithLocation(12, 13) + Diagnostic(ErrorCode.ERR_CannotClone, "b").WithArguments("B").WithLocation(12, 13) ); } @@ -2348,7 +2391,7 @@ public static void Main() comp.VerifyDiagnostics( // (12,13): error CS8858: The receiver type 'B' is not a valid record type. // b = b with { Y = 2 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "b").WithArguments("B").WithLocation(12, 13), + Diagnostic(ErrorCode.ERR_CannotClone, "b").WithArguments("B").WithLocation(12, 13), // (12,22): error CS0117: 'B' does not contain a definition for 'Y' // b = b with { Y = 2 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Y").WithArguments("B", "Y").WithLocation(12, 22) @@ -2371,7 +2414,7 @@ public static void Main() comp.VerifyDiagnostics( // (7,17): error CS8858: The receiver type 'dynamic' is not a valid record type. // var x = c with { X = 2 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("dynamic").WithLocation(7, 17) + Diagnostic(ErrorCode.ERR_CannotClone, "c").WithArguments("dynamic").WithLocation(7, 17) ); } @@ -2565,7 +2608,7 @@ public static void M(T t) comp.VerifyDiagnostics( // (6,13): error CS8858: The receiver type 'T' is not a valid record type. // _ = t with { X = 2 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "t").WithArguments("T").WithLocation(6, 13), + Diagnostic(ErrorCode.ERR_CannotClone, "t").WithArguments("T").WithLocation(6, 13), // (6,22): error CS0117: 'T' does not contain a definition for 'X' // _ = t with { X = 2 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("T", "X").WithLocation(6, 22) @@ -2589,7 +2632,7 @@ public static void M(T t) where T : I comp.VerifyDiagnostics( // (8,13): error CS8858: The receiver type 'T' is not a valid record type. // _ = t with { X = 2, Property = 3 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "t").WithArguments("T").WithLocation(8, 13), + Diagnostic(ErrorCode.ERR_CannotClone, "t").WithArguments("T").WithLocation(8, 13), // (8,22): error CS0117: 'T' does not contain a definition for 'X' // _ = t with { X = 2, Property = 3 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("T", "X").WithLocation(8, 22) @@ -2642,7 +2685,7 @@ public static void M(T t) where T : I comp.VerifyDiagnostics( // (6,13): error CS8858: The receiver type 'T' is not a valid record type. // _ = t with { X = 2, Property = 3 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "t").WithArguments("T").WithLocation(6, 13), + Diagnostic(ErrorCode.ERR_CannotClone, "t").WithArguments("T").WithLocation(6, 13), // (6,22): error CS0117: 'T' does not contain a definition for 'X' // _ = t with { X = 2, Property = 3 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("T", "X").WithLocation(6, 22) @@ -2722,9 +2765,19 @@ public override void M(U t) }"; var comp = CreateCompilationWithIL(new[] { src, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (11,13): error CS8858: The receiver type 'U' is not a valid record type. + // (11,13): error CS8652: The feature 'with on structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // _ = t with { X = 2, Property = 3 }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "t with { X = 2, Property = 3 }").WithArguments("with on structs").WithLocation(11, 13), + // (11,22): error CS0117: 'U' does not contain a definition for 'X' + // _ = t with { X = 2, Property = 3 }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("U", "X").WithLocation(11, 22), + // (11,29): error CS0117: 'U' does not contain a definition for 'Property' // _ = t with { X = 2, Property = 3 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "t").WithArguments("U").WithLocation(11, 13), + Diagnostic(ErrorCode.ERR_NoSuchMember, "Property").WithArguments("U", "Property").WithLocation(11, 29) + ); + + comp = CreateCompilationWithIL(new[] { src, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( // (11,22): error CS0117: 'U' does not contain a definition for 'X' // _ = t with { X = 2, Property = 3 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("U", "X").WithLocation(11, 22), @@ -3625,6 +3678,12 @@ .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::get_EqualityContract() } + + .method family hidebysig newslot virtual instance bool PrintMembers ( class [mscorlib]System.Text.StringBuilder builder ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } } // end of class A "; var source = @" @@ -3737,7 +3796,7 @@ static void Main() comp.VerifyEmitDiagnostics( // (6,15): error CS8858: The receiver type 'A' is not a valid record type. // A x = new A() with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "new A()").WithArguments("A").WithLocation(6, 15) + Diagnostic(ErrorCode.ERR_CannotClone, "new A()").WithArguments("A").WithLocation(6, 15) ); Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A") @@ -3835,6 +3894,12 @@ .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::get_EqualityContract() } + + .method family hidebysig newslot virtual instance bool PrintMembers ( class [mscorlib]System.Text.StringBuilder builder ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } } // end of class A "; var source = @" @@ -3957,7 +4022,7 @@ static void Main() comp.VerifyEmitDiagnostics( // (6,15): error CS8858: The receiver type 'A' is not a valid record type. // A x = new A() with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "new A()").WithArguments("A").WithLocation(6, 15) + Diagnostic(ErrorCode.ERR_CannotClone, "new A()").WithArguments("A").WithLocation(6, 15) ); Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A") @@ -4054,6 +4119,12 @@ .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::get_EqualityContract() } + + .method family hidebysig newslot virtual instance bool PrintMembers ( class [mscorlib]System.Text.StringBuilder builder ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } } // end of class A "; var source = @" @@ -4176,7 +4247,7 @@ static void Main() comp.VerifyEmitDiagnostics( // (6,15): error CS8858: The receiver type 'A' is not a valid record type. // A x = new A() with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "new A()").WithArguments("A").WithLocation(6, 15) + Diagnostic(ErrorCode.ERR_CannotClone, "new A()").WithArguments("A").WithLocation(6, 15) ); Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A") @@ -4284,7 +4355,7 @@ public static void Main() comp4.VerifyEmitDiagnostics( // (7,18): error CS8858: The receiver type 'C' is not a valid record type. // var c2 = c1 with { X = 11 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c1").WithArguments("C").WithLocation(7, 18), + Diagnostic(ErrorCode.ERR_CannotClone, "c1").WithArguments("C").WithLocation(7, 18), // (7,18): error CS0012: The type 'B' is defined in an assembly that is not referenced. You must add a reference to assembly 'Clone_13, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. // var c2 = c1 with { X = 11 }; Diagnostic(ErrorCode.ERR_NoTypeDef, "c1").WithArguments("B", "Clone_13, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 18), @@ -4300,7 +4371,7 @@ public static void Main() comp5.VerifyEmitDiagnostics( // (7,18): error CS8858: The receiver type 'C' is not a valid record type. // var c2 = c1 with { X = 11 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c1").WithArguments("C").WithLocation(7, 18), + Diagnostic(ErrorCode.ERR_CannotClone, "c1").WithArguments("C").WithLocation(7, 18), // (7,18): error CS0012: The type 'B' is defined in an assembly that is not referenced. You must add a reference to assembly 'Clone_13, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. // var c2 = c1 with { X = 11 }; Diagnostic(ErrorCode.ERR_NoTypeDef, "c1").WithArguments("B", "Clone_13, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 18), @@ -4569,6 +4640,12 @@ .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::get_EqualityContract() } + + .method family hidebysig newslot virtual instance bool PrintMembers ( class [mscorlib]System.Text.StringBuilder builder ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } } // end of class A "; var source = @" @@ -4666,6 +4743,12 @@ .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::get_EqualityContract() } + + .method family hidebysig newslot virtual instance bool PrintMembers ( class [mscorlib]System.Text.StringBuilder builder ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } } // end of class A "; var source = @" @@ -5281,7 +5364,7 @@ record C1 "; var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "C1 { P1 = 43 }", verify: Verification.Skipped /* init-only */); + CompileAndVerify(comp, expectedOutput: "C1 { P1 = 43 }"); comp.VerifyEmitDiagnostics(); } @@ -5353,6 +5436,9 @@ record C2: Error; // (2,8): error CS0115: 'C2.GetHashCode()': no suitable method found to override // record C2: Error; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.GetHashCode()").WithLocation(2, 8), + // (2,8): error CS0115: 'C2.PrintMembers(StringBuilder)': no suitable method found to override + // record C2: Error; + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 8), // (2,12): error CS0246: The type or namespace name 'Error' could not be found (are you missing a using directive or an assembly reference?) // record C2: Error; Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error").WithArguments("Error").WithLocation(2, 12) @@ -5382,7 +5468,10 @@ record R : R; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R").WithArguments("R.Equals(object?)").WithLocation(2, 8), // (2,8): error CS0115: 'R.GetHashCode()': no suitable method found to override // record R : R; - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R").WithArguments("R.GetHashCode()").WithLocation(2, 8) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R").WithArguments("R.GetHashCode()").WithLocation(2, 8), + // (2,8): error CS0115: 'R.PrintMembers(StringBuilder)': no suitable method found to override + // record R : R; + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R").WithArguments("R.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 8) ); } @@ -5432,7 +5521,7 @@ record C1 var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.DebugExe); comp.VerifyEmitDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "C1 { field = 42 }", verify: Verification.Skipped /* init-only */); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field = 42 }"); var print = comp.GetMember("C1." + WellKnownMemberNames.PrintMembersMethodName); Assert.Equal(Accessibility.Protected, print.DeclaredAccessibility); @@ -5490,7 +5579,7 @@ record C1 where T : struct var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.DebugExe); comp.VerifyEmitDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "C1 { field = 42 }", verify: Verification.Skipped /* init-only */); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field = 42 }"); v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { @@ -5532,7 +5621,7 @@ record C1 var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9, options: TestOptions.DebugExe); comp.VerifyEmitDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "C1 { field = hello }", verify: Verification.Skipped /* init-only */); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field = hello }"); v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { @@ -5661,7 +5750,7 @@ record C1 // private protected string field7; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field7").WithArguments("C1.field7", "null").WithLocation(14, 30) ); - var v = CompileAndVerify(comp, expectedOutput: "C1 { field1 = hi, field2 = }", verify: Verification.Skipped /* init-only */); + var v = CompileAndVerify(comp, expectedOutput: "C1 { field1 = hi, field2 = }"); v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { @@ -6423,12 +6512,9 @@ public record B : A { }"; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); comp.VerifyEmitDiagnostics( - // (2,15): warning CS0108: 'B.PrintMembers(StringBuilder)' hides inherited member 'A.PrintMembers(StringBuilder)'. Use the new keyword if hiding was intended. - // public record B : A { - Diagnostic(ErrorCode.WRN_NewRequired, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)", "A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15), - // (2,19): error CS8864: Records may only inherit from object or another record + // (2,15): error CS0506: 'B.PrintMembers(StringBuilder)': cannot override inherited member 'A.PrintMembers(StringBuilder)' because it is not marked virtual, abstract, or override // public record B : A { - Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19) + Diagnostic(ErrorCode.ERR_CantOverrideNonVirtual, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)", "A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15) ); } @@ -6498,9 +6584,9 @@ public record B : A { }"; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); comp.VerifyEmitDiagnostics( - // (2,19): error CS8864: Records may only inherit from object or another record + // (2,15): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override // public record B : A { - Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15) ); } @@ -6570,12 +6656,81 @@ public record B : A { }"; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); comp.VerifyEmitDiagnostics( - // (2,15): warning CS0114: 'B.PrintMembers(StringBuilder)' hides inherited member 'A.PrintMembers(StringBuilder)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. + // (2,15): error CS0508: 'B.PrintMembers(StringBuilder)': return type must be 'int' to match overridden member 'A.PrintMembers(StringBuilder)' // public record B : A { - Diagnostic(ErrorCode.WRN_NewOrOverrideExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)", "A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15), - // (2,19): error CS8864: Records may only inherit from object or another record + Diagnostic(ErrorCode.ERR_CantChangeReturnTypeOnOverride, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)", "A.PrintMembers(System.Text.StringBuilder)", "int").WithLocation(2, 15) + ); + } + + [Fact] + public void EqualityContract_BadBase_ReturnsInt() + { + var ilSource = @" +.class public auto ansi beforefieldinit A + extends System.Object +{ + .method public hidebysig specialname newslot virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .method public hidebysig virtual instance bool Equals ( object other ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .method public hidebysig virtual instance int32 GetHashCode () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .method public newslot virtual instance bool Equals ( class A '' ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .method family hidebysig specialname rtspecialname instance void .ctor ( class A '' ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .method public hidebysig specialname rtspecialname instance void .ctor () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .method family hidebysig newslot virtual instance int32 get_EqualityContract () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + + .property instance int32 EqualityContract() + { + .get instance int32 A::get_EqualityContract() + } + + .method family hidebysig newslot virtual instance bool '" + WellKnownMemberNames.PrintMembersMethodName + @"' (class [mscorlib]System.Text.StringBuilder builder) cil managed + { + IL_0000: ldnull + IL_0001: throw + } +} +"; + var source = @" +public record B : A { +}"; + var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (2,15): error CS1715: 'B.EqualityContract': type must be 'int' to match overridden member 'A.EqualityContract' // public record B : A { - Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19) + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "B").WithArguments("B.EqualityContract", "A.EqualityContract", "int").WithLocation(2, 15) ); } @@ -6650,14 +6805,7 @@ .property instance class [mscorlib]System.Type EqualityContract() public record B : A { }"; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); - comp.VerifyEmitDiagnostics( - // (2,15): warning CS0114: 'B.PrintMembers(StringBuilder)' hides inherited member 'A.PrintMembers(StringBuilder)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. - // public record B : A { - Diagnostic(ErrorCode.WRN_NewOrOverrideExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)", "A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15), - // (2,19): error CS8864: Records may only inherit from object or another record - // public record B : A { - Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -6720,9 +6868,9 @@ public record B : A { }"; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); comp.VerifyEmitDiagnostics( - // (2,19): error CS8864: Records may only inherit from object or another record + // (2,15): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override // public record B : A { - Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15) ); } @@ -6803,14 +6951,7 @@ .method public hidebysig virtual instance string ToString () cil managed public record B : A { }"; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); - comp.VerifyEmitDiagnostics( - // (2,15): warning CS0114: 'B.PrintMembers(StringBuilder)' hides inherited member 'A.PrintMembers(StringBuilder)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. - // public record B : A { - Diagnostic(ErrorCode.WRN_NewOrOverrideExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)", "A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 15), - // (2,19): error CS8864: Records may only inherit from object or another record - // public record B : A { - Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -6960,13 +7101,20 @@ public record C : B "; var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); comp.VerifyEmitDiagnostics( - // (2,19): error CS8864: Records may only inherit from object or another record - // public record C : B - Diagnostic(ErrorCode.ERR_BadRecordBase, "B").WithLocation(2, 19), // (4,29): error CS8871: 'C.PrintMembers(StringBuilder)' does not override expected method from 'B'. // protected override bool PrintMembers(System.Text.StringBuilder builder) => throw null; Diagnostic(ErrorCode.ERR_DoesNotOverrideBaseMethod, "PrintMembers").WithArguments("C.PrintMembers(System.Text.StringBuilder)", "B").WithLocation(4, 29) ); + + var source2 = @" +public record C : B; +"; + comp = CreateCompilationWithIL(new[] { source2, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (2,15): error CS8871: 'C.PrintMembers(StringBuilder)' does not override expected method from 'B'. + // public record C : B; + Diagnostic(ErrorCode.ERR_DoesNotOverrideBaseMethod, "C").WithArguments("C.PrintMembers(System.Text.StringBuilder)", "B").WithLocation(2, 15) + ); } [Fact] @@ -7944,7 +8092,7 @@ public static void Main() comp.VerifyDiagnostics( // (8,13): error CS8808: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". // c = c with { X = ""-3 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(8, 13), + Diagnostic(ErrorCode.ERR_CannotClone, "c").WithArguments("C").WithLocation(8, 13), // (8,26): error CS0019: Operator '-' cannot be applied to operands of type 'string' and 'int' // c = c with { X = ""-3 }; Diagnostic(ErrorCode.ERR_BadBinaryOps, @"""""-3").WithArguments("-", "string", "int").WithLocation(8, 26) @@ -8463,10 +8611,10 @@ public static void Main() comp.VerifyDiagnostics( // (9,13): error CS8808: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". // c = c with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(9, 13), + Diagnostic(ErrorCode.ERR_CannotClone, "c").WithArguments("C").WithLocation(9, 13), // (10,13): error CS8808: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". // c = c with { X = 11 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(10, 13) + Diagnostic(ErrorCode.ERR_CannotClone, "c").WithArguments("C").WithLocation(10, 13) ); } @@ -8774,7 +8922,7 @@ static void Main() comp.VerifyDiagnostics( // (11,13): error CS8858: The receiver type 'R' is not a valid record type. // _ = r with { P = 2 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "r").WithArguments("R").WithLocation(11, 13)); + Diagnostic(ErrorCode.ERR_CannotClone, "r").WithArguments("R").WithLocation(11, 13)); } [Fact] @@ -9634,6 +9782,40 @@ public static void Main() ); } + [Fact] + public void WithExpr_ValEscape() + { + var text = @" +using System; +record R +{ + public S1 Property { set { throw null; } } +} + +class Program +{ + static void Main() + { + var r = new R(); + Span local = stackalloc int[1]; + _ = r with { Property = MayWrap(ref local) }; + _ = new R() { Property = MayWrap(ref local) }; + } + + static S1 MayWrap(ref Span arg) + { + return default; + } +} + +ref struct S1 +{ + public ref int this[int i] => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(); + } + [WorkItem(44616, "https://github.com/dotnet/roslyn/issues/44616")] [Fact] public void Inheritance_01() @@ -9772,7 +9954,7 @@ record B(object P1, object P2, object P3, object P4, object P5, object P6, objec // (12,39): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P3").WithArguments("P3").WithLocation(12, 39), - // (12,50): error CS8866: Record member 'A.P4' must be a readable instance property of type 'object' to match positional parameter 'P4'. + // (12,50): error CS8866: Record member 'A.P4' must be a readable instance property or field of type 'object' to match positional parameter 'P4'. // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P4").WithArguments("A.P4", "object", "P4").WithLocation(12, 50), // (12,50): warning CS8907: Parameter 'P4' is unread. Did you forget to use it to initialize the property with that name? @@ -9781,7 +9963,7 @@ record B(object P1, object P2, object P3, object P4, object P5, object P6, objec // (12,61): warning CS8907: Parameter 'P5' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P5").WithArguments("P5").WithLocation(12, 61), - // (12,72): error CS8866: Record member 'A.P6' must be a readable instance property of type 'object' to match positional parameter 'P6'. + // (12,72): error CS8866: Record member 'A.P6' must be a readable instance property or field of type 'object' to match positional parameter 'P6'. // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P6").WithArguments("A.P6", "object", "P6").WithLocation(12, 72), // (12,72): warning CS8907: Parameter 'P6' is unread. Did you forget to use it to initialize the property with that name? @@ -9809,13 +9991,13 @@ record B(int P1, object P2) : A }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (7,14): error CS8866: Record member 'A.P1' must be a readable instance property of type 'int' to match positional parameter 'P1'. + // (7,14): error CS8866: Record member 'A.P1' must be a readable instance property or field of type 'int' to match positional parameter 'P1'. // record B(int P1, object P2) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("A.P1", "int", "P1").WithLocation(7, 14), // (7,14): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record B(int P1, object P2) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(7, 14), - // (7,25): error CS8866: Record member 'A.P2' must be a readable instance property of type 'object' to match positional parameter 'P2'. + // (7,25): error CS8866: Record member 'A.P2' must be a readable instance property or field of type 'object' to match positional parameter 'P2'. // record B(int P1, object P2) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P2").WithArguments("A.P2", "object", "P2").WithLocation(7, 25), // (7,25): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? @@ -9851,20 +10033,20 @@ static void Main() ((A)b).Z = 9; } }"; - var comp = CreateCompilation(source); + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( // (7,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? // record B(int X, int Y, int Z) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(7, 14), - // (7,21): error CS8866: Record member 'A.Y' must be a readable instance property of type 'int' to match positional parameter 'Y'. + // (7,21): error CS8866: Record member 'A.Y' must be a readable instance property or field of type 'int' to match positional parameter 'Y'. // record B(int X, int Y, int Z) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Y").WithArguments("A.Y", "int", "Y").WithLocation(7, 21), // (7,21): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? // record B(int X, int Y, int Z) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "Y").WithArguments("Y").WithLocation(7, 21), - // (7,28): error CS8866: Record member 'A.Z' must be a readable instance property of type 'int' to match positional parameter 'Z'. + // (7,24): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // record B(int X, int Y, int Z) : A - Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Z").WithArguments("A.Z", "int", "Z").WithLocation(7, 28), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "int Z").WithArguments("positional fields in records").WithLocation(7, 24), // (7,28): warning CS8907: Parameter 'Z' is unread. Did you forget to use it to initialize the property with that name? // record B(int X, int Y, int Z) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "Z").WithArguments("Z").WithLocation(7, 28)); @@ -10171,7 +10353,7 @@ record C(object P1, int P2, object P3, int P4) : B }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (13,17): error CS8866: Record member 'B.P1' must be a readable instance property of type 'object' to match positional parameter 'P1'. + // (13,17): error CS8866: Record member 'B.P1' must be a readable instance property or field of type 'object' to match positional parameter 'P1'. // record C(object P1, int P2, object P3, int P4) : B Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("B.P1", "object", "P1").WithLocation(13, 17), // (13,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? @@ -10183,7 +10365,7 @@ record C(object P1, int P2, object P3, int P4) : B // (13,36): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? // record C(object P1, int P2, object P3, int P4) : B Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P3").WithArguments("P3").WithLocation(13, 36), - // (13,44): error CS8866: Record member 'A.P4' must be a readable instance property of type 'int' to match positional parameter 'P4'. + // (13,44): error CS8866: Record member 'A.P4' must be a readable instance property or field of type 'int' to match positional parameter 'P4'. // record C(object P1, int P2, object P3, int P4) : B Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P4").WithArguments("A.P4", "int", "P4").WithLocation(13, 44), // (13,44): warning CS8907: Parameter 'P4' is unread. Did you forget to use it to initialize the property with that name? @@ -10204,13 +10386,13 @@ public void Inheritance_15() }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (1,14): error CS8866: Record member 'C.P1' must be a readable instance property of type 'int' to match positional parameter 'P1'. + // (1,14): error CS8866: Record member 'C.P1' must be a readable instance property or field of type 'int' to match positional parameter 'P1'. // record C(int P1, object P2) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("C.P1", "int", "P1").WithLocation(1, 14), // (1,14): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record C(int P1, object P2) Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(1, 14), - // (1,25): error CS8866: Record member 'C.P2' must be a readable instance property of type 'object' to match positional parameter 'P2'. + // (1,25): error CS8866: Record member 'C.P2' must be a readable instance property or field of type 'object' to match positional parameter 'P2'. // record C(int P1, object P2) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P2").WithArguments("C.P2", "object", "P2").WithLocation(1, 25), // (1,25): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? @@ -10247,13 +10429,13 @@ record B(object P1, int P2, object P3, int P4) : A // (8,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(8, 17), - // (8,25): error CS8866: Record member 'B.P2' must be a readable instance property of type 'int' to match positional parameter 'P2'. + // (8,25): error CS8866: Record member 'B.P2' must be a readable instance property or field of type 'int' to match positional parameter 'P2'. // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P2").WithArguments("B.P2", "int", "P2").WithLocation(8, 25), // (8,25): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(8, 25), - // (8,36): error CS8866: Record member 'A.P3' must be a readable instance property of type 'object' to match positional parameter 'P3'. + // (8,36): error CS8866: Record member 'A.P3' must be a readable instance property or field of type 'object' to match positional parameter 'P3'. // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P3").WithArguments("A.P3", "object", "P3").WithLocation(8, 36), // (8,36): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? @@ -10290,7 +10472,7 @@ record B(object P1, int P2, object P3, int P4) : A }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (8,17): error CS8866: Record member 'B.P1' must be a readable instance property of type 'object' to match positional parameter 'P1'. + // (8,17): error CS8866: Record member 'B.P1' must be a readable instance property or field of type 'object' to match positional parameter 'P1'. // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("B.P1", "object", "P1").WithLocation(8, 17), // (8,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? @@ -10302,7 +10484,7 @@ record B(object P1, int P2, object P3, int P4) : A // (8,36): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P3").WithArguments("P3").WithLocation(8, 36), - // (8,44): error CS8866: Record member 'A.P4' must be a readable instance property of type 'int' to match positional parameter 'P4'. + // (8,44): error CS8866: Record member 'A.P4' must be a readable instance property or field of type 'int' to match positional parameter 'P4'. // record B(object P1, int P2, object P3, int P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P4").WithArguments("A.P4", "int", "P4").WithLocation(8, 44), // (8,44): warning CS8907: Parameter 'P4' is unread. Did you forget to use it to initialize the property with that name? @@ -10339,13 +10521,13 @@ public object P3 { set { } } // (1,28): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? // record C(object P1, object P2, object P3, object P4, object P5) Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(1, 28), - // (1,39): error CS8866: Record member 'C.P3' must be a readable instance property of type 'object' to match positional parameter 'P3'. + // (1,39): error CS8866: Record member 'C.P3' must be a readable instance property or field of type 'object' to match positional parameter 'P3'. // record C(object P1, object P2, object P3, object P4, object P5) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P3").WithArguments("C.P3", "object", "P3").WithLocation(1, 39), // (1,39): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? // record C(object P1, object P2, object P3, object P4, object P5) Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P3").WithArguments("P3").WithLocation(1, 39), - // (1,50): error CS8866: Record member 'C.P4' must be a readable instance property of type 'object' to match positional parameter 'P4'. + // (1,50): error CS8866: Record member 'C.P4' must be a readable instance property or field of type 'object' to match positional parameter 'P4'. // record C(object P1, object P2, object P3, object P4, object P5) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P4").WithArguments("C.P4", "object", "P4").WithLocation(1, 50), // (1,50): warning CS8907: Parameter 'P4' is unread. Did you forget to use it to initialize the property with that name? @@ -10615,7 +10797,7 @@ record C(object P1, object P2) : B // (11,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record C(object P1, object P2) : B Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(11, 17), - // (11,28): error CS8866: Record member 'B.P2' must be a readable instance property of type 'object' to match positional parameter 'P2'. + // (11,28): error CS8866: Record member 'B.P2' must be a readable instance property or field of type 'object' to match positional parameter 'P2'. // record C(object P1, object P2) : B Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P2").WithArguments("B.P2", "object", "P2").WithLocation(11, 28), // (11,28): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? @@ -10715,21 +10897,21 @@ public class P1 { } @"record B(object P1, object P2, object P3, object P4) : A { }"; - var comp = CreateCompilation(new[] { sourceA, sourceB }); + var comp = CreateCompilation(new[] { sourceA, sourceB, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (1,17): error CS8866: Record member 'A.P1' must be a readable instance property of type 'object' to match positional parameter 'P1'. + // (1,17): error CS8866: Record member 'A.P1' must be a readable instance property or field of type 'object' to match positional parameter 'P1'. // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("A.P1", "object", "P1").WithLocation(1, 17), // (1,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(1, 17), - // (1,28): error CS8866: Record member 'A.P2' must be a readable instance property of type 'object' to match positional parameter 'P2'. + // (1,21): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // record B(object P1, object P2, object P3, object P4) : A - Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P2").WithArguments("A.P2", "object", "P2").WithLocation(1, 28), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "object P2").WithArguments("positional fields in records").WithLocation(1, 21), // (1,28): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P2").WithArguments("P2").WithLocation(1, 28), - // (1,39): error CS8866: Record member 'A.P3' must be a readable instance property of type 'object' to match positional parameter 'P3'. + // (1,39): error CS8866: Record member 'A.P3' must be a readable instance property or field of type 'object' to match positional parameter 'P3'. // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P3").WithArguments("A.P3", "object", "P3").WithLocation(1, 39), // (1,39): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? @@ -10747,13 +10929,13 @@ public class P1 { } var refA = comp.EmitToImageReference(); comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (1,17): error CS8866: Record member 'A.P1' must be a readable instance property of type 'object' to match positional parameter 'P1'. + // (1,17): error CS8866: Record member 'A.P1' must be a readable instance property or field of type 'object' to match positional parameter 'P1'. // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("A.P1", "object", "P1").WithLocation(1, 17), // (1,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(1, 17), - // (1,39): error CS8866: Record member 'A.P3' must be a readable instance property of type 'object' to match positional parameter 'P3'. + // (1,39): error CS8866: Record member 'A.P3' must be a readable instance property or field of type 'object' to match positional parameter 'P3'. // record B(object P1, object P2, object P3, object P4) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P3").WithArguments("A.P3", "object", "P3").WithLocation(1, 39), // (1,39): warning CS8907: Parameter 'P3' is unread. Did you forget to use it to initialize the property with that name? @@ -10783,7 +10965,7 @@ public void Inheritance_26() }"; var comp = CreateCompilation(new[] { sourceA, sourceB }); comp.VerifyDiagnostics( - // (1,17): error CS8866: Record member 'A.P' must be a readable instance property of type 'object' to match positional parameter 'P'. + // (1,17): error CS8866: Record member 'A.P' must be a readable instance property or field of type 'object' to match positional parameter 'P'. // record B(object P) : A Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("A.P", "object", "P").WithLocation(1, 17), // (1,17): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? @@ -10885,6 +11067,9 @@ End Class // (1,8): error CS0115: 'B.Equals(A?)': no suitable method found to override // record B(object P, object Q) : A Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.Equals(A?)").WithLocation(1, 8), + // (1,8): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override + // record B(object P, object Q) : A + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(1, 8), // (1,8): error CS8867: No accessible copy constructor found in base type 'A'. // record B(object P, object Q) : A Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "B").WithArguments("A").WithLocation(1, 8), @@ -10953,6 +11138,9 @@ End Class // (1,8): error CS0115: 'B.Equals(A?)': no suitable method found to override // record B(object P, object Q) : A Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.Equals(A?)").WithLocation(1, 8), + // (1,8): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override + // record B(object P, object Q) : A + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(1, 8), // (1,8): error CS8867: No accessible copy constructor found in base type 'A'. // record B(object P, object Q) : A Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "B").WithArguments("A").WithLocation(1, 8), @@ -10965,7 +11153,7 @@ End Class // (1,32): error CS8864: Records may only inherit from object or another record // record B(object P, object Q) : A Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(1, 32) - ); + ); var actualMembers = GetProperties(compB, "B").ToTestDisplayStrings(); AssertEx.Equal(new[] { "System.Type B.EqualityContract { get; }" }, actualMembers); @@ -11040,6 +11228,9 @@ End Class // (1,8): error CS0115: 'C.Equals(B?)': no suitable method found to override // record C(object P, object Q, object R) : B Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C").WithArguments("C.Equals(B?)").WithLocation(1, 8), + // (1,8): error CS0115: 'C.PrintMembers(StringBuilder)': no suitable method found to override + // record C(object P, object Q, object R) : B + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C").WithArguments("C.PrintMembers(System.Text.StringBuilder)").WithLocation(1, 8), // (1,8): error CS7036: There is no argument given that corresponds to the required formal parameter 'b' of 'B.B(B)' // record C(object P, object Q, object R) : B Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "C").WithArguments("b", "B.B(B)").WithLocation(1, 8), @@ -11052,7 +11243,7 @@ End Class // (1,42): error CS8864: Records may only inherit from object or another record // record C(object P, object Q, object R) : B Diagnostic(ErrorCode.ERR_BadRecordBase, "B").WithLocation(1, 42) - ); + ); var actualMembers = GetProperties(compB, "C").ToTestDisplayStrings(); var expectedMembers = new[] @@ -11093,19 +11284,19 @@ record B(object P1, object P2, object P3, object P4, object P5, object P6, objec // (11,50): warning CS8907: Parameter 'P4' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A; Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P4").WithArguments("P4").WithLocation(11, 50), - // (11,61): error CS8866: Record member 'A.P5' must be a readable instance property of type 'object' to match positional parameter 'P5'. + // (11,61): error CS8866: Record member 'A.P5' must be a readable instance property or field of type 'object' to match positional parameter 'P5'. // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P5").WithArguments("A.P5", "object", "P5").WithLocation(11, 61), // (11,61): warning CS8907: Parameter 'P5' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A; Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P5").WithArguments("P5").WithLocation(11, 61), - // (11,72): error CS8866: Record member 'A.P6' must be a readable instance property of type 'object' to match positional parameter 'P6'. + // (11,72): error CS8866: Record member 'A.P6' must be a readable instance property or field of type 'object' to match positional parameter 'P6'. // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P6").WithArguments("A.P6", "object", "P6").WithLocation(11, 72), // (11,72): warning CS8907: Parameter 'P6' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A; Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P6").WithArguments("P6").WithLocation(11, 72), - // (11,83): error CS8866: Record member 'A.P7' must be a readable instance property of type 'object' to match positional parameter 'P7'. + // (11,83): error CS8866: Record member 'A.P7' must be a readable instance property or field of type 'object' to match positional parameter 'P7'. // record B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P7").WithArguments("A.P7", "object", "P7").WithLocation(11, 83), // (11,83): warning CS8907: Parameter 'P7' is unread. Did you forget to use it to initialize the property with that name? @@ -11151,13 +11342,13 @@ record B(object P1, object P2, object P3, object P4, object P5, object P6) : A; // (10,50): error CS0507: 'B.P4.get': cannot change access modifiers when overriding 'protected' inherited member 'A.P4.get' // record B(object P1, object P2, object P3, object P4, object P5, object P6) : A; Diagnostic(ErrorCode.ERR_CantChangeAccessOnOverride, "P4").WithArguments("B.P4.get", "protected", "A.P4.get").WithLocation(10, 50), - // (10,61): error CS8866: Record member 'A.P5' must be a readable instance property of type 'object' to match positional parameter 'P5'. + // (10,61): error CS8866: Record member 'A.P5' must be a readable instance property or field of type 'object' to match positional parameter 'P5'. // record B(object P1, object P2, object P3, object P4, object P5, object P6) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P5").WithArguments("A.P5", "object", "P5").WithLocation(10, 61), // (10,61): warning CS8907: Parameter 'P5' is unread. Did you forget to use it to initialize the property with that name? // record B(object P1, object P2, object P3, object P4, object P5, object P6) : A; Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P5").WithArguments("P5").WithLocation(10, 61), - // (10,72): error CS8866: Record member 'A.P6' must be a readable instance property of type 'object' to match positional parameter 'P6'. + // (10,72): error CS8866: Record member 'A.P6' must be a readable instance property or field of type 'object' to match positional parameter 'P6'. // record B(object P1, object P2, object P3, object P4, object P5, object P6) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P6").WithArguments("A.P6", "object", "P6").WithLocation(10, 72), // (10,72): warning CS8907: Parameter 'P6' is unread. Did you forget to use it to initialize the property with that name? @@ -11196,13 +11387,13 @@ record B(string P1, string P2) : A; // (6,8): error CS0534: 'B' does not implement inherited abstract member 'A.P1.get' // record B(string P1, string P2) : A; Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B").WithArguments("B", "A.P1.get").WithLocation(6, 8), - // (6,17): error CS8866: Record member 'A.P1' must be a readable instance property of type 'string' to match positional parameter 'P1'. + // (6,17): error CS8866: Record member 'A.P1' must be a readable instance property or field of type 'string' to match positional parameter 'P1'. // record B(string P1, string P2) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P1").WithArguments("A.P1", "string", "P1").WithLocation(6, 17), // (6,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? // record B(string P1, string P2) : A; Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(6, 17), - // (6,28): error CS8866: Record member 'A.P2' must be a readable instance property of type 'string' to match positional parameter 'P2'. + // (6,28): error CS8866: Record member 'A.P2' must be a readable instance property or field of type 'string' to match positional parameter 'P2'. // record B(string P1, string P2) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P2").WithArguments("A.P2", "string", "P2").WithLocation(6, 28), // (6,28): warning CS8907: Parameter 'P2' is unread. Did you forget to use it to initialize the property with that name? @@ -12000,13 +12191,13 @@ static void Main() // (12,8): error CS0534: 'C' does not implement inherited abstract member 'A.Y.init' // record C(object X, object Y) : B; Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "C").WithArguments("C", "A.Y.init").WithLocation(12, 8), - // (12,17): error CS8866: Record member 'B.X' must be a readable instance property of type 'object' to match positional parameter 'X'. + // (12,17): error CS8866: Record member 'B.X' must be a readable instance property or field of type 'object' to match positional parameter 'X'. // record C(object X, object Y) : B; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("B.X", "object", "X").WithLocation(12, 17), // (12,17): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? // record C(object X, object Y) : B; Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(12, 17), - // (12,27): error CS8866: Record member 'B.Y' must be a readable instance property of type 'object' to match positional parameter 'Y'. + // (12,27): error CS8866: Record member 'B.Y' must be a readable instance property or field of type 'object' to match positional parameter 'Y'. // record C(object X, object Y) : B; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Y").WithArguments("B.Y", "object", "Y").WithLocation(12, 27), // (12,27): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? @@ -12114,7 +12305,7 @@ record CB(object P) : B; // (2,8): error CS0534: 'CB' does not implement inherited abstract member 'A.P.init' // record CB(object P) : B; Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "CB").WithArguments("CB", "A.P.init").WithLocation(2, 8), - // (2,18): error CS8866: Record member 'B.P' must be a readable instance property of type 'object' to match positional parameter 'P'. + // (2,18): error CS8866: Record member 'B.P' must be a readable instance property or field of type 'object' to match positional parameter 'P'. // record CB(object P) : B; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("B.P", "object", "P").WithLocation(2, 18), // (2,18): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? @@ -13270,6 +13461,9 @@ protected C(C c) : base(c) { } // (2,8): error CS0115: 'C.Equals(object?)': no suitable method found to override // record C(int j) : B(3, 4); Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C").WithArguments("C.Equals(object?)").WithLocation(2, 8), + // (2,8): error CS0115: 'C.PrintMembers(StringBuilder)': no suitable method found to override + // record C(int j) : B(3, 4); + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C").WithArguments("C.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 8), // (2,19): error CS0122: 'B' is inaccessible due to its protection level // record C(int j) : B(3, 4); Diagnostic(ErrorCode.ERR_BadAccess, "B").WithArguments("B").WithLocation(2, 19), @@ -14242,7 +14436,7 @@ static void Main() "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (7,14): error CS8866: Record member 'B.X' must be a readable instance property of type 'int' to match positional parameter 'X'. + // (7,14): error CS8866: Record member 'B.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. // record C(int X, int Y) : B Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("B.X", "int", "X").WithLocation(7, 14), // (7,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? @@ -14360,14 +14554,28 @@ static void Main() } } "; - var comp = CreateCompilation(source); - comp.VerifyDiagnostics( - // (6,9): error CS0102: The type 'C' already contains a definition for 'X' + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (4,10): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record C(int X) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "int X").WithArguments("positional fields in records").WithLocation(4, 10), + // (4,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record C(int X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(4, 14), + // (6,9): warning CS0169: The field 'C.X' is never used // int X; - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "X").WithArguments("C", "X").WithLocation(6, 9), + Diagnostic(ErrorCode.WRN_UnreferencedField, "X").WithArguments("C.X").WithLocation(6, 9) + ); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (4,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record C(int X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(4, 14), // (6,9): warning CS0169: The field 'C.X' is never used // int X; - Diagnostic(ErrorCode.WRN_UnreferencedField, "X").WithArguments("C.X").WithLocation(6, 9)); + Diagnostic(ErrorCode.WRN_UnreferencedField, "X").WithArguments("C.X").WithLocation(6, 9) + ); Assert.Equal( "void C.Deconstruct(out System.Int32 X)", @@ -14446,7 +14654,7 @@ static void Main() "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (9,14): error CS8866: Record member 'B.X' must be a readable instance property of type 'int' to match positional parameter 'X'. + // (9,14): error CS8866: Record member 'B.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. // record C(int X) : B Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("B.X", "int", "X").WithLocation(9, 14), // (9,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? @@ -14795,13 +15003,13 @@ static void Main() "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (4,14): error CS8866: Record member 'C.X' must be a readable instance property of type 'int' to match positional parameter 'X'. + // (4,14): error CS8866: Record member 'C.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. // record C(int X, int Y) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("C.X", "int", "X").WithLocation(4, 14), // (4,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? // record C(int X, int Y) Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(4, 14), - // (4,21): error CS8866: Record member 'C.Y' must be a readable instance property of type 'int' to match positional parameter 'Y'. + // (4,21): error CS8866: Record member 'C.Y' must be a readable instance property or field of type 'int' to match positional parameter 'Y'. // record C(int X, int Y) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Y").WithArguments("C.Y", "int", "Y").WithLocation(4, 21), // (4,21): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? @@ -14890,13 +15098,13 @@ static void Main() "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (7,18): error CS8866: Record member 'C.X' must be a readable instance property of type 'Derived' to match positional parameter 'X'. + // (7,18): error CS8866: Record member 'C.X' must be a readable instance property or field of type 'Derived' to match positional parameter 'X'. // record C(Derived X, Base Y) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("C.X", "Derived", "X").WithLocation(7, 18), // (7,18): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? // record C(Derived X, Base Y) Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(7, 18), - // (7,26): error CS8866: Record member 'C.Y' must be a readable instance property of type 'Base' to match positional parameter 'Y'. + // (7,26): error CS8866: Record member 'C.Y' must be a readable instance property or field of type 'Base' to match positional parameter 'Y'. // record C(Derived X, Base Y) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Y").WithArguments("C.Y", "Base", "Y").WithLocation(7, 26), // (7,26): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? @@ -15457,7 +15665,7 @@ record A(int X) "; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (2,14): error CS8866: Record member 'A.X' must be a readable instance property of type 'int' to match positional parameter 'X'. + // (2,14): error CS8866: Record member 'A.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. // record A(int X) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("A.X", "int", "X").WithLocation(2, 14), // (2,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? @@ -15480,7 +15688,7 @@ record B(int X) : A; "; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (7,14): error CS8866: Record member 'A.X' must be a readable instance property of type 'int' to match positional parameter 'X'. + // (7,14): error CS8866: Record member 'A.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. // record B(int X) : A; Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("A.X", "int", "X").WithLocation(7, 14), // (7,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? @@ -15592,7 +15800,7 @@ public static void Main() "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (4,21): error CS8866: Record member 'B.Y' must be a readable instance property of type 'int' to match positional parameter 'Y'. + // (4,21): error CS8866: Record member 'B.Y' must be a readable instance property or field of type 'int' to match positional parameter 'Y'. // record B(int X, int Y) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Y").WithArguments("B.Y", "int", "Y").WithLocation(4, 21), // (4,21): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? @@ -20618,7 +20826,7 @@ public void DuplicateProperty_02() // (1,17): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? // record C(object P, object Q) Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P").WithArguments("P").WithLocation(1, 17), - // (1,27): error CS8866: Record member 'C.Q' must be a readable instance property of type 'object' to match positional parameter 'Q'. + // (1,27): error CS8866: Record member 'C.Q' must be a readable instance property or field of type 'object' to match positional parameter 'Q'. // record C(object P, object Q) Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "Q").WithArguments("C.Q", "object", "Q").WithLocation(1, 27), // (1,27): warning CS8907: Parameter 'Q' is unread. Did you forget to use it to initialize the property with that name? @@ -21226,7 +21434,7 @@ record C : Base(X, Y) comp.VerifyEmitDiagnostics( // (13,16): error CS8861: Unexpected argument list. // record C : Base(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(13, 16) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X, Y)").WithLocation(13, 16) ); var tree = comp.SyntaxTrees.First(); @@ -21272,7 +21480,7 @@ partial record C : Base(X, Y) comp.VerifyEmitDiagnostics( // (17,24): error CS8861: Unexpected argument list. // partial record C : Base(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(17, 24) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X, Y)").WithLocation(17, 24) ); var tree = comp.SyntaxTrees.First(); @@ -21325,10 +21533,10 @@ partial record C : Base(X, Y) comp.VerifyEmitDiagnostics( // (13,24): error CS8861: Unexpected argument list. // partial record C : Base(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(13, 24), + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X, Y)").WithLocation(13, 24), // (17,24): error CS8861: Unexpected argument list. // partial record C : Base(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(17, 24) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X, Y)").WithLocation(17, 24) ); var tree = comp.SyntaxTrees.First(); @@ -21387,7 +21595,7 @@ partial record C : Base(X, Y) comp.VerifyEmitDiagnostics( // (17,24): error CS8861: Unexpected argument list. // partial record C : Base(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(17, 24) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X, Y)").WithLocation(17, 24) ); var tree = comp.SyntaxTrees.First(); @@ -21476,7 +21684,7 @@ partial record C(int X, int Y) : Base(X, Y) comp.VerifyEmitDiagnostics( // (13,24): error CS8861: Unexpected argument list. // partial record C : Base(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(13, 24) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X, Y)").WithLocation(13, 24) ); var tree = comp.SyntaxTrees.First(); @@ -21675,7 +21883,7 @@ class C : Base(X) Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "C").WithArguments("X", "Base.Base(int)").WithLocation(11, 7), // (11,15): error CS8861: Unexpected argument list. // class C : Base(X) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(11, 15) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X)").WithLocation(11, 15) ); var tree = comp.SyntaxTrees.First(); @@ -21712,7 +21920,7 @@ struct C : Base(X) comp.VerifyEmitDiagnostics( // (8,16): error CS8861: Unexpected argument list. // struct C : Base(X) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(8, 16) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X)").WithLocation(8, 16) ); var tree = comp.SyntaxTrees.First(); @@ -21749,7 +21957,7 @@ interface C : Base(X) comp.VerifyEmitDiagnostics( // (8,19): error CS8861: Unexpected argument list. // interface C : Base(X) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(8, 19) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X)").WithLocation(8, 19) ); var tree = comp.SyntaxTrees.First(); @@ -22152,7 +22360,7 @@ interface I {} comp.VerifyDiagnostics( // (11,15): error CS8861: Unexpected argument list. // class C : Base(GetInt(X, out var xx) + xx, Y), I - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(11, 15), + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(GetInt(X, out var xx) + xx, Y)").WithLocation(11, 15), // (13,30): error CS1729: 'Base' does not contain a constructor that takes 4 arguments // C(int X, int Y, int Z) : base(X, Y, Z, 1) { return; } Diagnostic(ErrorCode.ERR_BadCtorArgCount, "base").WithArguments("Base", "4").WithLocation(13, 30) @@ -22260,62 +22468,6 @@ interface I {} } } - [Fact(Skip = "record struct")] - public void Equality_01() - { - var source = -@"using static System.Console; -data struct S; -class Program -{ - static void Main() - { - var x = new S(); - var y = new S(); - WriteLine(x.Equals(y)); - WriteLine(((object)x).Equals(y)); - } -}"; - var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9, options: TestOptions.ReleaseExe); - var verifier = CompileAndVerify(comp, expectedOutput: -@"True -True").VerifyDiagnostics(); - - verifier.VerifyIL("S.Equals(in S)", -@"{ - // Code size 23 (0x17) - .maxstack 2 - .locals init (S V_0) - IL_0000: ldarg.0 - IL_0001: call ""System.Type S.EqualityContract.get"" - IL_0006: ldarg.1 - IL_0007: ldobj ""S"" - IL_000c: stloc.0 - IL_000d: ldloca.s V_0 - IL_000f: call ""System.Type S.EqualityContract.get"" - IL_0014: ceq - IL_0016: ret -}"); - verifier.VerifyIL("S.Equals(object)", -@"{ - // Code size 26 (0x1a) - .maxstack 2 - .locals init (S V_0) - IL_0000: ldarg.1 - IL_0001: isinst ""S"" - IL_0006: brtrue.s IL_000a - IL_0008: ldc.i4.0 - IL_0009: ret - IL_000a: ldarg.0 - IL_000b: ldarg.1 - IL_000c: unbox.any ""S"" - IL_0011: stloc.0 - IL_0012: ldloca.s V_0 - IL_0014: call ""bool S.Equals(in S)"" - IL_0019: ret -}"); - } - [Fact] public void Equality_02() { @@ -24163,15 +24315,18 @@ record B : A, System.IEquatable; // (1,8): error CS0115: 'A.ToString()': no suitable method found to override // record A : System.IEquatable>; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.ToString()").WithLocation(1, 8), - // (1,8): error CS0115: 'A.GetHashCode()': no suitable method found to override - // record A : System.IEquatable>; - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.GetHashCode()").WithLocation(1, 8), // (1,8): error CS0115: 'A.EqualityContract': no suitable method found to override // record A : System.IEquatable>; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.EqualityContract").WithLocation(1, 8), // (1,8): error CS0115: 'A.Equals(object?)': no suitable method found to override // record A : System.IEquatable>; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.Equals(object?)").WithLocation(1, 8), + // (1,8): error CS0115: 'A.GetHashCode()': no suitable method found to override + // record A : System.IEquatable>; + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.GetHashCode()").WithLocation(1, 8), + // (1,8): error CS0115: 'A.PrintMembers(StringBuilder)': no suitable method found to override + // record A : System.IEquatable>; + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.PrintMembers(System.Text.StringBuilder)").WithLocation(1, 8), // (1,22): error CS0234: The type or namespace name 'IEquatable<>' does not exist in the namespace 'System' (are you missing an assembly reference?) // record A : System.IEquatable>; Diagnostic(ErrorCode.ERR_DottedTypeNameNotFoundInNS, "IEquatable>").WithArguments("IEquatable<>", "System").WithLocation(1, 22), @@ -24249,15 +24404,18 @@ record B : A, IEquatable; // (2,8): error CS0115: 'A.ToString()': no suitable method found to override // record A : IEquatable>; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.ToString()").WithLocation(2, 8), - // (2,8): error CS0115: 'A.GetHashCode()': no suitable method found to override - // record A : IEquatable>; - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.GetHashCode()").WithLocation(2, 8), // (2,8): error CS0115: 'A.EqualityContract': no suitable method found to override // record A : IEquatable>; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.EqualityContract").WithLocation(2, 8), // (2,8): error CS0115: 'A.Equals(object?)': no suitable method found to override // record A : IEquatable>; Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.Equals(object?)").WithLocation(2, 8), + // (2,8): error CS0115: 'A.GetHashCode()': no suitable method found to override + // record A : IEquatable>; + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.GetHashCode()").WithLocation(2, 8), + // (2,8): error CS0115: 'A.PrintMembers(StringBuilder)': no suitable method found to override + // record A : IEquatable>; + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 8), // (2,15): error CS0246: The type or namespace name 'IEquatable<>' could not be found (are you missing a using directive or an assembly reference?) // record A : IEquatable>; Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "IEquatable>").WithArguments("IEquatable<>").WithLocation(2, 15), @@ -24929,19 +25087,7 @@ public record Test( IEnumerable getAttributeStrings(Symbol symbol) { - return GetAttributeStrings(symbol.GetAttributes().Where(a => - { - switch (a.AttributeClass!.Name) - { - case "A": - case "B": - case "C": - case "D": - return true; - } - - return false; - })); + return GetAttributeStrings(symbol.GetAttributes().Where(a => a.AttributeClass!.Name is "A" or "B" or "C" or "D")); } } @@ -25563,15 +25709,18 @@ record B : A> { // (2,8): error CS0115: 'A.ToString()': no suitable method found to override // record A : B> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.ToString()").WithLocation(2, 8), - // (2,8): error CS0115: 'A.GetHashCode()': no suitable method found to override - // record A : B> { } - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.GetHashCode()").WithLocation(2, 8), // (2,8): error CS0115: 'A.EqualityContract': no suitable method found to override // record A : B> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.EqualityContract").WithLocation(2, 8), // (2,8): error CS0115: 'A.Equals(object?)': no suitable method found to override // record A : B> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.Equals(object?)").WithLocation(2, 8), + // (2,8): error CS0115: 'A.GetHashCode()': no suitable method found to override + // record A : B> { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.GetHashCode()").WithLocation(2, 8), + // (2,8): error CS0115: 'A.PrintMembers(StringBuilder)': no suitable method found to override + // record A : B> { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "A").WithArguments("A.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 8), // (3,8): error CS0115: 'B.EqualityContract': no suitable method found to override // record B : A> { Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.EqualityContract").WithLocation(3, 8), @@ -25581,6 +25730,9 @@ record B : A> { // (3,8): error CS0115: 'B.GetHashCode()': no suitable method found to override // record B : A> { Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.GetHashCode()").WithLocation(3, 8), + // (3,8): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override + // record B : A> { + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(3, 8), // (3,8): error CS0115: 'B.ToString()': no suitable method found to override // record B : A> { Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.ToString()").WithLocation(3, 8) @@ -25633,9 +25785,6 @@ public partial record C3 : Base<(int a, int b)> { } // (3,23): error CS0263: Partial declarations of 'C1' must not specify different base classes // public partial record C1 : Base<(int a, int b)> { } Diagnostic(ErrorCode.ERR_PartialMultipleBases, "C1").WithArguments("C1").WithLocation(3, 23), - // (5,23): error CS0115: 'C2.GetHashCode()': no suitable method found to override - // public partial record C2 : Base<(int a, int b)> { } - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.GetHashCode()").WithLocation(5, 23), // (5,23): error CS0115: 'C2.ToString()': no suitable method found to override // public partial record C2 : Base<(int a, int b)> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.ToString()").WithLocation(5, 23), @@ -25645,18 +25794,27 @@ public partial record C3 : Base<(int a, int b)> { } // (5,23): error CS0115: 'C2.Equals(object?)': no suitable method found to override // public partial record C2 : Base<(int a, int b)> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.Equals(object?)").WithLocation(5, 23), + // (5,23): error CS0115: 'C2.GetHashCode()': no suitable method found to override + // public partial record C2 : Base<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.GetHashCode()").WithLocation(5, 23), + // (5,23): error CS0115: 'C2.PrintMembers(StringBuilder)': no suitable method found to override + // public partial record C2 : Base<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C2").WithArguments("C2.PrintMembers(System.Text.StringBuilder)").WithLocation(5, 23), // (3,23): error CS0115: 'C1.ToString()': no suitable method found to override // public partial record C1 : Base<(int a, int b)> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.ToString()").WithLocation(3, 23), - // (3,23): error CS0115: 'C1.GetHashCode()': no suitable method found to override - // public partial record C1 : Base<(int a, int b)> { } - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.GetHashCode()").WithLocation(3, 23), // (3,23): error CS0115: 'C1.EqualityContract': no suitable method found to override // public partial record C1 : Base<(int a, int b)> { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.EqualityContract").WithLocation(3, 23), // (3,23): error CS0115: 'C1.Equals(object?)': no suitable method found to override // public partial record C1 : Base<(int a, int b)> { } - Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.Equals(object?)").WithLocation(3, 23) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.Equals(object?)").WithLocation(3, 23), + // (3,23): error CS0115: 'C1.GetHashCode()': no suitable method found to override + // public partial record C1 : Base<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.GetHashCode()").WithLocation(3, 23), + // (3,23): error CS0115: 'C1.PrintMembers(StringBuilder)': no suitable method found to override + // public partial record C1 : Base<(int a, int b)> { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "C1").WithArguments("C1.PrintMembers(System.Text.StringBuilder)").WithLocation(3, 23) ); } @@ -27880,15 +28038,56 @@ public static class IsExternalInit } [Fact] - [WorkItem(44571, "https://github.com/dotnet/roslyn/issues/44571")] - public void XmlDoc_Cref() + public void XmlDoc_RecordClass() { var src = @" /// Summary -/// Description for -public record C(int I1) -{ - /// Summary +/// Description for I1 +public record class C(int I1); + +namespace System.Runtime.CompilerServices +{ + /// Ignored + public static class IsExternalInit + { + } +} +"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithDocumentationComments.WithLanguageVersion(LanguageVersion.Preview)); + comp.VerifyDiagnostics(); + + var cMember = comp.GetMember("C"); + Assert.Equal( +@" + Summary + Description for I1 + +", cMember.GetDocumentationCommentXml()); + var constructor = cMember.GetMembers(".ctor").OfType().Single(); + Assert.Equal( +@" + Summary + Description for I1 + +", constructor.GetDocumentationCommentXml()); + + Assert.Equal("", constructor.GetParameters()[0].GetDocumentationCommentXml()); + + var property = cMember.GetMembers("I1").Single(); + Assert.Equal("", property.GetDocumentationCommentXml()); + } + + [Fact] + [WorkItem(44571, "https://github.com/dotnet/roslyn/issues/44571")] + public void XmlDoc_Cref() + { + var src = @" +/// Summary +/// Description for +public record C(int I1) +{ + /// Summary /// Description for public void M(int x) { } } @@ -28680,5 +28879,1044 @@ public void SealedIncomplete() Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(2, 22) ); } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } // hiding +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } // hiding + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(9, 17) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Field() + { + var source = @" +public record Base +{ + public int I = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } // hiding +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } // hiding + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(9, 17) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Field_HiddenWithConstant() + { + var source = @" +public record Base +{ + public int I = 0; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public const int I = 0; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8866: Record member 'C.I' must be a readable instance property or field of type 'int' to match positional parameter 'I'. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "I").WithArguments("C.I", "int", "I").WithLocation(7, 21), + // (9,22): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public const int I = 0; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 22) + ); + } + + [Fact] + public void FieldAsPositionalMember() + { + var source = @" +var a = new A(42); +System.Console.Write(a.X); +System.Console.Write("" - ""); +a.Deconstruct(out int x); +System.Console.Write(x); + +record A(int X) +{ + public int X = X; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (8,10): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record A(int X) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "int X").WithArguments("positional fields in records").WithLocation(8, 10) + ); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42 - 42"); + verifier.VerifyIL("A.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: ldfld ""int A.X"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_TwoParameters() + { + var source = @" +var a = new A(42, 43); +System.Console.Write(a.Y); +System.Console.Write("" - ""); +a.Deconstruct(out int x, out int y); +System.Console.Write(y); + +record A(int X, int Y) +{ + public int X = X; + public int Y = Y; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "43 - 43"); + verifier.VerifyIL("A.Deconstruct", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: ldfld ""int A.X"" + IL_0007: stind.i4 + IL_0008: ldarg.2 + IL_0009: ldarg.0 + IL_000a: ldfld ""int A.Y"" + IL_000f: stind.i4 + IL_0010: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_Readonly() + { + var source = @" +var a = new A(42); +System.Console.Write(a.X); +System.Console.Write("" - ""); +a.Deconstruct(out int x); +System.Console.Write(x); + +record A(int X) +{ + public readonly int X = X; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42 - 42", verify: Verification.Skipped /* init-only */); + verifier.VerifyIL("A.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: ldfld ""int A.X"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_UnusedParameter() + { + var source = @" +record A(int X) +{ + public int X; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (2,14): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record A(int X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(2, 14), + // (4,16): warning CS0649: Field 'A.X' is never assigned to, and will always have its default value 0 + // public int X; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "X").WithArguments("A.X", "0").WithLocation(4, 16) + ); + } + + [Fact] + public void FieldAsPositionalMember_CurrentTypeComesFirst() + { + var source = @" +var c = new C(42); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I { get; set; } = 0; +} +public record C(int I) : Base +{ + public int I = I; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (12,16): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public int I = I; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(12, 16) + ); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: ldfld ""int C.I"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_CurrentTypeComesFirst_FieldInBase() + { + var source = @" +var c = new C(42); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I = 0; +} +public record C(int I) : Base +{ + public int I { get; set; } = I; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (12,16): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public int I { get; set; } = I; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(12, 16) + ); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""int C.I.get"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_FieldFromBase() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: ldfld ""int Base.I"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact] + public void FieldAsPositionalMember_FieldFromBase_StaticFieldInDerivedType() + { + var source = @" +public record Base +{ + public int I = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public static int I = 42; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (7,21): error CS8866: Record member 'C.I' must be a readable instance property or field of type 'int' to match positional parameter 'I'. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "I").WithArguments("C.I", "int", "I").WithLocation(7, 21), + // (9,23): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public static int I = 42; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 23) + ); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (7,21): error CS8866: Record member 'C.I' must be a readable instance property or field of type 'int' to match positional parameter 'I'. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "I").WithArguments("C.I", "int", "I").WithLocation(7, 21), + // (9,23): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public static int I = 42; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 23) + ); + + source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public static int I { get; set; } = 42; +} +"; + comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,21): error CS8866: Record member 'C.I' must be a readable instance property or field of type 'int' to match positional parameter 'I'. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "I").WithArguments("C.I", "int", "I").WithLocation(7, 21), + // (9,23): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public static int I { get; set; } = 42; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 23) + ); + } + + [Fact] + public void FieldAsPositionalMember_Static() + { + var source = @" +record A(int X) +{ + public static int X = 0; + public int Y = X; +} +"; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,14): error CS8866: Record member 'A.X' must be a readable instance property or field of type 'int' to match positional parameter 'X'. + // record A(int X) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "X").WithArguments("A.X", "int", "X").WithLocation(2, 14) + ); + } + + [Fact] + public void FieldAsPositionalMember_Const() + { + var src = @" +record C(int P) +{ + const int P = 4; +} +record C2(int P) +{ + const int P = P; +} +"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (2,14): error CS8866: Record member 'C.P' must be a readable instance property or field of type 'int' to match positional parameter 'P'. + // record C(int P) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("C.P", "int", "P").WithLocation(2, 14), + // (2,14): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? + // record C(int P) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P").WithArguments("P").WithLocation(2, 14), + // (6,15): error CS8866: Record member 'C2.P' must be a readable instance property or field of type 'int' to match positional parameter 'P'. + // record C2(int P) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("C2.P", "int", "P").WithLocation(6, 15), + // (6,15): warning CS8907: Parameter 'P' is unread. Did you forget to use it to initialize the property with that name? + // record C2(int P) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P").WithArguments("P").WithLocation(6, 15), + // (8,15): error CS0110: The evaluation of the constant value for 'C2.P' involves a circular definition + // const int P = P; + Diagnostic(ErrorCode.ERR_CircConstValue, "P").WithArguments("C2.P").WithLocation(8, 15) + ); + } + + [Fact] + public void FieldAsPositionalMember_Volatile() + { + var src = @" +record C(int P) +{ + public volatile int P = P; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void FieldAsPositionalMember_DifferentAccessibility() + { + var src = @" +record C(int P) +{ + private int P = P; +} +record C2(int P) +{ + protected int P = P; +} +record C3(int P) +{ + internal int P = P; +} +"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void FieldAsPositionalMember_WrongType() + { + var src = @" +record C(int P) +{ + public string P = null; + public int Q = P; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (2,14): error CS8866: Record member 'C.P' must be a readable instance property or field of type 'int' to match positional parameter 'P'. + // record C(int P) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "P").WithArguments("C.P", "int", "P").WithLocation(2, 14) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithZeroArityMethod() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } +} +"; + var expected = new[] + { + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(9, 17) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics(expected); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithZeroArityMethod_DeconstructInSource() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } + public void Deconstruct(out int i) { i = 0; } +} +"; + var expected = new[] + { + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(9, 17) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics(expected); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithZeroArityMethod_WithNew() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) // 1 +{ + public new void I() { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) // 1 + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithGenericMethod() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (11,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(11, 21), + // (13,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(13, 17) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_FromGrandBase() + { + var source = @" +public record GrandBase +{ + public int I { get; set; } = 42; +} +public record Base : GrandBase +{ + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } +} +"; + var expected = new[] + { + // (10,21): error CS8913: The positional member 'GrandBase.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("GrandBase.I").WithLocation(10, 21), + // (12,17): warning CS0108: 'C.I()' hides inherited member 'GrandBase.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "GrandBase.I").WithLocation(12, 17) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_NotHiddenByIndexer() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int Item { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int Item) : Base(Item) +{ + public int this[int x] { get => throw null; } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""int Base.Item.get"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_NotHiddenByIndexer_WithIndexerName() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + [System.Runtime.CompilerServices.IndexerName(""I"")] + public int this[int x] { get => throw null; } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""int Base.I.get"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithType() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public class I { } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,18): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public class I { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 18) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithEvent() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public event System.Action I; +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,32): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public event System.Action I; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 32), + // (9,32): warning CS0067: The event 'C.I' is never used + // public event System.Action I; + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "I").WithArguments("C.I").WithLocation(9, 32) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithConstant() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public const string I = null; +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8866: Record member 'C.I' must be a readable instance property or field of type 'int' to match positional parameter 'I'. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_BadRecordMemberForPositionalParameter, "I").WithArguments("C.I", "int", "I").WithLocation(7, 21), + // (9,25): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public const string I = null; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 25) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_AbstractInBase() + { + var source = @" +abstract record Base +{ + public abstract int I { get; init; } +} +record Derived(int I) : Base +{ + public int I() { return 0; } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (8,16): warning CS0108: 'Derived.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public int I() { return 0; } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("Derived.I()", "Base.I").WithLocation(8, 16), + // (8,16): error CS0102: The type 'Derived' already contains a definition for 'I' + // public int I() { return 0; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "I").WithArguments("Derived", "I").WithLocation(8, 16) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_AbstractInBase_AbstractInDerived() + { + var source = @" +abstract record Base +{ + public abstract int I { get; init; } +} +abstract record Derived(int I) : Base +{ + public int I() { return 0; } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (8,16): error CS0533: 'Derived.I()' hides inherited abstract member 'Base.I' + // public int I() { return 0; } + Diagnostic(ErrorCode.ERR_HidingAbstractMethod, "I").WithArguments("Derived.I()", "Base.I").WithLocation(8, 16), + // (8,16): error CS0102: The type 'Derived' already contains a definition for 'I' + // public int I() { return 0; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "I").WithArguments("Derived", "I").WithLocation(8, 16) + ); + } + + [Fact] + public void InterfaceWithParameters() + { + var src = @" +public interface I +{ +} + +record R(int X) : I() +{ +} + +record R2(int X) : I(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,20): error CS8861: Unexpected argument list. + // record R(int X) : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 20), + // (10,21): error CS8861: Unexpected argument list. + // record R2(int X) : I(X) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X)").WithLocation(10, 21), + // (10,21): error CS1729: 'object' does not contain a constructor that takes 1 arguments + // record R2(int X) : I(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("object", "1").WithLocation(10, 21) + ); + } + + [Fact] + public void InterfaceWithParameters_RecordClass() + { + var src = @" +public interface I +{ +} + +record class R(int X) : I() +{ +} + +record class R2(int X) : I(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,26): error CS8861: Unexpected argument list. + // record class R(int X) : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 26), + // (10,27): error CS8861: Unexpected argument list. + // record class R2(int X) : I(X) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(X)").WithLocation(10, 27), + // (10,27): error CS1729: 'object' does not contain a constructor that takes 1 arguments + // record class R2(int X) : I(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("object", "1").WithLocation(10, 27) + ); + } + + [Fact] + public void BaseErrorTypeWithParameters() + { + var src = @" +record R2(int X) : Error(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,8): error CS0115: 'R2.ToString()': no suitable method found to override + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R2").WithArguments("R2.ToString()").WithLocation(2, 8), + // (2,8): error CS0115: 'R2.EqualityContract': no suitable method found to override + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R2").WithArguments("R2.EqualityContract").WithLocation(2, 8), + // (2,8): error CS0115: 'R2.Equals(object?)': no suitable method found to override + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R2").WithArguments("R2.Equals(object?)").WithLocation(2, 8), + // (2,8): error CS0115: 'R2.GetHashCode()': no suitable method found to override + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R2").WithArguments("R2.GetHashCode()").WithLocation(2, 8), + // (2,8): error CS0115: 'R2.PrintMembers(StringBuilder)': no suitable method found to override + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "R2").WithArguments("R2.PrintMembers(System.Text.StringBuilder)").WithLocation(2, 8), + // (2,20): error CS0246: The type or namespace name 'Error' could not be found (are you missing a using directive or an assembly reference?) + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error").WithArguments("Error").WithLocation(2, 20), + // (2,25): error CS1729: 'Error' does not contain a constructor that takes 1 arguments + // record R2(int X) : Error(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("Error", "1").WithLocation(2, 25) + ); + } + + [Fact] + public void BaseDynamicTypeWithParameters() + { + var src = @" +record R(int X) : dynamic(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,19): error CS1965: 'R': cannot derive from the dynamic type + // record R(int X) : dynamic(X) + Diagnostic(ErrorCode.ERR_DeriveFromDynamic, "dynamic").WithArguments("R").WithLocation(2, 19), + // (2,26): error CS1729: 'object' does not contain a constructor that takes 1 arguments + // record R(int X) : dynamic(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("object", "1").WithLocation(2, 26) + ); + } + + [Fact] + public void BaseTypeParameterTypeWithParameters() + { + var src = @" +class C +{ + record R(int X) : T(X) + { + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (4,23): error CS0689: Cannot derive from 'T' because it is a type parameter + // record R(int X) : T(X) + Diagnostic(ErrorCode.ERR_DerivingFromATyVar, "T").WithArguments("T").WithLocation(4, 23), + // (4,24): error CS1729: 'object' does not contain a constructor that takes 1 arguments + // record R(int X) : T(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("object", "1").WithLocation(4, 24) + ); + } + + [Fact] + public void BaseObjectTypeWithParameters() + { + var src = @" +record R(int X) : object(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,25): error CS1729: 'object' does not contain a constructor that takes 1 arguments + // record R(int X) : object(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("object", "1").WithLocation(2, 25) + ); + } + + [Fact] + public void BaseValueTypeTypeWithParameters() + { + var src = @" +record R(int X) : System.ValueType(X) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,19): error CS0644: 'R' cannot derive from special class 'ValueType' + // record R(int X) : System.ValueType(X) + Diagnostic(ErrorCode.ERR_DeriveFromEnumOrValueType, "System.ValueType").WithArguments("R", "System.ValueType").WithLocation(2, 19), + // (2,35): error CS1729: 'object' does not contain a constructor that takes 1 arguments + // record R(int X) : System.ValueType(X) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "(X)").WithArguments("object", "1").WithLocation(2, 35) + ); + } + + [Fact] + public void InterfaceWithParameters_NoPrimaryConstructor() + { + var src = @" +public interface I +{ +} + +record R : I() +{ +} + +record R2 : I(0) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,13): error CS8861: Unexpected argument list. + // record R : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 13), + // (10,14): error CS8861: Unexpected argument list. + // record R2 : I(0) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(0)").WithLocation(10, 14) + ); + } + + [Fact] + public void InterfaceWithParameters_Class() + { + var src = @" +public interface I +{ +} + +class C : I() +{ +} + +class C2 : I(0) +{ +} +"; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,12): error CS8861: Unexpected argument list. + // class C : I() + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "()").WithLocation(6, 12), + // (10,13): error CS8861: Unexpected argument list. + // class C2 : I(0) + Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(0)").WithLocation(10, 13) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 32cfbabb4e215..8cee590a8de6a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -3498,10 +3498,10 @@ void M(S t, ref S t1) // (8,26): error CS0306: The type 'S' may not be used as a type argument // async Task M(Task t) Diagnostic(ErrorCode.ERR_BadTypeArgument, "t").WithArguments("S").WithLocation(8, 26), - // (12,9): error CS4012: Parameters or locals of type 'S' cannot be declared in async methods or lambda expressions. + // (12,9): error CS4012: Parameters or locals of type 'S' cannot be declared in async methods or async lambda expressions. // var a = await t; Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("S").WithLocation(12, 9), - // (14,9): error CS4012: Parameters or locals of type 'S' cannot be declared in async methods or lambda expressions. + // (14,9): error CS4012: Parameters or locals of type 'S' cannot be declared in async methods or async lambda expressions. // var r = t.Result; Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("S").WithLocation(14, 9), // (15,9): error CS8350: This combination of arguments to 'C.M(S, ref S)' is disallowed because it may expose variables referenced by parameter 't' outside of their declaration scope diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 41775701e1579..42cd3670adbfe 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -9244,6 +9244,49 @@ public static void Main() Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "z").WithArguments("decimal", "int").WithLocation(13, 30)); } + [Fact] + public void CS0266ERR_NoImplicitConvCast14() + { + string source = @" +class C +{ + public unsafe void M(int* p, object o) + { + _ = p[o]; // error with span on 'o' + _ = p[0]; // ok + } +} +"; + CreateCompilation(source, options: TestOptions.UnsafeDebugDll).VerifyDiagnostics( + // (6,15): error CS0266: Cannot implicitly convert type 'object' to 'int'. An explicit conversion exists (are you missing a cast?) + // _ = p[o]; // error with span on 'o' + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "o").WithArguments("object", "int").WithLocation(6, 15)); + } + + [Fact] + public void CS0266ERR_NoImplicitConvCast15() + { + string source = @" +class C +{ + public void M(object o) + { + int[o] x; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (6,12): error CS0270: Array size cannot be specified in a variable declaration (try initializing with a 'new' expression) + // int[o]; + Diagnostic(ErrorCode.ERR_ArraySizeInDeclaration, "[o]").WithLocation(6, 12), + // (6,13): error CS0266: Cannot implicitly convert type 'object' to 'int'. An explicit conversion exists (are you missing a cast?) + // int[o]; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "o").WithArguments("object", "int").WithLocation(6, 13), + // (6,16): warning CS0168: The variable 'x' is declared but never used + // int[o] x; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(6, 16)); + } + [Fact] public void CS0269ERR_UseDefViolationOut() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs index 729fc3aa3bb76..13a2578f3f772 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs @@ -958,7 +958,7 @@ public static async Task M1(Span arg) CSharpCompilation comp = CreateCompilationWithMscorlibAndSpan(text); comp.VerifyDiagnostics( - // (11,48): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or lambda expressions. + // (11,48): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or async lambda expressions. // public static async Task M1(Span arg) Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "arg").WithArguments("System.Span").WithLocation(11, 48) ); @@ -990,7 +990,7 @@ public static async Task M1() CSharpCompilation comp = CreateCompilationWithMscorlibAndSpan(text); comp.VerifyDiagnostics( - // (13,9): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or lambda expressions. + // (13,9): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or async lambda expressions. // Span local = default(Span); Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "Span").WithArguments("System.Span").WithLocation(13, 9) ); @@ -998,7 +998,7 @@ public static async Task M1() comp = CreateCompilationWithMscorlibAndSpan(text, TestOptions.DebugExe); comp.VerifyDiagnostics( - // (13,9): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or lambda expressions. + // (13,9): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or async lambda expressions. // Span local = default(Span); Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "Span").WithArguments("System.Span").WithLocation(13, 9) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs index b12a2501c8b7e..b2848eeecb968 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs @@ -745,7 +745,7 @@ async void M() // (8,38): error CS0150: A constant value is expected // Span p = stackalloc int[await Task.FromResult(1)] { await Task.FromResult(2) }; Diagnostic(ErrorCode.ERR_ConstantExpected, "await Task.FromResult(1)").WithLocation(8, 38), - // (8,9): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or lambda expressions. + // (8,9): error CS4012: Parameters or locals of type 'Span' cannot be declared in async methods or async lambda expressions. // Span p = stackalloc int[await Task.FromResult(1)] { await Task.FromResult(2) }; Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "Span").WithArguments("System.Span").WithLocation(8, 9) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs index 9892ba7b9acd0..1c6e6423ed72b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs @@ -1130,7 +1130,7 @@ static async Task Main() } }"; var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }).VerifyDiagnostics( - // (16,22): error CS4012: Parameters or locals of type 'S1' cannot be declared in async methods or lambda expressions. + // (16,22): error CS4012: Parameters or locals of type 'S1' cannot be declared in async methods or async lambda expressions. // await using (S1 c = new S1()) Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "S1").WithArguments("S1").WithLocation(16, 22) ); diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs index 7bc0335b15d1d..49a62ab5fc23f 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs @@ -1358,5 +1358,138 @@ class C { } Assert.True(tree.TryGetRoot(out var rootFromTryGetRoot)); Assert.Same(rootFromGetRoot, rootFromTryGetRoot); } + + [Fact] + public void Diagnostics_Respect_Suppression() + { + var source = @" +class C { } +"; + var parseOptions = TestOptions.Regular; + Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); + compilation.VerifyDiagnostics(); + + Assert.Single(compilation.SyntaxTrees); + CallbackGenerator gen = new CallbackGenerator((c) => { }, (c) => + { + c.ReportDiagnostic(CSDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 2)); + c.ReportDiagnostic(CSDiagnostic.Create("GEN002", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 3)); + }); + + var options = ((CSharpCompilationOptions)compilation.Options); + + // generator driver diagnostics are reported seperately from the compilation + verifyDiagnosticsWithOptions(options, + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1)); + + // warnings can be individually suppressed + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN001", ReportDiagnostic.Suppress), + Diagnostic("GEN002").WithLocation(1, 1)); + + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN002", ReportDiagnostic.Suppress), + Diagnostic("GEN001").WithLocation(1, 1)); + + // warning level is respected + verifyDiagnosticsWithOptions(options.WithWarningLevel(0)); + + verifyDiagnosticsWithOptions(options.WithWarningLevel(2), + Diagnostic("GEN001").WithLocation(1, 1)); + + verifyDiagnosticsWithOptions(options.WithWarningLevel(3), + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1)); + + // warnings can be upgraded to errors + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN001", ReportDiagnostic.Error), + Diagnostic("GEN001").WithLocation(1, 1).WithWarningAsError(true), + Diagnostic("GEN002").WithLocation(1, 1)); + + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN002", ReportDiagnostic.Error), + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1).WithWarningAsError(true)); + + + void verifyDiagnosticsWithOptions(CompilationOptions options, params DiagnosticDescription[] expected) + { + GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions: parseOptions); + var updatedCompilation = compilation.WithOptions(options); + + driver.RunGeneratorsAndUpdateCompilation(updatedCompilation, out var outputCompilation, out var diagnostics); + outputCompilation.VerifyDiagnostics(); + diagnostics.Verify(expected); + } + } + + [Fact] + public void Diagnostics_Respect_Pragma_Suppression() + { + var gen001 = CSDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 2); + + // reported diagnostics can have a location in source + verifyDiagnosticsWithSource("//comment", + new[] { (gen001, TextSpan.FromBounds(2, 5)) }, + Diagnostic("GEN001", "com").WithLocation(1, 3)); + + // diagnostics are suppressed via #pragma + verifyDiagnosticsWithSource( +@"#pragma warning disable +//comment", + new[] { (gen001, TextSpan.FromBounds(27, 30)) }, + Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3)); + + // but not when they don't have a source location + verifyDiagnosticsWithSource( +@"#pragma warning disable +//comment", + new[] { (gen001, new TextSpan(0, 0)) }, + Diagnostic("GEN001").WithLocation(1, 1)); + + // can be suppressed explicitly + verifyDiagnosticsWithSource( +@"#pragma warning disable GEN001 +//comment", + new[] { (gen001, TextSpan.FromBounds(34, 37)) }, + Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3)); + + // suppress + restore + verifyDiagnosticsWithSource( +@"#pragma warning disable GEN001 +//comment +#pragma warning restore GEN001 +//another", + new[] { (gen001, TextSpan.FromBounds(34, 37)), (gen001, TextSpan.FromBounds(77, 80)) }, + Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3), + Diagnostic("GEN001", "ano").WithLocation(4, 3)); + + void verifyDiagnosticsWithSource(string source, (Diagnostic, TextSpan)[] reportDiagnostics, params DiagnosticDescription[] expected) + { + var parseOptions = TestOptions.Regular; + source = source.Replace(Environment.NewLine, "\r\n"); + Compilation compilation = CreateCompilation(source, sourceFileName: "sourcefile.cs", options: TestOptions.DebugDll, parseOptions: parseOptions); + compilation.VerifyDiagnostics(); + Assert.Single(compilation.SyntaxTrees); + + CallbackGenerator gen = new CallbackGenerator((c) => { }, (c) => + { + foreach ((var d, var l) in reportDiagnostics) + { + if (l.IsEmpty) + { + c.ReportDiagnostic(d); + } + else + { + c.ReportDiagnostic(d.WithLocation(Location.Create(c.Compilation.SyntaxTrees.First(), l))); + } + } + }); + GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions: parseOptions); + + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); + outputCompilation.VerifyDiagnostics(); + diagnostics.Verify(expected); + } + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs index 4ecf331944c5e..cc831c33af8c5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs @@ -3873,7 +3873,7 @@ class C var alias2 = model2.GetAliasInfo(node); Assert.Equal(alias1, alias2); - Assert.NotSame(alias1, alias2); + Assert.Same(alias1, alias2); } [WorkItem(542475, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542475")] @@ -3937,7 +3937,7 @@ static void Main() { } // This symbol we generate on-demand. var alias2b = model.GetDeclaredSymbol(usingDirectives[1]); - Assert.NotSame(alias2, alias2b); + Assert.Same(alias2, alias2b); Assert.Equal(alias2, alias2b); } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/UsedAssembliesTests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/UsedAssembliesTests.cs index 82102a0b3ae09..3e291c27d2dac 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/UsedAssembliesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/UsedAssembliesTests.cs @@ -4,12 +4,14 @@ #nullable disable +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; @@ -77,45 +79,162 @@ void verify(string source2, MetadataReference reference) where private void AssertUsedAssemblyReferences(Compilation comp, MetadataReference[] expected, DiagnosticDescription[] before, DiagnosticDescription[] after, MetadataReference[] specificReferencesToAssert) { - comp.VerifyDiagnostics(before); + foreach (var c in CloneCompilationsWithUsings(comp, before, after)) + { + assertUsedAssemblyReferences(c.comp, expected, c.before, c.after, specificReferencesToAssert); + } + + void assertUsedAssemblyReferences(Compilation comp, MetadataReference[] expected, DiagnosticDescription[] before, DiagnosticDescription[] after, MetadataReference[] specificReferencesToAssert) + { + comp.VerifyDiagnostics(before); + + bool hasCoreLibraryRef = !comp.ObjectType.IsErrorType(); + var used = comp.GetUsedAssemblyReferences(); + + if (hasCoreLibraryRef) + { + Assert.Same(comp.ObjectType.ContainingAssembly, comp.GetAssemblyOrModuleSymbol(used[0])); + AssertEx.Equal(expected, used.Skip(1)); + } + else + { + AssertEx.Equal(expected, used); + } + + Assert.Empty(used.Where(r => r.Properties.Kind == MetadataImageKind.Module)); + + var comp2 = comp.RemoveAllReferences().AddReferences(used.Concat(comp.References.Where(r => r.Properties.Kind == MetadataImageKind.Module))); + + if (!after.Any(d => ErrorFacts.GetSeverity((ErrorCode)d.Code) == DiagnosticSeverity.Error)) + { + CompileAndVerify(comp2, verify: Verification.Skipped).Diagnostics.Where(d => d.Code != (int)ErrorCode.WRN_NoRuntimeMetadataVersion).Verify(after); - bool hasCoreLibraryRef = !comp.ObjectType.IsErrorType(); - var used = comp.GetUsedAssemblyReferences(); + if (specificReferencesToAssert is object) + { + var tryRemove = specificReferencesToAssert.Where(reference => reference.Properties.Kind == MetadataImageKind.Assembly && !used.Contains(reference)); + if (tryRemove.Count() > 1) + { + foreach (var reference in tryRemove) + { + var comp3 = comp.RemoveReferences(reference); + CompileAndVerify(comp3, verify: Verification.Skipped).Diagnostics.Where(d => d.Code != (int)ErrorCode.WRN_NoRuntimeMetadataVersion).Verify(after); + } + } + } + } + else + { + comp2.VerifyDiagnostics(after); + } + } + } + + private static IEnumerable<(Compilation comp, DiagnosticDescription[] before, DiagnosticDescription[] after)> CloneCompilationsWithUsings(Compilation comp, DiagnosticDescription[] before, DiagnosticDescription[] after) + { + var tree = comp.SyntaxTrees.Single(); + var unit = (CompilationUnitSyntax)tree.GetRoot(); - if (hasCoreLibraryRef) + if (!unit.Usings.Any()) { - Assert.Same(comp.ObjectType.ContainingAssembly, comp.GetAssemblyOrModuleSymbol(used[0])); - AssertEx.Equal(expected, used.Skip(1)); + yield return (comp, before, after); + yield break; } - else + + var source = unit.ToFullString(); + var beforeUsings = source.Substring(0, unit.Usings.First().FullSpan.Start); + var afterUsings = source.Substring(unit.Usings.Last().FullSpan.End); + + // With regular usings + var builder = new System.Text.StringBuilder(); + builder.Append(beforeUsings); + builder.Append(@" +#line 1000 +"); + foreach (var directive in unit.Usings) { - AssertEx.Equal(expected, used); + builder.Append(directive.ToFullString()); } - Assert.Empty(used.Where(r => r.Properties.Kind == MetadataImageKind.Module)); + builder.Append(@" +#line 2000 +"); + builder.Append(afterUsings); - var comp2 = comp.RemoveAllReferences().AddReferences(used.Concat(comp.References.Where(r => r.Properties.Kind == MetadataImageKind.Module))); + yield return (comp.ReplaceSyntaxTree(tree, CSharpTestBase.Parse(builder.ToString(), tree.FilePath, (CSharpParseOptions)tree.Options)), before, after); - if (!after.Any(d => ErrorFacts.GetSeverity((ErrorCode)d.Code) == DiagnosticSeverity.Error)) + // With global usings + before = adjustDiagnosticDescription(before); + after = adjustDiagnosticDescription(after); + + builder.Clear(); + builder.Append(@" +#line 1000 +"); + foreach (var directive in unit.Usings) { - CompileAndVerify(comp2, verify: Verification.Skipped).Diagnostics.Where(d => d.Code != (int)ErrorCode.WRN_NoRuntimeMetadataVersion).Verify(after); + builder.Append(directive.ToFullString().Replace("using", "global using")); + } - if (specificReferencesToAssert is object) + var globalUsings = builder.ToString(); + + builder.Clear(); + builder.Append(beforeUsings); + builder.Append(globalUsings); + + builder.Append(@" +#line 2000 +"); + builder.Append(afterUsings); + + var parseOptions = ((CSharpParseOptions)tree.Options).WithLanguageVersion(LanguageVersion.Preview); + yield return (comp.ReplaceSyntaxTree(tree, CSharpTestBase.Parse(builder.ToString(), tree.FilePath, parseOptions)), before, after); + + // With global usings in a separate unit + builder.Clear(); + builder.Append(beforeUsings); + + builder.Append(@" +#line 2000 +"); + builder.Append(afterUsings); + + yield return (comp.ReplaceSyntaxTree(tree, CSharpTestBase.Parse(builder.ToString(), tree.FilePath, parseOptions)). + AddSyntaxTrees(CSharpTestBase.Parse(globalUsings, "", parseOptions)), before, after); + + static DiagnosticDescription[] adjustDiagnosticDescription(DiagnosticDescription[] input) + { + if (input is null) { - var tryRemove = specificReferencesToAssert.Where(reference => reference.Properties.Kind == MetadataImageKind.Assembly && !used.Contains(reference)); - if (tryRemove.Count() > 1) + return null; + } + + DiagnosticDescription[] output = null; + + for (int i = 0; i < input.Length; i++) + { + var old = input[i]; + + if (old.Code is (int)ErrorCode.HDN_UnusedUsingDirective) { - foreach (var reference in tryRemove) - { - var comp3 = comp.RemoveReferences(reference); - CompileAndVerify(comp3, verify: Verification.Skipped).Diagnostics.Where(d => d.Code != (int)ErrorCode.WRN_NoRuntimeMetadataVersion).Verify(after); - } + allocateOutput(input, ref output)[i] = old.WithSquiggledText("global " + old.SquiggledText); + } + else if (old.LocationLine is > 1000 and < 2000) + { + allocateOutput(input, ref output)[i] = old.WithLocation(old.LocationLine, old.LocationCharacter + 7); } } + + return output ?? input; } - else + + static DiagnosticDescription[] allocateOutput(DiagnosticDescription[] input, ref DiagnosticDescription[] output) { - comp2.VerifyDiagnostics(after); + if (output is null) + { + System.Array.Copy(input, output = new DiagnosticDescription[input.Length], input.Length); + } + + return output; } } @@ -144,11 +263,20 @@ private Compilation AssertUsedAssemblyReferences(string source, params MetadataR private static void AssertUsedAssemblyReferences(string source, MetadataReference[] references, params DiagnosticDescription[] expected) { Compilation comp = CreateCompilation(source, references: references); - var diagnostics = comp.GetDiagnostics(); - diagnostics.Verify(expected); - Assert.True(diagnostics.Any(d => d.DefaultSeverity == DiagnosticSeverity.Error)); - AssertEx.Equal(comp.References.Where(r => r.Properties.Kind == MetadataImageKind.Assembly), comp.GetUsedAssemblyReferences()); + foreach (var c in CloneCompilationsWithUsings(comp, expected, null)) + { + assertUsedAssemblyReferences(c.comp, c.before); + } + + static void assertUsedAssemblyReferences(Compilation comp, params DiagnosticDescription[] expected) + { + var diagnostics = comp.GetDiagnostics(); + diagnostics.Verify(expected); + + Assert.True(diagnostics.Any(d => d.DefaultSeverity == DiagnosticSeverity.Error)); + AssertEx.Equal(comp.References.Where(r => r.Properties.Kind == MetadataImageKind.Assembly), comp.GetUsedAssemblyReferences()); + } } private ImmutableArray CompileWithUsedAssemblyReferences(string source, TargetFramework targetFramework, params MetadataReference[] references) @@ -170,28 +298,49 @@ private ImmutableArray CompileWithUsedAssemblyReferences(stri private ImmutableArray CompileWithUsedAssemblyReferences(Compilation comp, string expectedOutput = null, MetadataReference[] specificReferencesToAssert = null) { - var used = comp.GetUsedAssemblyReferences(); - CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: expectedOutput).VerifyDiagnostics(); + ImmutableArray result = default; - Assert.Empty(used.Where(r => r.Properties.Kind == MetadataImageKind.Module)); + foreach (var c in CloneCompilationsWithUsings(comp, null, null)) + { + var currentResult = compileWithUsedAssemblyReferences(c.comp, expectedOutput, specificReferencesToAssert); - if (specificReferencesToAssert is object) + if (result.IsDefault) + { + result = currentResult; + } + else + { + AssertEx.Equal(result, currentResult); + } + } + + return result; + + ImmutableArray compileWithUsedAssemblyReferences(Compilation comp, string expectedOutput = null, MetadataReference[] specificReferencesToAssert = null) { - var tryRemove = specificReferencesToAssert.Where(reference => reference.Properties.Kind == MetadataImageKind.Assembly && !used.Contains(reference)); - if (tryRemove.Count() > 1) + var used = comp.GetUsedAssemblyReferences(); + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: expectedOutput).VerifyDiagnostics(); + + Assert.Empty(used.Where(r => r.Properties.Kind == MetadataImageKind.Module)); + + if (specificReferencesToAssert is object) { - foreach (var reference in tryRemove) + var tryRemove = specificReferencesToAssert.Where(reference => reference.Properties.Kind == MetadataImageKind.Assembly && !used.Contains(reference)); + if (tryRemove.Count() > 1) { - var comp3 = comp.RemoveReferences(reference); - CompileAndVerify(comp3, verify: Verification.Skipped, expectedOutput: expectedOutput).VerifyDiagnostics(); + foreach (var reference in tryRemove) + { + var comp3 = comp.RemoveReferences(reference); + CompileAndVerify(comp3, verify: Verification.Skipped, expectedOutput: expectedOutput).VerifyDiagnostics(); + } } } - } - var comp2 = comp.RemoveAllReferences().AddReferences(used.Concat(comp.References.Where(r => r.Properties.Kind == MetadataImageKind.Module))); - CompileAndVerify(comp2, verify: Verification.Skipped, expectedOutput: expectedOutput).VerifyDiagnostics(); + var comp2 = comp.RemoveAllReferences().AddReferences(used.Concat(comp.References.Where(r => r.Properties.Kind == MetadataImageKind.Module))); + CompileAndVerify(comp2, verify: Verification.Skipped, expectedOutput: expectedOutput).VerifyDiagnostics(); - return used; + return used; + } } private ImmutableArray CompileWithUsedAssemblyReferences(string source, params MetadataReference[] references) @@ -1678,12 +1827,12 @@ public static void Main() "; AssertUsedAssemblyReferences(source5, new[] { comp0Ref, comp1Ref }, - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(2, 1), - // (8,20): error CS0103: The name 'M1' does not exist in the current context + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(1001, 1), + // (2005,20): error CS0103: The name 'M1' does not exist in the current context // _ = nameof(M1); - Diagnostic(ErrorCode.ERR_NameNotInContext, "M1").WithArguments("M1").WithLocation(8, 20) + Diagnostic(ErrorCode.ERR_NameNotInContext, "M1").WithArguments("M1").WithLocation(2005, 20) ); @@ -2020,9 +2169,9 @@ public class C2 { } ", - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using N1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1;").WithLocation(1001, 1) ); verify1(comp1Ref, @@ -2033,9 +2182,9 @@ public class C2 { } ", - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static N1.C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static N1.C1;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static N1.C1;").WithLocation(1001, 1) ); verify1(comp1Ref, @@ -2046,9 +2195,9 @@ public class C2 { } ", - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using alias = N1.C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias = N1.C1;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias = N1.C1;").WithLocation(1001, 1) ); verify1(comp1Ref, @@ -2059,9 +2208,9 @@ public class C2 { } ", - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using alias = N1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias = N1;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using alias = N1;").WithLocation(1001, 1) ); verify1(comp1Ref.WithAliases(new[] { "N1C1" }), @@ -2161,14 +2310,23 @@ public class C2 static void verify1(MetadataReference reference, string source, params DiagnosticDescription[] expected) { Compilation comp = CreateCompilation(source, references: new[] { reference }); - comp.VerifyDiagnostics(expected); - Assert.True(comp.References.Count() > 1); + foreach (var c in CloneCompilationsWithUsings(comp, expected, null)) + { + verify(c.comp, c.before); + } - var used = comp.GetUsedAssemblyReferences(); + static void verify(Compilation comp, params DiagnosticDescription[] expected) + { + comp.VerifyDiagnostics(expected); + + Assert.True(comp.References.Count() > 1); + + var used = comp.GetUsedAssemblyReferences(); - Assert.Equal(1, used.Length); - Assert.Same(comp.ObjectType.ContainingAssembly, comp.GetAssemblyOrModuleSymbol(used[0])); + Assert.Equal(1, used.Length); + Assert.Same(comp.ObjectType.ContainingAssembly, comp.GetAssemblyOrModuleSymbol(used[0])); + } } void verify2(MetadataReference reference, string source, string @using) @@ -3371,9 +3529,14 @@ void verifyNotUsed(string source2) void verifyNotUsed(string source2, MetadataReference ref0, MetadataReference ref1) { - var used = CreateCompilation(source2, references: new[] { ref0, ref1 }).GetUsedAssemblyReferences(); - Assert.DoesNotContain(ref0, used); - Assert.DoesNotContain(ref1, used); + var comp = CreateCompilation(source2, references: new[] { ref0, ref1 }); + + foreach (var c in CloneCompilationsWithUsings(comp, null, null)) + { + var used = c.comp.GetUsedAssemblyReferences(); + Assert.DoesNotContain(ref0, used); + Assert.DoesNotContain(ref1, used); + } } } } @@ -3814,7 +3977,7 @@ public static void Main() Assert.DoesNotContain(comp1Ref, used); - used = CreateCompilation(@" + var comp = CreateCompilation(@" using static C2.C1; public class C3 @@ -3823,12 +3986,16 @@ public static void Main() { } } -", references: new[] { comp0Ref, comp1Ref }).GetUsedAssemblyReferences(); +", references: new[] { comp0Ref, comp1Ref }); - Assert.DoesNotContain(comp0Ref, used); - Assert.DoesNotContain(comp1Ref, used); + foreach (var c in CloneCompilationsWithUsings(comp, null, null)) + { + used = c.comp.GetUsedAssemblyReferences(); + Assert.DoesNotContain(comp0Ref, used); + Assert.DoesNotContain(comp1Ref, used); + } - used = CreateCompilation(@" + comp = CreateCompilation(@" using alias = C2.C1; public class C3 @@ -3837,10 +4004,14 @@ public static void Main() { } } -", references: new[] { comp0Ref, comp1Ref }).GetUsedAssemblyReferences(); +", references: new[] { comp0Ref, comp1Ref }); - Assert.DoesNotContain(comp0Ref, used); - Assert.DoesNotContain(comp1Ref, used); + foreach (var c in CloneCompilationsWithUsings(comp, null, null)) + { + used = c.comp.GetUsedAssemblyReferences(); + Assert.DoesNotContain(comp0Ref, used); + Assert.DoesNotContain(comp1Ref, used); + } CompileWithUsedAssemblyReferences(@" using static C2.C1; @@ -4171,17 +4342,17 @@ static void Main() AssertUsedAssemblyReferences(CreateCompilation(source3, references: references, parseOptions: TestOptions.Regular.WithDocumentationMode(DocumentationMode.Parse)), new MetadataReference[] { }, new[] { - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using N1.N2; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1.N2;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1.N2;").WithLocation(1001, 1) }, new[] { - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using N1.N2; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1.N2;").WithLocation(2, 1), - // (2,7): error CS0246: The type or namespace name 'N1' could not be found (are you missing a using directive or an assembly reference?) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1.N2;").WithLocation(1001, 1), + // (1001,7): error CS0246: The type or namespace name 'N1' could not be found (are you missing a using directive or an assembly reference?) // using N1.N2; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "N1").WithArguments("N1").WithLocation(2, 7) + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "N1").WithArguments("N1").WithLocation(1001, 7) }, references); var source4 = @@ -4200,17 +4371,17 @@ static void Main() AssertUsedAssemblyReferences(CreateCompilation(source4, references: references, parseOptions: TestOptions.Regular.WithDocumentationMode(DocumentationMode.Parse)), new MetadataReference[] { }, new[] { - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using N1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1;").WithLocation(1001, 1) }, new[] { - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using N1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1;").WithLocation(2, 1), - // (2,7): error CS0246: The type or namespace name 'N1' could not be found (are you missing a + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N1;").WithLocation(1001, 1), + // (1001,7): error CS0246: The type or namespace name 'N1' could not be found (are you missing a // using N1; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "N1").WithArguments("N1").WithLocation(2, 7) + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "N1").WithArguments("N1").WithLocation(1001, 7) }, references); var source5 = @@ -4391,17 +4562,17 @@ static void Main() AssertUsedAssemblyReferences(CreateCompilation(source3, references: references, parseOptions: TestOptions.Regular.WithDocumentationMode(DocumentationMode.Parse)), new MetadataReference[] { }, new[] { - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static N1.N2.E0; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static N1.N2.E0;").WithLocation(2, 1) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static N1.N2.E0;").WithLocation(1001, 1) }, new[] { - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static N1.N2.E0; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static N1.N2.E0;").WithLocation(2, 1), - // (2,14): error CS0246: The type or namespace name 'N1' could not be found (are you missing a using directive or an assembly reference?) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static N1.N2.E0;").WithLocation(1001, 1), + // (1001,14): error CS0246: The type or namespace name 'N1' could not be found (are you missing a using directive or an assembly reference?) // using static N1.N2.E0; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "N1").WithArguments("N1").WithLocation(2, 14) + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "N1").WithArguments("N1").WithLocation(1001, 14) }, references); var source5 = @@ -4897,12 +5068,12 @@ public static void Main() "; AssertUsedAssemblyReferences(source4, references, - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(2, 1), - // (8,9): error CS1545: Property, indexer, or event 'C1.P1[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P1(int)' or 'C1.set_P1(int, C0)' + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(1001, 1), + // (2005,9): error CS1545: Property, indexer, or event 'C1.P1[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P1(int)' or 'C1.set_P1(int, C0)' // P1[0] = null; - Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P1").WithArguments("C1.P1[int]", "C1.get_P1(int)", "C1.set_P1(int, C0)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P1").WithArguments("C1.P1[int]", "C1.get_P1(int)", "C1.set_P1(int, C0)").WithLocation(2005, 9) ); var source5 = @@ -4919,12 +5090,12 @@ public static void Main() "; AssertUsedAssemblyReferences(source5, references, - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(2, 1), - // (8,13): error CS1545: Property, indexer, or event 'C1.P1[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P1(int)' or 'C1.set_P1(int, C0)' + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(1001, 1), + // (2005,13): error CS1545: Property, indexer, or event 'C1.P1[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P1(int)' or 'C1.set_P1(int, C0)' // _ = P1[0]; - Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P1").WithArguments("C1.P1[int]", "C1.get_P1(int)", "C1.set_P1(int, C0)").WithLocation(8, 13) + Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P1").WithArguments("C1.P1[int]", "C1.get_P1(int)", "C1.set_P1(int, C0)").WithLocation(2005, 13) ); var source6 = @@ -4958,12 +5129,12 @@ public static void Main() "; AssertUsedAssemblyReferences(source7, references, - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(2, 1), - // (8,20): error CS1545: Property, indexer, or event 'C1.P1[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P1(int)' or 'C1.set_P1(int, C0)' + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(1001, 1), + // (2005,20): error CS1545: Property, indexer, or event 'C1.P1[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P1(int)' or 'C1.set_P1(int, C0)' // _ = nameof(P1); - Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P1").WithArguments("C1.P1[int]", "C1.get_P1(int)", "C1.set_P1(int, C0)").WithLocation(8, 20) + Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P1").WithArguments("C1.P1[int]", "C1.get_P1(int)", "C1.set_P1(int, C0)").WithLocation(2005, 20) ); var source8 = @@ -4997,12 +5168,12 @@ public static void Main() "; AssertUsedAssemblyReferences(source9, references, - // (2,1): hidden CS8019: Unnecessary using directive. + // (1001,1): hidden CS8019: Unnecessary using directive. // using static C1; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(2, 1), - // (8,20): error CS1545: Property, indexer, or event 'C1.P2[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P2(int)' or 'C1.set_P2(int, C0)' + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C1;").WithLocation(1001, 1), + // (2005,20): error CS1545: Property, indexer, or event 'C1.P2[int]' is not supported by the language; try directly calling accessor methods 'C1.get_P2(int)' or 'C1.set_P2(int, C0)' // _ = nameof(P2); - Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P2").WithArguments("C1.P2[int]", "C1.get_P2(int)", "C1.set_P2(int, C0)").WithLocation(8, 20) + Diagnostic(ErrorCode.ERR_BindToBogusProp2, "P2").WithArguments("C1.P2[int]", "C1.get_P2(int)", "C1.set_P2(int, C0)").WithLocation(2005, 20) ); } diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index f56b37d03ca46..2fb52eb889c1c 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -7653,6 +7653,75 @@ record Person(string First, string Last); SymbolDisplayPartKind.RecordClassName); } + [Fact] + public void RecordClassDeclaration() + { + var text = @" +record class Person(string First, string Last); +"; + Func findSymbol = global => global.GetTypeMembers("Person").Single(); + + var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); + + TestSymbolDescription( + text, + findSymbol, + format, + TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp9), + "record Person", + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.RecordClassName); + } + + [Fact] + public void RecordStructDeclaration() + { + var text = @" +record struct Person(string First, string Last); +"; + Func findSymbol = global => global.GetTypeMembers("Person").Single(); + + var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); + + TestSymbolDescription( + text, + findSymbol, + format, + TestOptions.Regular.WithLanguageVersion(LanguageVersion.Preview), + "record struct Person", + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.RecordStructName); + } + + [Fact] + public void ReadOnlyRecordStructDeclaration() + { + var text = @" +readonly record struct Person(string First, string Last); +"; + Func findSymbol = global => global.GetTypeMembers("Person").Single(); + + var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); + + TestSymbolDescription( + text, + findSymbol, + format, + TestOptions.Regular.WithLanguageVersion(LanguageVersion.Preview), + "readonly record struct Person", + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.RecordStructName); + } + [Fact, WorkItem(51222, "https://github.com/dotnet/roslyn/issues/51222")] public void TestFunctionPointerWithoutIncludeTypesInParameterOptions() { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index c793f70bc77cf..cdcf4580a744f 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -43456,6 +43456,277 @@ static void Main() CompileAndVerify(compilation3, verify: VerifyOnMonoOrCoreClr, expectedOutput: !ExecutionConditionUtil.IsMonoOrCoreClr ? null : expectedOutput); } + [Fact] + [WorkItem(52202, "https://github.com/dotnet/roslyn/issues/52202")] + public void Operators_32() + { + var source1 = +@" +public interface I1 +{ + static I1 operator +(I1 x) + { + System.Console.WriteLine(""+""); + return x; + } + + static I1 operator -(I1 x) + { + System.Console.WriteLine(""-""); + return x; + } + + static I1 operator !(I1 x) + { + System.Console.WriteLine(""!""); + return x; + } + + static I1 operator ~(I1 x) + { + System.Console.WriteLine(""~""); + return x; + } + + static I1 operator ++(I1 x) + { + System.Console.WriteLine(""++""); + return x; + } + + static I1 operator --(I1 x) + { + System.Console.WriteLine(""--""); + return x; + } + + static bool operator true(I1 x) + { + System.Console.WriteLine(""true""); + return true; + } + + static bool operator false(I1 x) + { + System.Console.WriteLine(""false""); + return false; + } + + static I1 operator +(I1 x, I1 y) + { + System.Console.WriteLine(""+2""); + return x; + } + + static I1 operator -(I1 x, I1 y) + { + System.Console.WriteLine(""-2""); + return x; + } + + static I1 operator *(I1 x, I1 y) + { + System.Console.WriteLine(""*""); + return x; + } + + static I1 operator /(I1 x, I1 y) + { + System.Console.WriteLine(""/""); + return x; + } + + static I1 operator %(I1 x, I1 y) + { + System.Console.WriteLine(""%""); + return x; + } + + static I1 operator &(I1 x, I1 y) + { + System.Console.WriteLine(""&""); + return x; + } + + static I1 operator |(I1 x, I1 y) + { + System.Console.WriteLine(""|""); + return x; + } + + static I1 operator ^(I1 x, I1 y) + { + System.Console.WriteLine(""^""); + return x; + } + + static I1 operator <<(I1 x, int y) + { + System.Console.WriteLine(""<<""); + return x; + } + + static I1 operator >>(I1 x, int y) + { + System.Console.WriteLine("">>""); + return x; + } + + static I1 operator >(I1 x, I1 y) + { + System.Console.WriteLine("">""); + return x; + } + + static I1 operator <(I1 x, I1 y) + { + System.Console.WriteLine(""<""); + return x; + } + + static I1 operator >=(I1 x, I1 y) + { + System.Console.WriteLine("">=""); + return x; + } + + static I1 operator <=(I1 x, I1 y) + { + System.Console.WriteLine(""<=""); + return x; + } +} +"; + + var source2 = +@" +class Test2 : I1 +{ + static void Main() + { + I1 x = new Test2(); + I1 y = new Test2(); + + x = +x; + x = -x; + x = !x; + x = ~x; + x = ++x; + x = x--; + + x = x + y; + x = x - y; + x = x * y; + x = x / y; + x = x % y; + if (x && y) { } + x = x | y; + x = x ^ y; + x = x << 1; + x = x >> 2; + x = x > y; + x = x < y; + x = x >= y; + x = x <= y; + } +} +"; + + var expectedOutput = +@" ++ +- +! +~ +++ +-- ++2 +-2 +* +/ +% +false +& +true +| +^ +<< +>> +> +< +>= +<= +"; + + var compilation1 = CreateCompilation(source1 + source2, options: TestOptions.DebugExe, + targetFramework: TargetFramework.NetCoreApp, + parseOptions: TestOptions.Regular); + + var i1 = compilation1.GlobalNamespace.GetTypeMember("I1"); + + foreach (var member in i1.GetMembers()) + { + Assert.Equal(Accessibility.Public, member.DeclaredAccessibility); + } + + CompileAndVerify(compilation1, expectedOutput: !ExecutionConditionUtil.IsMonoOrCoreClr ? null : expectedOutput, verify: VerifyOnMonoOrCoreClr).VerifyDiagnostics(); + + CompilationReference compilationReference = compilation1.ToMetadataReference(); + MetadataReference metadataReference = compilation1.EmitToImageReference(); + + var compilation2 = CreateCompilation(source2, new[] { compilationReference }, options: TestOptions.DebugExe, + targetFramework: TargetFramework.NetCoreApp, + parseOptions: TestOptions.Regular); + + CompileAndVerify(compilation2, expectedOutput: !ExecutionConditionUtil.IsMonoOrCoreClr ? null : expectedOutput, verify: VerifyOnMonoOrCoreClr).VerifyDiagnostics(); + + var compilation3 = CreateCompilation(source2, new[] { metadataReference }, options: TestOptions.DebugExe, + parseOptions: TestOptions.Regular); + + CompileAndVerify(compilation3, expectedOutput: !ExecutionConditionUtil.IsMonoOrCoreClr ? null : expectedOutput, verify: VerifyOnMonoOrCoreClr).VerifyDiagnostics(); + } + + [Fact] + [WorkItem(52202, "https://github.com/dotnet/roslyn/issues/52202")] + public void Operators_33() + { + var source1 = +@" +public interface I1 +{ + static implicit operator int(I1 x) + { + return 0; + } + + static explicit operator byte(I1 x) + { + return 0; + } +} +"; + + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, + targetFramework: TargetFramework.NetCoreApp, + parseOptions: TestOptions.Regular); + + var i1 = compilation1.GlobalNamespace.GetTypeMember("I1"); + + foreach (var member in i1.GetMembers()) + { + Assert.Equal(Accessibility.Public, member.DeclaredAccessibility); + } + + compilation1.VerifyDiagnostics( + // (4,30): error CS0567: Interfaces cannot contain conversion, equality, or inequality operators + // static implicit operator int(I1 x) + Diagnostic(ErrorCode.ERR_InterfacesCantContainConversionOrEqualityOperators, "int").WithLocation(4, 30), + // (9,30): error CS0567: Interfaces cannot contain conversion, equality, or inequality operators + // static explicit operator byte(I1 x) + Diagnostic(ErrorCode.ERR_InterfacesCantContainConversionOrEqualityOperators, "byte").WithLocation(9, 30) + ); + } + [Fact] public void RuntimeFeature_01() { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DestructorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DestructorTests.cs index cdd7e14c24fa9..3b73251cc95ea 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DestructorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DestructorTests.cs @@ -545,7 +545,7 @@ public static int Main() "; CreateCompilation(source).VerifyDiagnostics( - // (5,6): error CS0577: The Conditional attribute is not valid on 'Test.~Test()' because it is a constructor, destructor, operator, or explicit interface implementation + // (5,6): error CS0577: The Conditional attribute is not valid on 'Test.~Test()' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation // [Conditional("Debug")] Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"Conditional(""Debug"")").WithArguments("Test.~Test()")); } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ErrorTypeSymbolTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ErrorTypeSymbolTests.cs index 2f5350dcb8620..7a7caaf752188 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ErrorTypeSymbolTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ErrorTypeSymbolTests.cs @@ -108,5 +108,18 @@ private void CompareConstructedErrorTypes(CSharpCompilation compilation, bool mi } } } + + [WorkItem(52516, "https://github.com/dotnet/roslyn/issues/52516")] + [Fact] + public void ErrorInfo_01() + { + var error = new MissingMetadataTypeSymbol.Nested(new UnsupportedMetadataTypeSymbol(), "Test", 0, false); + var info = error.ErrorInfo; + + Assert.Equal(ErrorCode.ERR_BogusType, (ErrorCode)info.Code); + Assert.Null(error.ContainingModule); + Assert.Null(error.ContainingAssembly); + Assert.NotNull(error.ContainingSymbol); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs index f58bca71de5aa..d6377beb3b457 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs @@ -1140,9 +1140,9 @@ static class S // (5,9): error CS1656: Cannot assign to 'E' because it is a 'method group' // o.E += o.E; Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "o.E").WithArguments("E", "method group").WithLocation(5, 9), - // (6,13): error CS0019: Operator '!=' cannot be applied to operands of type 'method group' and '' + // (6,13): error CS8652: The feature 'inferred delegate type' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // if (o.E != null) - Diagnostic(ErrorCode.ERR_BadBinaryOps, "o.E != null").WithArguments("!=", "method group", "").WithLocation(6, 13), + Diagnostic(ErrorCode.ERR_FeatureInPreview, "o.E").WithArguments("inferred delegate type").WithLocation(6, 13), // (8,15): error CS1503: Argument 1: cannot convert from 'method group' to 'object' // M(o.E); Diagnostic(ErrorCode.ERR_BadArgType, "o.E").WithArguments("1", "method group", "object").WithLocation(8, 15), @@ -1152,22 +1152,22 @@ static class S // (10,17): error CS0023: Operator '!' cannot be applied to operand of type 'method group' // o = !o.E; Diagnostic(ErrorCode.ERR_BadUnaryOp, "!o.E").WithArguments("!", "method group").WithLocation(10, 17), - // (12,11): error CS1061: 'object' does not contain a definition for 'F' and no extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // (12,11): error CS1061: 'object' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) // o.F += o.F; Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("object", "F").WithLocation(12, 11), - // (12,18): error CS1061: 'object' does not contain a definition for 'F' and no extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // (12,18): error CS1061: 'object' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) // o.F += o.F; Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("object", "F").WithLocation(12, 18), - // (13,15): error CS1061: 'object' does not contain a definition for 'F' and no extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // (13,15): error CS1061: 'object' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) // if (o.F != null) Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("object", "F").WithLocation(13, 15), - // (15,17): error CS1061: 'object' does not contain a definition for 'F' and no extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // (15,17): error CS1061: 'object' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) // M(o.F); Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("object", "F").WithLocation(15, 17), - // (16,15): error CS1061: 'object' does not contain a definition for 'F' and no extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // (16,15): error CS1061: 'object' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) // o.F.ToString(); Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("object", "F").WithLocation(16, 15), - // (17,20): error CS1061: 'object' does not contain a definition for 'F' and no extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) + // (17,20): error CS1061: 'object' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) // o = !o.F; Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("object", "F").WithLocation(17, 20), // (19,11): error CS0119: 'S.E(object)' is a method, which is not valid in the given context diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/GenericConstraintTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/GenericConstraintTests.cs index 3d48858fc7e6b..2b67e358b971b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/GenericConstraintTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/GenericConstraintTests.cs @@ -7123,5 +7123,108 @@ static void M() Diagnostic(ErrorCode.ERR_BadArity, "GetServiceC<>").WithArguments("Program.GetServiceC()", "method", "2").WithLocation(14, 9) ); } + + [Fact, WorkItem(1279758, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1279758/")] + public void RecursiveConstraintsFromUnifiedAssemblies_1() + { + var code = @" +public abstract class A + where T1 : A + where T2 : A.B +{ + public abstract class B + where T3 : A + where T4 : A.B + { } +} +public class C : A +{ + public class D : A.B + { + } +} +"; + var metadataComp = CreateEmptyCompilation(code, new[] { MscorlibRef_v20 }, assemblyName: "assembly1"); + metadataComp.VerifyDiagnostics(); + var comp = CreateCompilation(@"System.Console.WriteLine(typeof(C.D).FullName);", + new[] { metadataComp.EmitToImageReference() }, + targetFramework: TargetFramework.Mscorlib45); + + // warning CS1701: Assuming assembly reference 'mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' used by 'assembly1' matches identity 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' of 'mscorlib', you may need to supply runtime policy + DiagnosticDescription expectedDiagnostic = Diagnostic(ErrorCode.WRN_UnifyReferenceMajMin).WithArguments("mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "assembly1", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "mscorlib").WithLocation(1, 1); + + // These are unification use-site diagnostics. The original stackoverflow bug here came from checking constraints as part of + // unification diagnostic calculation, so we want to verify that these are present to make sure we're testing the correct scenario. + comp.VerifyDiagnostics( + expectedDiagnostic, + // warning CS1701: Assuming assembly reference 'mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' used by 'assembly1' matches identity 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' of 'mscorlib', you may need to supply runtime policy + expectedDiagnostic + ); + + CompileAndVerify( + comp.WithOptions(comp.Options.WithSpecificDiagnosticOptions("CS1701", ReportDiagnostic.Suppress)), + expectedOutput: "C+D"); + + var c = comp.GetTypeByMetadataName("C"); + Assert.True(c.ContainingModule.HasUnifiedReferences); + Assert.Equal(expectedDiagnostic.Code, c.GetUseSiteDiagnostic().Code); + } + + [Fact, WorkItem(1279758, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1279758/")] + public void RecursiveConstraintsFromUnifiedAssemblies_2() + { + var remappedCode = @"public class F {}"; + + var remappedComp11 = CreateCompilation( + new AssemblyIdentity("remapped", new Version("1.0.0.0"), publicKeyOrToken: SigningTestHelpers.PublicKey, hasPublicKey: true), + new[] { remappedCode }, + TargetFrameworkUtil.NetStandard20References.ToArray(), + TestOptions.ReleaseDll.WithPublicSign(true)); + + var remappedComp12 = CreateCompilation( + new AssemblyIdentity("remapped", new Version("2.0.0.0"), publicKeyOrToken: SigningTestHelpers.PublicKey, hasPublicKey: true), + new[] { remappedCode }, + TargetFrameworkUtil.NetStandard20References.ToArray(), + TestOptions.ReleaseDll.WithPublicSign(true)); + + var code = @" +public abstract class A + where T1 : A + where T2 : A.B +{ + public abstract class B + where T3 : A + where T4 : A.B + { } +} +public class C : A +{ + public class D : A.B + { + } +} + +public class G : F {} +"; + + var metadataComp = CreateCompilation(code, new[] { remappedComp11.EmitToImageReference() }, assemblyName: "intermediate", targetFramework: TargetFramework.NetStandard20); + metadataComp.VerifyDiagnostics(); + + var comp = CreateCompilation(@" +System.Console.WriteLine(typeof(C.D).FullName); +System.Console.WriteLine(typeof(G).FullName); +", + new[] { metadataComp.EmitToImageReference(), remappedComp12.EmitToImageReference() }, + targetFramework: TargetFramework.NetStandard20); + + comp.VerifyDiagnostics( + // warning CS1701: Assuming assembly reference 'remapped, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ce65828c82a341f2' used by 'intermediate' matches identity 'remapped, Version=2.0.0.0, Culture=neutral, PublicKeyToken=ce65828c82a341f2' of 'remapped', you may need to supply runtime policy + Diagnostic(ErrorCode.WRN_UnifyReferenceMajMin).WithArguments("remapped, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ce65828c82a341f2", "intermediate", "remapped, Version=2.0.0.0, Culture=neutral, PublicKeyToken=ce65828c82a341f2", "remapped").WithLocation(1, 1) + ); + + var c = comp.GetTypeByMetadataName("C"); + Assert.Null(c.GetUseSiteDiagnostic()); + Assert.True(c.ContainingModule.HasUnifiedReferences); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs index 607ce03a4b0dc..0b23102da8b11 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs @@ -325,6 +325,7 @@ internal override AttributeUsageInfo GetAttributeUsageInfo() internal sealed override NamedTypeSymbol NativeIntegerUnderlyingType => null; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index 8761da9f9410e..b8a1065b7f6f4 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -942,62 +942,6 @@ .maxstack 2 "); } - [Fact(Skip = "record struct")] - public void RecordClone4_0() - { - var comp = CreateCompilation(@" -using System; -public data struct S(int x, int y) -{ - public event Action E; - public int Z; -}"); - comp.VerifyDiagnostics( - // (3,21): error CS0171: Field 'S.E' must be fully assigned before control is returned to the caller - // public data struct S(int x, int y) - Diagnostic(ErrorCode.ERR_UnassignedThis, "(int x, int y)").WithArguments("S.E").WithLocation(3, 21), - // (3,21): error CS0171: Field 'S.Z' must be fully assigned before control is returned to the caller - // public data struct S(int x, int y) - Diagnostic(ErrorCode.ERR_UnassignedThis, "(int x, int y)").WithArguments("S.Z").WithLocation(3, 21), - // (5,25): warning CS0067: The event 'S.E' is never used - // public event Action E; - Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E").WithArguments("S.E").WithLocation(5, 25) - ); - - var s = comp.GlobalNamespace.GetTypeMember("S"); - var clone = s.GetMethod(WellKnownMemberNames.CloneMethodName); - Assert.Equal(0, clone.Arity); - Assert.Equal(0, clone.ParameterCount); - Assert.Equal(s, clone.ReturnType); - - var ctor = (MethodSymbol)s.GetMembers(".ctor")[1]; - Assert.Equal(1, ctor.ParameterCount); - Assert.True(ctor.Parameters[0].Type.Equals(s, TypeCompareKind.ConsiderEverything)); - } - - [Fact(Skip = "record struct")] - public void RecordClone4_1() - { - var comp = CreateCompilation(@" -using System; -public data struct S(int x, int y) -{ - public event Action E = null; - public int Z = 0; -}"); - comp.VerifyDiagnostics( - // (5,25): error CS0573: 'S': cannot have instance property or field initializers in structs - // public event Action E = null; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "E").WithArguments("S").WithLocation(5, 25), - // (5,25): warning CS0414: The field 'S.E' is assigned but its value is never used - // public event Action E = null; - Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "E").WithArguments("S.E").WithLocation(5, 25), - // (6,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int Z = 0; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "Z").WithArguments("S").WithLocation(6, 16) - ); - } - [Fact] public void NominalRecordEquals() { @@ -1137,7 +1081,7 @@ record C "System.String! C.Y { get; init; }", "System.String! C.Y.get", "void C.Y.init", - "System.String C.ToString()", + "System.String! C.ToString()", "System.Boolean C." + WellKnownMemberNames.PrintMembersMethodName + "(System.Text.StringBuilder! builder)", "System.Boolean C.operator !=(C? left, C? right)", "System.Boolean C.operator ==(C? left, C? right)", @@ -1371,6 +1315,9 @@ enum G : C { }"; // (3,8): error CS0115: 'B.Equals(A?)': no suitable method found to override // record B : A { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.Equals(A?)").WithLocation(3, 8), + // (3,8): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override + // record B : A { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(3, 8), // (3,8): error CS8867: No accessible copy constructor found in base type 'A'. // record B : A { } Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "B").WithArguments("A").WithLocation(3, 8), @@ -1424,6 +1371,9 @@ enum H : C { } // (3,8): error CS0115: 'E.Equals(A?)': no suitable method found to override // record E : A { } Diagnostic(ErrorCode.ERR_OverrideNotExpected, "E").WithArguments("E.Equals(A?)").WithLocation(3, 8), + // (3,8): error CS0115: 'E.PrintMembers(StringBuilder)': no suitable method found to override + // record E : A { } + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "E").WithArguments("E.PrintMembers(System.Text.StringBuilder)").WithLocation(3, 8), // (3,8): error CS8867: No accessible copy constructor found in base type 'A'. // record E : A { } Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "E").WithArguments("A").WithLocation(3, 8), diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs index f6189db3f1727..3ba22698c6c4b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -7723,7 +7723,8 @@ class D : C public override void F(int t) {} // CS0462 } "; - var comp = CreateCompilation(text); + var comp = CreateCompilation(text, targetFramework: TargetFramework.StandardLatest); + Assert.Equal(RuntimeUtilities.IsCoreClrRuntime, comp.Assembly.RuntimeSupportsCovariantReturnsOfClasses); if (comp.Assembly.RuntimeSupportsDefaultInterfaceImplementation) { comp.VerifyDiagnostics( @@ -10737,7 +10738,64 @@ class Cls3 : global::Globals.Errors.ResolveInheritance.ConflictingAlias.Nested { } [Fact] - public void CS0577ERR_ConditionalOnSpecialMethod() + public void CS0577ERR_ConditionalOnSpecialMethod_01() + { + var sourceA = +@"#pragma warning disable 436 +using System.Diagnostics; +class Program +{ + [Conditional("""")] Program() { } +}"; + var sourceB = +@"namespace System.Diagnostics +{ + public class ConditionalAttribute : Attribute + { + public ConditionalAttribute(string s) { } + } +}"; + var comp = CreateCompilation(new[] { sourceA, sourceB }); + comp.VerifyDiagnostics( + // (5,6): error CS0577: The Conditional attribute is not valid on 'Program.Program()' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + // [Conditional("")] Program() { } + Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"Conditional("""")").WithArguments("Program.Program()").WithLocation(5, 6)); + } + + [Fact] + public void CS0577ERR_ConditionalOnSpecialMethod_02() + { + var source = +@"using System.Diagnostics; +class Program +{ + [Conditional("""")] ~Program() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,6): error CS0577: The Conditional attribute is not valid on 'Program.~Program()' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + // [Conditional("")] ~Program() { } + Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"Conditional("""")").WithArguments("Program.~Program()").WithLocation(4, 6)); + } + + [Fact] + public void CS0577ERR_ConditionalOnSpecialMethod_03() + { + var source = +@"using System.Diagnostics; +class C +{ + [Conditional("""")] public static C operator !(C c) => c; +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,6): error CS0577: The Conditional attribute is not valid on 'C.operator !(C)' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation + // [Conditional("")] public static C operator !(C c) => c; + Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"Conditional("""")").WithArguments("C.operator !(C)").WithLocation(4, 6)); + } + + [Fact] + public void CS0577ERR_ConditionalOnSpecialMethod_04() { var text = @"interface I { @@ -10751,7 +10809,7 @@ void I.m() { } } "; CreateCompilation(text).VerifyDiagnostics( - // (8,6): error CS0577: The Conditional attribute is not valid on 'MyClass.I.m()' because it is a constructor, destructor, operator, or explicit interface implementation + // (8,6): error CS0577: The Conditional attribute is not valid on 'MyClass.I.m()' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation // [System.Diagnostics.Conditional("a")] // CS0577 Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"System.Diagnostics.Conditional(""a"")").WithArguments("MyClass.I.m()").WithLocation(8, 6)); } @@ -18349,7 +18407,8 @@ public override void Test(string s, ref int x) { } } "; // We no longer report a runtime ambiguous override (CS1957) because the compiler produces a methodimpl record to disambiguate. - CSharpCompilation comp = CreateCompilation(text); + CSharpCompilation comp = CreateCompilation(text, targetFramework: TargetFramework.StandardLatest); + Assert.Equal(RuntimeUtilities.IsCoreClrRuntime, comp.Assembly.RuntimeSupportsCovariantReturnsOfClasses); if (comp.Assembly.RuntimeSupportsDefaultInterfaceImplementation) { comp.VerifyDiagnostics( diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/LocationsTests.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/LocationsTests.cs index 3919883ebde7d..9355bfdd78b55 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/LocationsTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/LocationsTests.cs @@ -5,10 +5,10 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; -using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -71,6 +71,12 @@ private TextSpan GetSpanIn(SyntaxTree syntaxTree, string textToFind) return new TextSpan(index, textToFind.Length); } + private static IEnumerable InspectLineMapping(SyntaxTree tree) + { + var text = tree.GetText(); + return tree.GetLineMappings().Select(mapping => $"[|{text.GetSubText(text.Lines.GetTextSpan(mapping.Span))}|] -> {(mapping.IsHidden ? "" : mapping.MappedSpan)}"); + } + [ClrOnlyFact] public void TestGetSourceLocationInFile() { @@ -120,7 +126,7 @@ public void TestLineMapping1() string sampleProgram = @"using System; class X { #line 20 ""banana.cs"" -int x; +int x; int y; #line 44 int z; @@ -134,7 +140,6 @@ class X { #endif int a; }".NormalizeLineEndings(); - var resolver = new TestSourceResolver(); SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "goo.cs"); @@ -147,6 +152,15 @@ class X { AssertMappedSpanEqual(syntaxTree, "w;", "goo.cs", 8, 4, 8, 6, hasMappedPath: false); AssertMappedSpanEqual(syntaxTree, "q;\r\nin", "goo.cs", 10, 4, 11, 2, hasMappedPath: false); AssertMappedSpanEqual(syntaxTree, "a;", "goo.cs", 15, 4, 15, 6, hasMappedPath: false); + + AssertEx.Equal(new[] + { + "[|using System;\r\nclass X {\r\n|] -> : (0,0)-(1,11)", + "[|int x;\r\nint y;\r\n|] -> banana.cs: (19,0)-(20,8)", + "[|int z;\r\n|] -> banana.cs: (43,0)-(43,8)", + "[|int w;\r\n|] -> : (8,0)-(8,8)", + "[|int q;\r\nint f;\r\n#if false\r\n#line 17 \"d:\\twing.cs\"\r\n#endif\r\nint a;\r\n}|] -> " + }, InspectLineMapping(syntaxTree)); } [Fact] @@ -165,8 +179,6 @@ class X { #line 40 int v; }"; - var resolver = new TestSourceResolver(); - SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs"); AssertMappedSpanEqual(syntaxTree, "int x;", "c:\\goo.cs", 19, 0, 19, 6, hasMappedPath: false); @@ -183,8 +195,6 @@ public void TestLineMapping_NoSyntaxTreePath() #line 20 class X {} "; - var resolver = new TestSourceResolver(); - AssertMappedSpanEqual(SyntaxFactory.ParseSyntaxTree(sampleProgram, path: ""), "class X {}", "", 19, 0, 19, 10, hasMappedPath: false); AssertMappedSpanEqual(SyntaxFactory.ParseSyntaxTree(sampleProgram, path: " "), "class X {}", " ", 19, 0, 19, 10, hasMappedPath: false); } @@ -194,17 +204,25 @@ public void TestInvalidLineMapping() { string sampleProgram = @"using System; class X { - int q; + int q; #line 0 ""firstdirective"" - int r; + int r; #line 20 ""seconddirective"" - int s; -}"; + int s; +}".NormalizeLineEndings(); + SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "filename.cs"); AssertMappedSpanEqual(syntaxTree, "int q", "filename.cs", 2, 4, 2, 9, hasMappedPath: false); AssertMappedSpanEqual(syntaxTree, "int r", "filename.cs", 4, 4, 4, 9, hasMappedPath: false); // invalid #line args AssertMappedSpanEqual(syntaxTree, "int s", "seconddirective", 19, 4, 19, 9, hasMappedPath: true); + + AssertEx.Equal(new[] + { + "[|using System;\r\nclass X {\r\n int q;\r\n|] -> : (0,0)-(2,12)", + "[| int r;\r\n|] -> : (4,0)-(4,12)", + "[| int s;\r\n}|] -> seconddirective: (19,0)-(20,1)" + }, InspectLineMapping(syntaxTree)); } [Fact] @@ -212,7 +230,7 @@ public void TestLineMappingNoDirectives() { string sampleProgram = @"using System; class X { -int x; +int x; }"; SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs"); @@ -220,6 +238,57 @@ class X { AssertMappedSpanEqual(syntaxTree, "class X", "c:\\goo.cs", 1, 0, 1, 7, hasMappedPath: false); AssertMappedSpanEqual(syntaxTree, $"System;{Environment.NewLine}class X", "c:\\goo.cs", 0, 6, 1, 7, hasMappedPath: false); AssertMappedSpanEqual(syntaxTree, "x;", "c:\\goo.cs", 2, 4, 2, 6, hasMappedPath: false); + + Assert.Empty(InspectLineMapping(syntaxTree)); + } + + [Fact] + public void TestLineMappingFirstAndLastLineDirectives() + { + string sampleProgram = @"#line 20 +class X {} +#line 30".NormalizeLineEndings(); + var syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs"); + + AssertEx.Equal(new[] + { + "[|class X {}\r\n|] -> : (19,0)-(19,12)", + }, InspectLineMapping(syntaxTree)); + } + + [Fact] + public void TestLineMappingLastLineDirectiveFollowedByEmptyLine() + { + string sampleProgram = @"#line 30 +".NormalizeLineEndings(); + + var syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs"); + + AssertEx.Equal(new[] + { + "[||] -> : (29,0)-(29,0)", + }, InspectLineMapping(syntaxTree)); + } + + [Fact] + public void TestLineMappingConsecutiveDirectives() + { + string sampleProgram = +@"#line hidden +#line default +class C {} +#line 5 +#line 10 +class D {} +".NormalizeLineEndings(); + + var syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs"); + + AssertEx.Equal(new[] + { + "[|class C {}\r\n|] -> : (2,0)-(2,12)", + "[|class D {}\r\n|] -> : (9,0)-(10,0)", + }, InspectLineMapping(syntaxTree)); } [WorkItem(537005, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/537005")] diff --git a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs index fe56c3ae30b27..6180e30fec559 100644 --- a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs +++ b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs @@ -167,13 +167,13 @@ private static Syntax.InternalSyntax.AnonymousMethodExpressionSyntax GenerateAno => InternalSyntaxFactory.AnonymousMethodExpression(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.DelegateKeyword), null, GenerateBlock(), null); private static Syntax.InternalSyntax.SimpleLambdaExpressionSyntax GenerateSimpleLambdaExpression() - => InternalSyntaxFactory.SimpleLambdaExpression(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), GenerateParameter(), InternalSyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), null, null); + => InternalSyntaxFactory.SimpleLambdaExpression(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), GenerateParameter(), InternalSyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), null, null); private static Syntax.InternalSyntax.RefExpressionSyntax GenerateRefExpression() => InternalSyntaxFactory.RefExpression(InternalSyntaxFactory.Token(SyntaxKind.RefKeyword), GenerateIdentifierName()); private static Syntax.InternalSyntax.ParenthesizedLambdaExpressionSyntax GenerateParenthesizedLambdaExpression() - => InternalSyntaxFactory.ParenthesizedLambdaExpression(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), GenerateParameterList(), InternalSyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), null, null); + => InternalSyntaxFactory.ParenthesizedLambdaExpression(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), GenerateParameterList(), InternalSyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), null, null); private static Syntax.InternalSyntax.InitializerExpressionSyntax GenerateInitializerExpression() => InternalSyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression, InternalSyntaxFactory.Token(SyntaxKind.OpenBraceToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBraceToken)); @@ -407,13 +407,13 @@ private static Syntax.InternalSyntax.SwitchSectionSyntax GenerateSwitchSection() => InternalSyntaxFactory.SwitchSection(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList()); private static Syntax.InternalSyntax.CasePatternSwitchLabelSyntax GenerateCasePatternSwitchLabel() - => InternalSyntaxFactory.CasePatternSwitchLabel(InternalSyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateDiscardPattern(), null, InternalSyntaxFactory.Identifier("ColonToken")); + => InternalSyntaxFactory.CasePatternSwitchLabel(InternalSyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateDiscardPattern(), null, InternalSyntaxFactory.Token(SyntaxKind.ColonToken)); private static Syntax.InternalSyntax.CaseSwitchLabelSyntax GenerateCaseSwitchLabel() - => InternalSyntaxFactory.CaseSwitchLabel(InternalSyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateIdentifierName(), InternalSyntaxFactory.Identifier("ColonToken")); + => InternalSyntaxFactory.CaseSwitchLabel(InternalSyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateIdentifierName(), InternalSyntaxFactory.Token(SyntaxKind.ColonToken)); private static Syntax.InternalSyntax.DefaultSwitchLabelSyntax GenerateDefaultSwitchLabel() - => InternalSyntaxFactory.DefaultSwitchLabel(InternalSyntaxFactory.Token(SyntaxKind.DefaultKeyword), InternalSyntaxFactory.Identifier("ColonToken")); + => InternalSyntaxFactory.DefaultSwitchLabel(InternalSyntaxFactory.Token(SyntaxKind.DefaultKeyword), InternalSyntaxFactory.Token(SyntaxKind.ColonToken)); private static Syntax.InternalSyntax.SwitchExpressionSyntax GenerateSwitchExpression() => InternalSyntaxFactory.SwitchExpression(GenerateIdentifierName(), InternalSyntaxFactory.Token(SyntaxKind.SwitchKeyword), InternalSyntaxFactory.Token(SyntaxKind.OpenBraceToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBraceToken)); @@ -443,7 +443,7 @@ private static Syntax.InternalSyntax.ExternAliasDirectiveSyntax GenerateExternAl => InternalSyntaxFactory.ExternAliasDirective(InternalSyntaxFactory.Token(SyntaxKind.ExternKeyword), InternalSyntaxFactory.Token(SyntaxKind.AliasKeyword), InternalSyntaxFactory.Identifier("Identifier"), InternalSyntaxFactory.Token(SyntaxKind.SemicolonToken)); private static Syntax.InternalSyntax.UsingDirectiveSyntax GenerateUsingDirective() - => InternalSyntaxFactory.UsingDirective(InternalSyntaxFactory.Token(SyntaxKind.UsingKeyword), null, null, GenerateIdentifierName(), InternalSyntaxFactory.Token(SyntaxKind.SemicolonToken)); + => InternalSyntaxFactory.UsingDirective(null, InternalSyntaxFactory.Token(SyntaxKind.UsingKeyword), null, null, GenerateIdentifierName(), InternalSyntaxFactory.Token(SyntaxKind.SemicolonToken)); private static Syntax.InternalSyntax.NamespaceDeclarationSyntax GenerateNamespaceDeclaration() => InternalSyntaxFactory.NamespaceDeclaration(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.NamespaceKeyword), GenerateIdentifierName(), InternalSyntaxFactory.Token(SyntaxKind.OpenBraceToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBraceToken), null); @@ -482,7 +482,7 @@ private static Syntax.InternalSyntax.InterfaceDeclarationSyntax GenerateInterfac => InternalSyntaxFactory.InterfaceDeclaration(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.InterfaceKeyword), InternalSyntaxFactory.Identifier("Identifier"), null, null, new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.OpenBraceToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBraceToken), null); private static Syntax.InternalSyntax.RecordDeclarationSyntax GenerateRecordDeclaration() - => InternalSyntaxFactory.RecordDeclaration(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Identifier("Keyword"), InternalSyntaxFactory.Identifier("Identifier"), null, null, null, new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), null, new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), null, null); + => InternalSyntaxFactory.RecordDeclaration(SyntaxKind.RecordDeclaration, new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Identifier("Keyword"), null, InternalSyntaxFactory.Identifier("Identifier"), null, null, null, new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), null, new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), null, null); private static Syntax.InternalSyntax.EnumDeclarationSyntax GenerateEnumDeclaration() => InternalSyntaxFactory.EnumDeclaration(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.EnumKeyword), InternalSyntaxFactory.Identifier("Identifier"), null, InternalSyntaxFactory.Token(SyntaxKind.OpenBraceToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBraceToken), null); @@ -1317,6 +1317,7 @@ public void TestSimpleLambdaExpressionFactoryAndProperties() { var node = GenerateSimpleLambdaExpression(); + Assert.Equal(default, node.AttributeLists); Assert.Equal(default, node.Modifiers); Assert.NotNull(node.Parameter); Assert.Equal(SyntaxKind.EqualsGreaterThanToken, node.ArrowToken.Kind); @@ -1342,6 +1343,7 @@ public void TestParenthesizedLambdaExpressionFactoryAndProperties() { var node = GenerateParenthesizedLambdaExpression(); + Assert.Equal(default, node.AttributeLists); Assert.Equal(default, node.Modifiers); Assert.NotNull(node.ParameterList); Assert.Equal(SyntaxKind.EqualsGreaterThanToken, node.ArrowToken.Kind); @@ -2334,7 +2336,7 @@ public void TestCasePatternSwitchLabelFactoryAndProperties() Assert.Equal(SyntaxKind.CaseKeyword, node.Keyword.Kind); Assert.NotNull(node.Pattern); Assert.Null(node.WhenClause); - Assert.Equal(SyntaxKind.IdentifierToken, node.ColonToken.Kind); + Assert.Equal(SyntaxKind.ColonToken, node.ColonToken.Kind); AttachAndCheckDiagnostics(node); } @@ -2346,7 +2348,7 @@ public void TestCaseSwitchLabelFactoryAndProperties() Assert.Equal(SyntaxKind.CaseKeyword, node.Keyword.Kind); Assert.NotNull(node.Value); - Assert.Equal(SyntaxKind.IdentifierToken, node.ColonToken.Kind); + Assert.Equal(SyntaxKind.ColonToken, node.ColonToken.Kind); AttachAndCheckDiagnostics(node); } @@ -2357,7 +2359,7 @@ public void TestDefaultSwitchLabelFactoryAndProperties() var node = GenerateDefaultSwitchLabel(); Assert.Equal(SyntaxKind.DefaultKeyword, node.Keyword.Kind); - Assert.Equal(SyntaxKind.IdentifierToken, node.ColonToken.Kind); + Assert.Equal(SyntaxKind.ColonToken, node.ColonToken.Kind); AttachAndCheckDiagnostics(node); } @@ -2485,6 +2487,7 @@ public void TestUsingDirectiveFactoryAndProperties() { var node = GenerateUsingDirective(); + Assert.Null(node.GlobalKeyword); Assert.Equal(SyntaxKind.UsingKeyword, node.UsingKeyword.Kind); Assert.Null(node.StaticKeyword); Assert.Null(node.Alias); @@ -2675,6 +2678,7 @@ public void TestRecordDeclarationFactoryAndProperties() Assert.Equal(default, node.AttributeLists); Assert.Equal(default, node.Modifiers); Assert.Equal(SyntaxKind.IdentifierToken, node.Keyword.Kind); + Assert.Null(node.ClassOrStructKeyword); Assert.Equal(SyntaxKind.IdentifierToken, node.Identifier.Kind); Assert.Null(node.TypeParameterList); Assert.Null(node.ParameterList); @@ -9830,13 +9834,13 @@ private static AnonymousMethodExpressionSyntax GenerateAnonymousMethodExpression => SyntaxFactory.AnonymousMethodExpression(new SyntaxTokenList(), SyntaxFactory.Token(SyntaxKind.DelegateKeyword), default(ParameterListSyntax), GenerateBlock(), default(ExpressionSyntax)); private static SimpleLambdaExpressionSyntax GenerateSimpleLambdaExpression() - => SyntaxFactory.SimpleLambdaExpression(new SyntaxTokenList(), GenerateParameter(), SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default(BlockSyntax), default(ExpressionSyntax)); + => SyntaxFactory.SimpleLambdaExpression(new SyntaxList(), new SyntaxTokenList(), GenerateParameter(), SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default(BlockSyntax), default(ExpressionSyntax)); private static RefExpressionSyntax GenerateRefExpression() => SyntaxFactory.RefExpression(SyntaxFactory.Token(SyntaxKind.RefKeyword), GenerateIdentifierName()); private static ParenthesizedLambdaExpressionSyntax GenerateParenthesizedLambdaExpression() - => SyntaxFactory.ParenthesizedLambdaExpression(new SyntaxTokenList(), GenerateParameterList(), SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default(BlockSyntax), default(ExpressionSyntax)); + => SyntaxFactory.ParenthesizedLambdaExpression(new SyntaxList(), new SyntaxTokenList(), GenerateParameterList(), SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken), default(BlockSyntax), default(ExpressionSyntax)); private static InitializerExpressionSyntax GenerateInitializerExpression() => SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression, SyntaxFactory.Token(SyntaxKind.OpenBraceToken), new SeparatedSyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); @@ -10070,13 +10074,13 @@ private static SwitchSectionSyntax GenerateSwitchSection() => SyntaxFactory.SwitchSection(new SyntaxList(), new SyntaxList()); private static CasePatternSwitchLabelSyntax GenerateCasePatternSwitchLabel() - => SyntaxFactory.CasePatternSwitchLabel(SyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateDiscardPattern(), default(WhenClauseSyntax), SyntaxFactory.Identifier("ColonToken")); + => SyntaxFactory.CasePatternSwitchLabel(SyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateDiscardPattern(), default(WhenClauseSyntax), SyntaxFactory.Token(SyntaxKind.ColonToken)); private static CaseSwitchLabelSyntax GenerateCaseSwitchLabel() - => SyntaxFactory.CaseSwitchLabel(SyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateIdentifierName(), SyntaxFactory.Identifier("ColonToken")); + => SyntaxFactory.CaseSwitchLabel(SyntaxFactory.Token(SyntaxKind.CaseKeyword), GenerateIdentifierName(), SyntaxFactory.Token(SyntaxKind.ColonToken)); private static DefaultSwitchLabelSyntax GenerateDefaultSwitchLabel() - => SyntaxFactory.DefaultSwitchLabel(SyntaxFactory.Token(SyntaxKind.DefaultKeyword), SyntaxFactory.Identifier("ColonToken")); + => SyntaxFactory.DefaultSwitchLabel(SyntaxFactory.Token(SyntaxKind.DefaultKeyword), SyntaxFactory.Token(SyntaxKind.ColonToken)); private static SwitchExpressionSyntax GenerateSwitchExpression() => SyntaxFactory.SwitchExpression(GenerateIdentifierName(), SyntaxFactory.Token(SyntaxKind.SwitchKeyword), SyntaxFactory.Token(SyntaxKind.OpenBraceToken), new SeparatedSyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); @@ -10106,7 +10110,7 @@ private static ExternAliasDirectiveSyntax GenerateExternAliasDirective() => SyntaxFactory.ExternAliasDirective(SyntaxFactory.Token(SyntaxKind.ExternKeyword), SyntaxFactory.Token(SyntaxKind.AliasKeyword), SyntaxFactory.Identifier("Identifier"), SyntaxFactory.Token(SyntaxKind.SemicolonToken)); private static UsingDirectiveSyntax GenerateUsingDirective() - => SyntaxFactory.UsingDirective(SyntaxFactory.Token(SyntaxKind.UsingKeyword), default(SyntaxToken), default(NameEqualsSyntax), GenerateIdentifierName(), SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + => SyntaxFactory.UsingDirective(default(SyntaxToken), SyntaxFactory.Token(SyntaxKind.UsingKeyword), default(SyntaxToken), default(NameEqualsSyntax), GenerateIdentifierName(), SyntaxFactory.Token(SyntaxKind.SemicolonToken)); private static NamespaceDeclarationSyntax GenerateNamespaceDeclaration() => SyntaxFactory.NamespaceDeclaration(new SyntaxList(), new SyntaxTokenList(), SyntaxFactory.Token(SyntaxKind.NamespaceKeyword), GenerateIdentifierName(), SyntaxFactory.Token(SyntaxKind.OpenBraceToken), new SyntaxList(), new SyntaxList(), new SyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBraceToken), default(SyntaxToken)); @@ -10145,7 +10149,7 @@ private static InterfaceDeclarationSyntax GenerateInterfaceDeclaration() => SyntaxFactory.InterfaceDeclaration(new SyntaxList(), new SyntaxTokenList(), SyntaxFactory.Token(SyntaxKind.InterfaceKeyword), SyntaxFactory.Identifier("Identifier"), default(TypeParameterListSyntax), default(BaseListSyntax), new SyntaxList(), SyntaxFactory.Token(SyntaxKind.OpenBraceToken), new SyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBraceToken), default(SyntaxToken)); private static RecordDeclarationSyntax GenerateRecordDeclaration() - => SyntaxFactory.RecordDeclaration(new SyntaxList(), new SyntaxTokenList(), SyntaxFactory.Identifier("Keyword"), SyntaxFactory.Identifier("Identifier"), default(TypeParameterListSyntax), default(ParameterListSyntax), default(BaseListSyntax), new SyntaxList(), default(SyntaxToken), new SyntaxList(), default(SyntaxToken), default(SyntaxToken)); + => SyntaxFactory.RecordDeclaration(SyntaxKind.RecordDeclaration, new SyntaxList(), new SyntaxTokenList(), SyntaxFactory.Identifier("Keyword"), default(SyntaxToken), SyntaxFactory.Identifier("Identifier"), default(TypeParameterListSyntax), default(ParameterListSyntax), default(BaseListSyntax), new SyntaxList(), default(SyntaxToken), new SyntaxList(), default(SyntaxToken), default(SyntaxToken)); private static EnumDeclarationSyntax GenerateEnumDeclaration() => SyntaxFactory.EnumDeclaration(new SyntaxList(), new SyntaxTokenList(), SyntaxFactory.Token(SyntaxKind.EnumKeyword), SyntaxFactory.Identifier("Identifier"), default(BaseListSyntax), SyntaxFactory.Token(SyntaxKind.OpenBraceToken), new SeparatedSyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBraceToken), default(SyntaxToken)); @@ -10980,12 +10984,13 @@ public void TestSimpleLambdaExpressionFactoryAndProperties() { var node = GenerateSimpleLambdaExpression(); + Assert.Equal(default, node.AttributeLists); Assert.Equal(default, node.Modifiers); Assert.NotNull(node.Parameter); Assert.Equal(SyntaxKind.EqualsGreaterThanToken, node.ArrowToken.Kind()); Assert.Null(node.Block); Assert.Null(node.ExpressionBody); - var newNode = node.WithModifiers(node.Modifiers).WithParameter(node.Parameter).WithArrowToken(node.ArrowToken).WithBlock(node.Block).WithExpressionBody(node.ExpressionBody); + var newNode = node.WithAttributeLists(node.AttributeLists).WithModifiers(node.Modifiers).WithParameter(node.Parameter).WithArrowToken(node.ArrowToken).WithBlock(node.Block).WithExpressionBody(node.ExpressionBody); Assert.Equal(node, newNode); } @@ -11005,12 +11010,13 @@ public void TestParenthesizedLambdaExpressionFactoryAndProperties() { var node = GenerateParenthesizedLambdaExpression(); + Assert.Equal(default, node.AttributeLists); Assert.Equal(default, node.Modifiers); Assert.NotNull(node.ParameterList); Assert.Equal(SyntaxKind.EqualsGreaterThanToken, node.ArrowToken.Kind()); Assert.Null(node.Block); Assert.Null(node.ExpressionBody); - var newNode = node.WithModifiers(node.Modifiers).WithParameterList(node.ParameterList).WithArrowToken(node.ArrowToken).WithBlock(node.Block).WithExpressionBody(node.ExpressionBody); + var newNode = node.WithAttributeLists(node.AttributeLists).WithModifiers(node.Modifiers).WithParameterList(node.ParameterList).WithArrowToken(node.ArrowToken).WithBlock(node.Block).WithExpressionBody(node.ExpressionBody); Assert.Equal(node, newNode); } @@ -11997,7 +12003,7 @@ public void TestCasePatternSwitchLabelFactoryAndProperties() Assert.Equal(SyntaxKind.CaseKeyword, node.Keyword.Kind()); Assert.NotNull(node.Pattern); Assert.Null(node.WhenClause); - Assert.Equal(SyntaxKind.IdentifierToken, node.ColonToken.Kind()); + Assert.Equal(SyntaxKind.ColonToken, node.ColonToken.Kind()); var newNode = node.WithKeyword(node.Keyword).WithPattern(node.Pattern).WithWhenClause(node.WhenClause).WithColonToken(node.ColonToken); Assert.Equal(node, newNode); } @@ -12009,7 +12015,7 @@ public void TestCaseSwitchLabelFactoryAndProperties() Assert.Equal(SyntaxKind.CaseKeyword, node.Keyword.Kind()); Assert.NotNull(node.Value); - Assert.Equal(SyntaxKind.IdentifierToken, node.ColonToken.Kind()); + Assert.Equal(SyntaxKind.ColonToken, node.ColonToken.Kind()); var newNode = node.WithKeyword(node.Keyword).WithValue(node.Value).WithColonToken(node.ColonToken); Assert.Equal(node, newNode); } @@ -12020,7 +12026,7 @@ public void TestDefaultSwitchLabelFactoryAndProperties() var node = GenerateDefaultSwitchLabel(); Assert.Equal(SyntaxKind.DefaultKeyword, node.Keyword.Kind()); - Assert.Equal(SyntaxKind.IdentifierToken, node.ColonToken.Kind()); + Assert.Equal(SyntaxKind.ColonToken, node.ColonToken.Kind()); var newNode = node.WithKeyword(node.Keyword).WithColonToken(node.ColonToken); Assert.Equal(node, newNode); } @@ -12148,12 +12154,13 @@ public void TestUsingDirectiveFactoryAndProperties() { var node = GenerateUsingDirective(); + Assert.Equal(SyntaxKind.None, node.GlobalKeyword.Kind()); Assert.Equal(SyntaxKind.UsingKeyword, node.UsingKeyword.Kind()); Assert.Equal(SyntaxKind.None, node.StaticKeyword.Kind()); Assert.Null(node.Alias); Assert.NotNull(node.Name); Assert.Equal(SyntaxKind.SemicolonToken, node.SemicolonToken.Kind()); - var newNode = node.WithUsingKeyword(node.UsingKeyword).WithStaticKeyword(node.StaticKeyword).WithAlias(node.Alias).WithName(node.Name).WithSemicolonToken(node.SemicolonToken); + var newNode = node.WithGlobalKeyword(node.GlobalKeyword).WithUsingKeyword(node.UsingKeyword).WithStaticKeyword(node.StaticKeyword).WithAlias(node.Alias).WithName(node.Name).WithSemicolonToken(node.SemicolonToken); Assert.Equal(node, newNode); } @@ -12338,6 +12345,7 @@ public void TestRecordDeclarationFactoryAndProperties() Assert.Equal(default, node.AttributeLists); Assert.Equal(default, node.Modifiers); Assert.Equal(SyntaxKind.IdentifierToken, node.Keyword.Kind()); + Assert.Equal(SyntaxKind.None, node.ClassOrStructKeyword.Kind()); Assert.Equal(SyntaxKind.IdentifierToken, node.Identifier.Kind()); Assert.Null(node.TypeParameterList); Assert.Null(node.ParameterList); @@ -12347,7 +12355,7 @@ public void TestRecordDeclarationFactoryAndProperties() Assert.Equal(default, node.Members); Assert.Equal(SyntaxKind.None, node.CloseBraceToken.Kind()); Assert.Equal(SyntaxKind.None, node.SemicolonToken.Kind()); - var newNode = node.WithAttributeLists(node.AttributeLists).WithModifiers(node.Modifiers).WithKeyword(node.Keyword).WithIdentifier(node.Identifier).WithTypeParameterList(node.TypeParameterList).WithParameterList(node.ParameterList).WithBaseList(node.BaseList).WithConstraintClauses(node.ConstraintClauses).WithOpenBraceToken(node.OpenBraceToken).WithMembers(node.Members).WithCloseBraceToken(node.CloseBraceToken).WithSemicolonToken(node.SemicolonToken); + var newNode = node.WithAttributeLists(node.AttributeLists).WithModifiers(node.Modifiers).WithKeyword(node.Keyword).WithClassOrStructKeyword(node.ClassOrStructKeyword).WithIdentifier(node.Identifier).WithTypeParameterList(node.TypeParameterList).WithParameterList(node.ParameterList).WithBaseList(node.BaseList).WithConstraintClauses(node.ConstraintClauses).WithOpenBraceToken(node.OpenBraceToken).WithMembers(node.Members).WithCloseBraceToken(node.CloseBraceToken).WithSemicolonToken(node.SemicolonToken); Assert.Equal(node, newNode); } diff --git a/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs index edd8a4d7cddfa..e6d26e31e0e5f 100644 --- a/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs @@ -3019,6 +3019,109 @@ void M() WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); } + [Theory] + [InlineData("[Attr] () => { }")] + [InlineData("[Attr] x => x")] + [InlineData("([Attr] x) => x")] + [InlineData("([Attr] int x) => x")] + public void Lambda_EditAttributeList(string lambdaExpression) + { + var source = +$@"class Program +{{ + static void Main() + {{ + F({lambdaExpression}); + }} +}}"; + var tree = SyntaxFactory.ParseSyntaxTree(source); + var text = tree.GetText(); + var substring = "Attr"; + var span = new TextSpan(source.IndexOf(substring) + substring.Length, 0); + var change = new TextChange(span, "1, Attr2"); + text = text.WithChanges(change); + tree = tree.WithChangedText(text); + var fullTree = SyntaxFactory.ParseSyntaxTree(text.ToString()); + WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); + } + + [Theory] + [InlineData("() => { }", "() => { }")] + [InlineData("x => x", "x => x")] + [InlineData("(x) => x", "x) => x")] + [InlineData("(int x) => x", "int x) => x")] + public void Lambda_AddFirstAttributeList(string lambdaExpression, string substring) + { + var source = +$@"class Program +{{ + static void Main() + {{ + F({lambdaExpression}); + }} +}}"; + var tree = SyntaxFactory.ParseSyntaxTree(source); + var text = tree.GetText(); + var span = new TextSpan(source.IndexOf(substring), 0); + var change = new TextChange(span, "[Attr]"); + text = text.WithChanges(change); + tree = tree.WithChangedText(text); + var fullTree = SyntaxFactory.ParseSyntaxTree(text.ToString()); + WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); + } + + [Theory] + [InlineData("[Attr1] () => { }")] + [InlineData("[Attr1] x => x")] + [InlineData("([Attr1] x) => x")] + [InlineData("([Attr1] int x) => x")] + public void Lambda_AddSecondAttributeList(string lambdaExpression) + { + var source = +$@"class Program +{{ + static void Main() + {{ + F({lambdaExpression}); + }} +}}"; + var tree = SyntaxFactory.ParseSyntaxTree(source); + var text = tree.GetText(); + var substring = @"[Attr1]"; + var span = new TextSpan(source.IndexOf(substring) + substring.Length, 0); + var change = new TextChange(span, " [Attr2]"); + text = text.WithChanges(change); + tree = tree.WithChangedText(text); + var fullTree = SyntaxFactory.ParseSyntaxTree(text.ToString()); + WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); + } + + [Theory] + [InlineData("[Attr] () => { }")] + [InlineData("[Attr] x => x")] + [InlineData("([Attr] x) => x")] + [InlineData("([Attr] int x) => x")] + public void Lambda_RemoveAttributeList(string lambdaExpression) + { + var source = +$@"class Program +{{ + static void Main() + {{ + F({lambdaExpression}); + }} +}}"; + var tree = SyntaxFactory.ParseSyntaxTree(source); + var text = tree.GetText(); + var substring = "[Attr] "; + var span = new TextSpan(source.IndexOf(substring) + substring.Length, 0); + var change = new TextChange(span, ""); + text = text.WithChanges(change); + tree = tree.WithChangedText(text); + var fullTree = SyntaxFactory.ParseSyntaxTree(text.ToString()); + WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); + } + [Fact] public void EditGlobalStatementWithAttributes_01() { diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs new file mode 100644 index 0000000000000..c177860f9044f --- /dev/null +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs @@ -0,0 +1,2002 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class LambdaAttributeParsingTests : ParsingTests + { + public LambdaAttributeParsingTests(ITestOutputHelper output) : base(output) { } + + protected override SyntaxTree ParseTree(string text, CSharpParseOptions? options) + { + return SyntaxFactory.ParseSyntaxTree(text, options: options); + } + + protected override CSharpSyntaxNode ParseNode(string text, CSharpParseOptions? options) + { + return SyntaxFactory.ParseExpression(text, options: options); + } + + [Fact] + public void LambdaAttribute_01() + { + string source = "[A] x => x"; + UsingExpression(source, TestOptions.Regular9); + verify(); + + UsingExpression(source, TestOptions.RegularPreview); + verify(); + + void verify() + { + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + EOF(); + } + } + + [Fact] + public void LambdaAttribute_02() + { + string source = "[A, B] () => { }"; + UsingExpression(source, TestOptions.Regular9); + verify(); + + UsingExpression(source, TestOptions.RegularPreview); + verify(); + + void verify() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + public void LambdaAttribute_03() + { + string source = "[A][B] (object x) => x"; + UsingExpression(source, TestOptions.Regular9); + verify(); + + UsingExpression(source, TestOptions.RegularPreview); + verify(); + + void verify() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.ObjectKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + EOF(); + } + } + + [Fact] + public void LambdaAttribute_04() + { + string source = "([A] object x) => x"; + UsingExpression(source, TestOptions.Regular9); + verify(); + + UsingExpression(source, TestOptions.RegularPreview); + verify(); + + void verify() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.ObjectKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + EOF(); + } + } + + [Fact] + public void LambdaAttribute_05() + { + string source = "[A] (ref x) => x"; + UsingExpression(source, TestOptions.RegularPreview, + // (1,11): error CS1001: Identifier expected + // [A] (ref x) => x + Diagnostic(ErrorCode.ERR_IdentifierExpected, ")").WithLocation(1, 11)); + + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + EOF(); + } + + [Fact] + public void LambdaAttribute_06() + { + string source = "[A] ref x => x"; + UsingExpression(source, TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token 'ref' + // [A] ref x => x + Diagnostic(ErrorCode.ERR_UnexpectedToken, "[A]").WithArguments("ref").WithLocation(1, 1), + // (1,1): error CS1525: Invalid expression term '[' + // [A] ref x => x + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(1, 1)); + + N(SyntaxKind.ElementAccessExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + EOF(); + } + + [Fact] + public void LambdaAttribute_07() + { + string source = "[A] in x => x"; + UsingExpression(source, TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token 'in' + // [A] in x => x + Diagnostic(ErrorCode.ERR_UnexpectedToken, "[A]").WithArguments("in").WithLocation(1, 1), + // (1,1): error CS1525: Invalid expression term '[' + // [A] in x => x + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(1, 1)); + + N(SyntaxKind.ElementAccessExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + EOF(); + } + + // [A] x => x + private void LambdaExpression_01(params SyntaxKind[] modifiers) + { + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + foreach (var modifier in modifiers) + { + N(modifier); + } + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + + // [A] () => { } + private void LambdaExpression_02(params SyntaxKind[] modifiers) + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + foreach (var modifier in modifiers) + { + N(modifier); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // [A] (x) => { } + private void LambdaExpression_03(params SyntaxKind[] modifiers) + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + foreach (var modifier in modifiers) + { + N(modifier); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // [A] (object x) => { } + private void LambdaExpression_04(params SyntaxKind[] modifiers) + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + foreach (var modifier in modifiers) + { + N(modifier); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.ObjectKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // [A(B)]() => { } + private void LambdaExpression_05() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.AttributeArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.AttributeArgument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // [A, B]() => { } + private void LambdaExpression_06() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // [A][B]() => { } + private void LambdaExpression_07() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // [A] (ref int x) => ref x + private void LambdaExpression_08() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.RefExpression); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + + // [return: A] static x => x + private void LambdaExpression_09() + { + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.AttributeTargetSpecifier); + { + N(SyntaxKind.ReturnKeyword); + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + + // ([A] int x) => x + private void LambdaExpression_10() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + + // ([A] out int x) => { } + private void LambdaExpression_11() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.OutKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + // ([A] ref int x) => ref x + private void LambdaExpression_12() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.RefKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.RefExpression); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + + // ([A] x) => x + private void LambdaExpression_13() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + + // (int x, [A] int y) => x + private void LambdaExpression_14() + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + + public static IEnumerable GetLambdaTestData() + { + yield return getData("[A] x => x", tests => tests.LambdaExpression_01()); + yield return getData("[A] async x => x", tests => tests.LambdaExpression_01(SyntaxKind.AsyncKeyword)); + yield return getData("[A] static x => x", tests => tests.LambdaExpression_01(SyntaxKind.StaticKeyword)); + yield return getData("[A] async static x => x", tests => tests.LambdaExpression_01(SyntaxKind.AsyncKeyword, SyntaxKind.StaticKeyword)); + yield return getData("[A] static async x => x", tests => tests.LambdaExpression_01(SyntaxKind.StaticKeyword, SyntaxKind.AsyncKeyword)); + + yield return getData("[A]() => { }", tests => tests.LambdaExpression_02()); + yield return getData("[A]async () => { }", tests => tests.LambdaExpression_02(SyntaxKind.AsyncKeyword)); + yield return getData("[A]static () => { }", tests => tests.LambdaExpression_02(SyntaxKind.StaticKeyword)); + yield return getData("[A]async static () => { }", tests => tests.LambdaExpression_02(SyntaxKind.AsyncKeyword, SyntaxKind.StaticKeyword)); + yield return getData("[A]static async () => { }", tests => tests.LambdaExpression_02(SyntaxKind.StaticKeyword, SyntaxKind.AsyncKeyword)); + + yield return getData("[A] (x) => { }", tests => tests.LambdaExpression_03()); + yield return getData("[A] async (x) => { }", tests => tests.LambdaExpression_03(SyntaxKind.AsyncKeyword)); + yield return getData("[A] static (x) => { }", tests => tests.LambdaExpression_03(SyntaxKind.StaticKeyword)); + yield return getData("[A] async static (x) => { }", tests => tests.LambdaExpression_03(SyntaxKind.AsyncKeyword, SyntaxKind.StaticKeyword)); + yield return getData("[A] static async (x) => { }", tests => tests.LambdaExpression_03(SyntaxKind.StaticKeyword, SyntaxKind.AsyncKeyword)); + + yield return getData("[A] (object x) => { }", tests => tests.LambdaExpression_04()); + yield return getData("[A] async (object x) => { }", tests => tests.LambdaExpression_04(SyntaxKind.AsyncKeyword)); + yield return getData("[A] static (object x) => { }", tests => tests.LambdaExpression_04(SyntaxKind.StaticKeyword)); + yield return getData("[A] async static (object x) => { }", tests => tests.LambdaExpression_04(SyntaxKind.AsyncKeyword, SyntaxKind.StaticKeyword)); + yield return getData("[A] static async (object x) => { }", tests => tests.LambdaExpression_04(SyntaxKind.StaticKeyword, SyntaxKind.AsyncKeyword)); + + yield return getData("[A(B)]() => { }", tests => tests.LambdaExpression_05()); + yield return getData("[A, B]() => { }", tests => tests.LambdaExpression_06()); + yield return getData("[A][B]() => { }", tests => tests.LambdaExpression_07()); + yield return getData("[A] (ref int x) => ref x", tests => tests.LambdaExpression_08()); + + yield return getData("[return: A] static x => x", tests => tests.LambdaExpression_09()); + yield return getData("([A] int x) => x", tests => tests.LambdaExpression_10()); + yield return getData("([A] out int x) => { }", tests => tests.LambdaExpression_11()); + yield return getData("([A] ref int x) => ref x", tests => tests.LambdaExpression_12()); + yield return getData("([A] x) => x", tests => tests.LambdaExpression_13()); + yield return getData("(int x, [A] int y) => x", tests => tests.LambdaExpression_14()); + + static object[] getData(string expr, Action action) => new object[] { expr, action }; + } + + [Theory] + [MemberData(nameof(GetLambdaTestData))] + public void Assignment(string exprLambda, Action verifyLambda) + { + UsingExpression($"f = {exprLambda}", TestOptions.RegularPreview); + + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.EqualsToken); + verifyLambda(this); + } + EOF(); + } + + [Theory] + [MemberData(nameof(GetLambdaTestData))] + public void Argument_01(string exprLambda, Action verifyLambda) + { + UsingExpression($"F({exprLambda})", TestOptions.RegularPreview); + + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "F"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + verifyLambda(this); + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + [Theory] + [MemberData(nameof(GetLambdaTestData))] + public void Argument_02(string exprLambda, Action verifyLambda) + { + UsingExpression($"F(x, {exprLambda})", TestOptions.RegularPreview); + + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "F"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + verifyLambda(this); + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + // Parenthesized lambda with attribute is similar to lambda with parameter attribute. + [Fact] + public void ParenthesizedLambdaWithAttribute() + { + UsingExpression("f = ([A] x => x)", TestOptions.RegularPreview); + + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + // Lambda with attribute in collection initializer is similar to dictionary initializer. + [Fact] + public void CollectionInitializer_01() + { + UsingExpression("new B { [A] x => y }", TestOptions.RegularPreview, + // (1,13): error CS1003: Syntax error, '=' expected + // new B { [A] x => y } + Diagnostic(ErrorCode.ERR_SyntaxError, "x").WithArguments("=", "").WithLocation(1, 13)); + + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.ImplicitElementAccess); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + M(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + // Lambda with attribute in collection initializer is similar to dictionary initializer. + [Fact] + public void CollectionInitializer_02() + { + UsingExpression("new B { ([A] x => y) }", TestOptions.RegularPreview); + + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.CollectionInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact] + public void PostfixOperator() + { + UsingExpression("[A] () => { } ++", TestOptions.RegularPreview); + + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.PlusPlusToken); + } + EOF(); + } + + [Fact] + public void PrefixOperator() + { + UsingExpression("-- [A] () => { }", TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token '=>' + // -- [A] () => { } + Diagnostic(ErrorCode.ERR_UnexpectedToken, "-- [A] ()").WithArguments("=>").WithLocation(1, 1), + // (1,4): error CS1525: Invalid expression term '[' + // -- [A] () => { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(1, 4)); + + N(SyntaxKind.PreDecrementExpression); + { + N(SyntaxKind.MinusMinusToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.ElementAccessExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact] + public void UnaryOperator() + { + UsingExpression("! [A] () => { }", TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token '=>' + // ! [A] () => { } + Diagnostic(ErrorCode.ERR_UnexpectedToken, "! [A] ()").WithArguments("=>").WithLocation(1, 1), + // (1,3): error CS1525: Invalid expression term '[' + // ! [A] () => { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(1, 3)); + + N(SyntaxKind.LogicalNotExpression); + { + N(SyntaxKind.ExclamationToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.ElementAccessExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact] + public void Cast() + { + UsingExpression("(F) [A] () => { }", TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token '=>' + // (F) [A] () => { } + Diagnostic(ErrorCode.ERR_UnexpectedToken, "(F) [A] ()").WithArguments("=>").WithLocation(1, 1)); + + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "F"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + [Fact] + public void BinaryOperator_01() + { + UsingExpression("[A] () => { } + y", TestOptions.RegularPreview, + // (1,15): warning CS8848: Operator '+' cannot be used here due to precedence. Use parentheses to disambiguate. + // [A] () => { } + y + Diagnostic(ErrorCode.WRN_PrecedenceInversion, "+").WithArguments("+").WithLocation(1, 15)); + + N(SyntaxKind.AddExpression); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.PlusToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + EOF(); + } + + [Fact] + public void BinaryOperator_02() + { + UsingExpression("x * [A] () => { }", TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token '=>' + // x * [A] () => { } + Diagnostic(ErrorCode.ERR_UnexpectedToken, "x * [A] ()").WithArguments("=>").WithLocation(1, 1), + // (1,5): error CS1525: Invalid expression term '[' + // x * [A] () => { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(1, 5)); + + N(SyntaxKind.MultiplyExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.AsteriskToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.ElementAccessExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact] + public void Is() + { + UsingExpression("[A] () => { } is E", TestOptions.RegularPreview, + // (1,15): warning CS8848: Operator 'is' cannot be used here due to precedence. Use parentheses to disambiguate. + // [A] () => { } is E + Diagnostic(ErrorCode.WRN_PrecedenceInversion, "is").WithArguments("is").WithLocation(1, 15)); + + N(SyntaxKind.IsExpression); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + } + EOF(); + } + + [Fact] + public void As() + { + UsingExpression("[A] () => x as E", TestOptions.RegularPreview); + + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.AsExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.AsKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + } + } + EOF(); + } + + [Fact] + public void ConditionalExpression_01() + { + UsingExpression("x ? [A] () => { } : z", TestOptions.RegularPreview, + // (1,1): error CS1073: Unexpected token '=>' + // x ? [A] () => { } : z + Diagnostic(ErrorCode.ERR_UnexpectedToken, "x ? [A] ()").WithArguments("=>").WithLocation(1, 1)); + + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact] + public void ConditionalExpression_02() + { + UsingExpression("x ? y : [A] () => { }", TestOptions.RegularPreview); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + EOF(); + } + + // Lambda with attribute in conditional expression is similar to conditional element access. + [Fact] + public void ConditionalExpression_03() + { + UsingExpression("x ? ([A] () => { }) : y", TestOptions.RegularPreview); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + EOF(); + } + + [Fact] + public void SwitchExpression_01() + { + UsingExpression("[A] () => { } switch { }", TestOptions.RegularPreview, + // (1,15): warning CS8848: Operator 'switch' cannot be used here due to precedence. Use parentheses to disambiguate. + // [A] () => { } switch { } + Diagnostic(ErrorCode.WRN_PrecedenceInversion, "switch").WithArguments("switch").WithLocation(1, 15)); + + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + EOF(); + } + + [Fact] + public void SwitchExpression_02() + { + UsingExpression("x switch { y => [A] () => { }, _ => [A] () => z }", TestOptions.RegularPreview); + + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.DiscardPattern); + { + N(SyntaxKind.UnderscoreToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "z"); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + EOF(); + } + + [Fact] + public void Tuple_01() + { + UsingExpression("([A] () => { }, y)", TestOptions.RegularPreview); + + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.CloseParenToken); + } + EOF(); + } + + [Fact] + public void Tuple_02() + { + UsingExpression("(x, [A] () => { })", TestOptions.RegularPreview); + + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + EOF(); + } + + [Fact] + public void Range_01() + { + UsingExpression("s[[A] x => x..]", TestOptions.RegularPreview); + + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "s"); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.AttributeList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.DotDotToken); + } + } + } + N(SyntaxKind.CloseBracketToken); + } + } + EOF(); + } + + [Fact] + public void Range_02() + { + UsingExpression("s[..[A] () => { }]", TestOptions.RegularPreview, + // (1,5): error CS1525: Invalid expression term '[' + // s[..[A] () => { }] + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(1, 5), + // (1,12): error CS1003: Syntax error, ',' expected + // s[..[A] () => { }] + Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(1, 12)); + + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "s"); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.ElementAccessExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + } + N(SyntaxKind.CloseBracketToken); + } + } + EOF(); + } + } +} diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs index 8f4e9e508e6c5..6d651a3a88adc 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs @@ -3293,6 +3293,9 @@ static void Main() "; CreateCompilation(test).VerifyDiagnostics( + // (8,22): warning CS0219: The variable 'message' is assigned but its value is never used + // const string message = "the parameter is obsolete"; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "message").WithArguments("message").WithLocation(8, 22), // (10,13): error CS7014: Attributes are not valid in this context. // [ObsoleteAttribute(message)] [ObsoleteAttribute(message)] int x, Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[ObsoleteAttribute(message)]").WithLocation(10, 13), @@ -3301,10 +3304,7 @@ static void Main() Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[ObsoleteAttribute(message)]").WithLocation(10, 42), // (11,13): error CS7014: Attributes are not valid in this context. // [ObsoleteAttribute(message)] int y - Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[ObsoleteAttribute(message)]").WithLocation(11, 13), - // (8,22): warning CS0219: The variable 'message' is assigned but its value is never used - // const string message = "the parameter is obsolete"; - Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "message").WithArguments("message").WithLocation(8, 22)); + Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[ObsoleteAttribute(message)]").WithLocation(11, 13)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs index 2dc801bb5dcba..8a41b481ea4bf 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs @@ -2222,18 +2222,7 @@ public void Base_01( : B" + (withBaseArguments ? "(X, Y)" : "") + @" " + (withBody ? "{ }" : ";"); - if (!withParameters && withBaseArguments) - { - UsingTree(text, - // (2,4): error CS8861: Unexpected argument list. - // : B(X, Y) - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(2, 4) - ); - } - else - { - UsingTree(text); - } + UsingTree(text); N(SyntaxKind.CompilationUnit); { @@ -2550,9 +2539,6 @@ public void Base_05() { var text = "interface C : B(X, Y);"; UsingTree(text, - // (1,16): error CS8861: Unexpected argument list. - // interface C : B(X, Y); - Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(1, 16), // (1,22): error CS1003: Syntax error, ',' expected // interface C : B(X, Y); Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",", ";").WithLocation(1, 22), @@ -2609,10 +2595,1399 @@ public void Base_05() EOF(); } - [Fact, WorkItem(51590, "https://github.com/dotnet/roslyn/issues/51590")] - public void ParseIncompleteRecordSyntax() + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_RecordNamedStruct() { - ParseIncompleteSyntax("public sealed record C() { }"); + var text = "record struct(int X, int Y);"; + UsingTree(text, options: TestOptions.Regular9, + // (1,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct(int X, int Y); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(1, 8), + // (1,14): error CS1001: Identifier expected + // record struct(int X, int Y); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "(").WithLocation(1, 14) + ); + verify(); + + UsingTree(text, options: TestOptions.RegularPreview, + // (1,14): error CS1001: Identifier expected + // record struct(int X, int Y); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "(").WithLocation(1, 14) + ); + verify(); + + void verify() + { + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "Y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing() + { + var text = "record struct C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview); + + verifyParsedAsRecord(); + + UsingTree(text, options: TestOptions.Regular9, + // (1,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record struct C(int X, int Y); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(1, 8) + ); + + verifyParsedAsRecord(); + + UsingTree(text, options: TestOptions.Regular8, + // (1,1): error CS0116: A namespace cannot directly contain members such as fields or methods + // record struct C(int X, int Y); + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "record").WithLocation(1, 1), + // (1,16): error CS1514: { expected + // record struct C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(1, 16), + // (1,16): error CS1513: } expected + // record struct C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(1, 16), + // (1,16): error CS8400: Feature 'top-level statements' is not available in C# 8.0. Please use language version 9.0 or greater. + // record struct C(int X, int Y); + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "(int X, int Y);").WithArguments("top-level statements", "9.0").WithLocation(1, 16), + // (1,16): error CS8803: Top-level statements must precede namespace and type declarations. + // record struct C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "(int X, int Y);").WithLocation(1, 16) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + + void verifyParsedAsRecord() + { + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "Y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_WithBody() + { + var text = "record struct C(int X, int Y) { }"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "Y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordClassParsing() + { + var text = "record class C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview); + + verifyParsedAsRecord(); + + UsingTree(text, options: TestOptions.Regular9, + // (1,8): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // record class C(int X, int Y); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "class").WithArguments("record structs").WithLocation(1, 8) + ); + + verifyParsedAsRecord(); + + UsingTree(text, options: TestOptions.Regular8, + // (1,1): error CS0116: A namespace cannot directly contain members such as fields or methods + // record class C(int X, int Y); + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "record").WithLocation(1, 1), + // (1,15): error CS1514: { expected + // record class C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(1, 15), + // (1,15): error CS1513: } expected + // record class C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(1, 15), + // (1,15): error CS8400: Feature 'top-level statements' is not available in C# 8.0. Please use language version 9.0 or greater. + // record class C(int X, int Y); + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "(int X, int Y);").WithArguments("top-level statements", "9.0").WithLocation(1, 15), + // (1,15): error CS8803: Top-level statements must precede namespace and type declarations. + // record class C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "(int X, int Y);").WithLocation(1, 15) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + } + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + + void verifyParsedAsRecord() + { + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "Y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordInterfaceParsing() + { + var text = "record interface C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,8): error CS1001: Identifier expected + // record interface C(int X, int Y); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "interface").WithLocation(1, 8), + // (1,8): error CS1514: { expected + // record interface C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "interface").WithLocation(1, 8), + // (1,8): error CS1513: } expected + // record interface C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "interface").WithLocation(1, 8), + // (1,19): error CS1514: { expected + // record interface C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(1, 19), + // (1,19): error CS1513: } expected + // record interface C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(1, 19), + // (1,19): error CS8803: Top-level statements must precede namespace and type declarations. + // record interface C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "(int X, int Y);").WithLocation(1, 19) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + M(SyntaxKind.IdentifierToken); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.InterfaceDeclaration); + { + N(SyntaxKind.InterfaceKeyword); + N(SyntaxKind.IdentifierToken, "C"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordRecordParsing() + { + var text = "record record C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,15): error CS1514: { expected + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "C").WithLocation(1, 15), + // (1,15): error CS1513: } expected + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "C").WithLocation(1, 15), + // (1,15): error CS8803: Top-level statements must precede namespace and type declarations. + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "C(int X, int Y);").WithLocation(1, 15), + // (1,17): error CS1525: Invalid expression term 'int' + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 17), + // (1,21): error CS1003: Syntax error, ',' expected + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "X").WithArguments(",", "").WithLocation(1, 21), + // (1,24): error CS1525: Invalid expression term 'int' + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 24), + // (1,28): error CS1003: Syntax error, ',' expected + // record record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "Y").WithArguments(",", "").WithLocation(1, 28) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "record"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_WrongOrder() + { + var text = "struct record C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,15): error CS1514: { expected + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "C").WithLocation(1, 15), + // (1,15): error CS1513: } expected + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "C").WithLocation(1, 15), + // (1,15): error CS8803: Top-level statements must precede namespace and type declarations. + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "C(int X, int Y);").WithLocation(1, 15), + // (1,17): error CS1525: Invalid expression term 'int' + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 17), + // (1,21): error CS1003: Syntax error, ',' expected + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "X").WithArguments(",", "").WithLocation(1, 21), + // (1,24): error CS1525: Invalid expression term 'int' + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 24), + // (1,28): error CS1003: Syntax error, ',' expected + // struct record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "Y").WithArguments(",", "").WithLocation(1, 28) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "record"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordClassParsing_WrongOrder() + { + var text = "class record C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,14): error CS1514: { expected + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "C").WithLocation(1, 14), + // (1,14): error CS1513: } expected + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "C").WithLocation(1, 14), + // (1,14): error CS8803: Top-level statements must precede namespace and type declarations. + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "C(int X, int Y);").WithLocation(1, 14), + // (1,16): error CS1525: Invalid expression term 'int' + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 16), + // (1,20): error CS1003: Syntax error, ',' expected + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "X").WithArguments(",", "").WithLocation(1, 20), + // (1,23): error CS1525: Invalid expression term 'int' + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 23), + // (1,27): error CS1003: Syntax error, ',' expected + // class record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "Y").WithArguments(",", "").WithLocation(1, 27) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "record"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordInterfaceParsing_WrongOrder() + { + var text = "interface record C(int X, int Y);"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,18): error CS1514: { expected + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_LbraceExpected, "C").WithLocation(1, 18), + // (1,18): error CS1513: } expected + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_RbraceExpected, "C").WithLocation(1, 18), + // (1,18): error CS8803: Top-level statements must precede namespace and type declarations. + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "C(int X, int Y);").WithLocation(1, 18), + // (1,20): error CS1525: Invalid expression term 'int' + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 20), + // (1,24): error CS1003: Syntax error, ',' expected + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "X").WithArguments(",", "").WithLocation(1, 24), + // (1,27): error CS1525: Invalid expression term 'int' + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(1, 27), + // (1,31): error CS1003: Syntax error, ',' expected + // interface record C(int X, int Y); + Diagnostic(ErrorCode.ERR_SyntaxError, "Y").WithArguments(",", "").WithLocation(1, 31) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.InterfaceDeclaration); + { + N(SyntaxKind.InterfaceKeyword); + N(SyntaxKind.IdentifierToken, "record"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Partial() + { + var text = "partial record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordClassParsing_Partial() + { + var text = "partial record class S;"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordParsing_Partial() + { + var text = "partial record S;"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Partial_WithParameterList() + { + var text = "partial record struct S(int X);"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Partial_WithParameterList_AndMembers() + { + var text = "partial record struct S(int X) { }"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Readonly() + { + var text = "readonly record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_ReadonlyPartial() + { + var text = "readonly partial record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.PartialKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_PartialReadonly() + { + var text = "partial readonly record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,9): error CS1585: Member modifier 'readonly' must precede the member type and name + // partial readonly record struct S; + Diagnostic(ErrorCode.ERR_BadModifierLocation, "readonly").WithArguments("readonly").WithLocation(1, 9) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "partial"); + } + } + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_New() + { + var text = "new record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,12): error CS1526: A new expression requires an argument list or (), [], or {} after type + // new record struct S; + Diagnostic(ErrorCode.ERR_BadNewExpr, "struct").WithLocation(1, 12), + // (1,12): error CS1002: ; expected + // new record struct S; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 12), + // (1,20): error CS1514: { expected + // new record struct S; + Diagnostic(ErrorCode.ERR_LbraceExpected, ";").WithLocation(1, 20), + // (1,20): error CS1513: } expected + // new record struct S; + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 20) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + M(SyntaxKind.ArgumentList); + { + M(SyntaxKind.OpenParenToken); + M(SyntaxKind.CloseParenToken); + } + } + M(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Ref() + { + var text = "ref record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview); + + verifyParsedAsRecord(); + + UsingTree(text, options: TestOptions.Regular8, + // (1,5): error CS0116: A namespace cannot directly contain members such as fields or methods + // ref record struct S; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "record").WithLocation(1, 5), + // (1,20): error CS1514: { expected + // ref record struct S; + Diagnostic(ErrorCode.ERR_LbraceExpected, ";").WithLocation(1, 20), + // (1,20): error CS1513: } expected + // ref record struct S; + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 20) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.RefType); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + + UsingTree(text, options: TestOptions.Regular9, + // (1,12): error CS8652: The feature 'record structs' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // ref record struct S; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "struct").WithArguments("record structs").WithLocation(1, 12) + ); + + verifyParsedAsRecord(); + + void verifyParsedAsRecord() + { + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Const() + { + var text = "const record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,14): error CS1001: Identifier expected + // const record struct S; + Diagnostic(ErrorCode.ERR_IdentifierExpected, "struct").WithLocation(1, 14), + // (1,14): error CS0145: A const field requires a value to be provided + // const record struct S; + Diagnostic(ErrorCode.ERR_ConstValueRequired, "struct").WithLocation(1, 14), + // (1,14): error CS1002: ; expected + // const record struct S; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 14), + // (1,22): error CS1514: { expected + // const record struct S; + Diagnostic(ErrorCode.ERR_LbraceExpected, ";").WithLocation(1, 22), + // (1,22): error CS1513: } expected + // const record struct S; + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 22) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.ConstKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + M(SyntaxKind.VariableDeclarator); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_Fixed() + { + var text = "fixed record struct S;"; + UsingTree(text, options: TestOptions.RegularPreview, + // (1,14): error CS1001: Identifier expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_IdentifierExpected, "struct").WithLocation(1, 14), + // (1,14): error CS1003: Syntax error, '[' expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_SyntaxError, "struct").WithArguments("[", "struct").WithLocation(1, 14), + // (1,14): error CS1003: Syntax error, ']' expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_SyntaxError, "struct").WithArguments("]", "struct").WithLocation(1, 14), + // (1,14): error CS0443: Syntax error; value expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_ValueExpected, "struct").WithLocation(1, 14), + // (1,14): error CS1002: ; expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(1, 14), + // (1,22): error CS1514: { expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_LbraceExpected, ";").WithLocation(1, 22), + // (1,22): error CS1513: } expected + // fixed record struct S; + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 22) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.FixedKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "record"); + } + N(SyntaxKind.VariableDeclarator); + { + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.BracketedArgumentList); + { + M(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + } + M(SyntaxKind.CloseBracketToken); + } + } + } + M(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_BaseListWithParens() + { + var text = "record struct S : Base(1);"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.BaseList); + { + N(SyntaxKind.ColonToken); + N(SyntaxKind.PrimaryConstructorBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Base"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.RecordStructs)] + public void RecordStructParsing_BaseListWithParens_WithPositionalParameterList() + { + var text = "record struct S(int X) : Base(1);"; + UsingTree(text, options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordStructDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.BaseList); + { + N(SyntaxKind.ColonToken); + N(SyntaxKind.PrimaryConstructorBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Base"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(51590, "https://github.com/dotnet/roslyn/issues/51590")] + public void ParseIncompleteRecordSyntax() + { + ParseIncompleteSyntax("public sealed record C() { }"); + } + + [Fact] + public void ParseIncompleteRecordStructSyntax() + { + ParseIncompleteSyntax("public sealed record struct C() { }"); } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 9cffea78ee445..ffaca6b7089bb 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -693,6 +693,70 @@ public void TestTreesWithDifferentTriviaAreNotEquivalent() Assert.False(tree1.GetCompilationUnitRoot().IsEquivalentTo(tree2.GetCompilationUnitRoot())); } + [Fact] + public void TestNodeIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.True(tree.GetCompilationUnitRoot().IsIncrementallyIdenticalTo(tree.GetCompilationUnitRoot())); + } + + [Fact] + public void TestTokenIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.True(tree.GetCompilationUnitRoot().EndOfFileToken.IsIncrementallyIdenticalTo(tree.GetCompilationUnitRoot().EndOfFileToken)); + } + + [Fact] + public void TestDifferentTokensFromSameTreeNotIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.False(tree.GetCompilationUnitRoot().GetFirstToken().IsIncrementallyIdenticalTo(tree.GetCompilationUnitRoot().GetFirstToken().GetNextToken())); + } + + [Fact] + public void TestCachedTokensFromDifferentTreesIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = SyntaxFactory.ParseSyntaxTree(text); + Assert.True(tree1.GetCompilationUnitRoot().GetFirstToken().IsIncrementallyIdenticalTo(tree2.GetCompilationUnitRoot().GetFirstToken())); + } + + [Fact] + public void TestNodesFromSameContentNotIncrementallyParsedNotIncrementallyEquivalent() + { + var text = "class goo { }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = SyntaxFactory.ParseSyntaxTree(text); + Assert.False(tree1.GetCompilationUnitRoot().IsIncrementallyIdenticalTo(tree2.GetCompilationUnitRoot())); + } + + [Fact] + public void TestNodesFromIncrementalParseIncrementallyEquivalent1() + { + var text = "class goo { void M() { } }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = tree1.WithChangedText(tree1.GetText().WithChanges(new TextChange(default, " "))); + Assert.True( + tree1.GetCompilationUnitRoot().DescendantNodes().OfType().Single().IsIncrementallyIdenticalTo( + tree2.GetCompilationUnitRoot().DescendantNodes().OfType().Single())); + } + + [Fact] + public void TestNodesFromIncrementalParseNotIncrementallyEquivalent1() + { + var text = "class goo { void M() { } }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = tree1.WithChangedText(tree1.GetText().WithChanges(new TextChange(new TextSpan(22, 0), " return; "))); + Assert.False( + tree1.GetCompilationUnitRoot().DescendantNodes().OfType().Single().IsIncrementallyIdenticalTo( + tree2.GetCompilationUnitRoot().DescendantNodes().OfType().Single())); + } + [Fact, WorkItem(536664, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/536664")] public void TestTriviaNodeCached() { diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs index 2e8fa09adecb3..3b57464ac3a0f 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs @@ -16,6 +16,142 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class SyntaxNormalizerTests { + [Fact, WorkItem(52543, "https://github.com/dotnet/roslyn/issues/52543")] + public void TestNormalizePatternInIf() + { + TestNormalizeStatement( + @"{object x = 1; + if (x is {}) + { + } + if (x is {} t) + { + } + if (x is int {} t2) + { + } + if (x is System.ValueTuple(_, _) { Item1: > 10 } t3) + { + } + if (x is System.ValueTuple(_, _) { Item1: > 10, Item2: < 20 }) + { + } +}", + @"{ + object x = 1; + if (x is { }) + { + } + + if (x is { } t) + { + } + + if (x is int { } t2) + { + } + + if (x is System.ValueTuple (_, _) { Item1: > 10 } t3) + { + } + + if (x is System.ValueTuple (_, _) { Item1: > 10, Item2: < 20 }) + { + } +}".NormalizeLineEndings() + ); + } + + [Fact, WorkItem(52543, "https://github.com/dotnet/roslyn/issues/52543")] + public void TestNormalizeSwitchExpression() + { + TestNormalizeStatement( + @"var x = (int)1 switch { 1 => ""one"", 2 => ""two"", 3 => ""three"", {} => "">= 4"" };", + @"var x = (int)1 switch +{ + 1 => ""one"", + 2 => ""two"", + 3 => ""three"", + { } => "">= 4"" +};".NormalizeLineEndings() + ); + } + + [Fact, WorkItem(52543, "https://github.com/dotnet/roslyn/issues/52543")] + public void TestNormalizeSwitchRecPattern() + { + TestNormalizeStatement( + @"var x = (object)1 switch { + int { } => ""two"", + { } t when t.GetHashCode() == 42 => ""42"", + System.ValueTuple (1, _) { Item2: > 2 and < 20 } => ""tuple.Item2 < 20"", + System.ValueTuple (1, _) { Item2: >= 100 } greater => greater.ToString(), + System.ValueType {} => ""not null value"", + object {} i when i is not 42 => ""not 42"", + { } => ""not null"", + null => ""null"", +};", + @"var x = (object)1 switch +{ + int { } => ""two"", + { } t when t.GetHashCode() == 42 => ""42"", + System.ValueTuple (1, _) { Item2: > 2 and < 20 } => ""tuple.Item2 < 20"", + System.ValueTuple (1, _) { Item2: >= 100 } greater => greater.ToString(), + System.ValueType { } => ""not null value"", + object { } i when i is not 42 => ""not 42"", + { } => ""not null"", + null => ""null"", +};".NormalizeLineEndings() + ); + } + + [Fact, WorkItem(52543, "https://github.com/dotnet/roslyn/issues/52543")] + public void TestNormalizeSwitchExpressionComplex() + { + var a = @"var x = vehicle switch + { + Car { Passengers: 0 } => 2.00m + 0.50m, + Car { Passengers: 1 } => 2.0m, + Car { Passengers: 2 } => 2.0m - 0.50m, + Car c => 2.00m - 1.0m, + + Taxi { Fares: 0 } => 3.50m + 1.00m, + Taxi { Fares: 1 } => 3.50m, + Taxi { Fares: 2 } => 3.50m - 0.50m, + Taxi t => 3.50m - 1.00m, + + Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m, + Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m, + Bus b => 5.00m, + + DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m, + DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m, + DeliveryTruck t => 10.00m, + { } => -1, //throw new ArgumentException(message: ""Not a known vehicle type"", paramName: nameof(vehicle)), + null => 0//throw new ArgumentNullException(nameof(vehicle)) + };"; + var b = @"var x = vehicle switch +{ + Car { Passengers: 0 } => 2.00m + 0.50m, + Car { Passengers: 1 } => 2.0m, + Car { Passengers: 2 } => 2.0m - 0.50m, + Car c => 2.00m - 1.0m, + Taxi { Fares: 0 } => 3.50m + 1.00m, + Taxi { Fares: 1 } => 3.50m, + Taxi { Fares: 2 } => 3.50m - 0.50m, + Taxi t => 3.50m - 1.00m, + Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m, + Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m, + Bus b => 5.00m, + DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m, + DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m, + DeliveryTruck t => 10.00m, + { } => -1, //throw new ArgumentException(message: ""Not a known vehicle type"", paramName: nameof(vehicle)), + null => 0 //throw new ArgumentNullException(nameof(vehicle)) +};".NormalizeLineEndings(); + TestNormalizeStatement(a, b); + } + [Fact, WorkItem(50742, "https://github.com/dotnet/roslyn/issues/50742")] public void TestLineBreakInterpolations() { @@ -258,9 +394,17 @@ public void TestNormalizeDeclaration1() TestNormalizeDeclaration("using a.b;", "using a.b;"); TestNormalizeDeclaration("using A; using B; class C {}", "using A;\r\nusing B;\r\n\r\nclass C\r\n{\r\n}"); + TestNormalizeDeclaration("global using a;", "global using a;"); + TestNormalizeDeclaration("global using a=b;", "global using a = b;"); + TestNormalizeDeclaration("global using a.b;", "global using a.b;"); + TestNormalizeDeclaration("global using A; global using B; class C {}", "global using A;\r\nglobal using B;\r\n\r\nclass C\r\n{\r\n}"); + TestNormalizeDeclaration("global using A; using B; class C {}", "global using A;\r\nusing B;\r\n\r\nclass C\r\n{\r\n}"); + TestNormalizeDeclaration("using A; global using B; class C {}", "using A;\r\nglobal using B;\r\n\r\nclass C\r\n{\r\n}"); + // namespace TestNormalizeDeclaration("namespace a{}", "namespace a\r\n{\r\n}"); TestNormalizeDeclaration("namespace a{using b;}", "namespace a\r\n{\r\n using b;\r\n}"); + TestNormalizeDeclaration("namespace a{global using b;}", "namespace a\r\n{\r\n global using b;\r\n}"); TestNormalizeDeclaration("namespace a{namespace b{}}", "namespace a\r\n{\r\n namespace b\r\n {\r\n }\r\n}"); TestNormalizeDeclaration("namespace a{}namespace b{}", "namespace a\r\n{\r\n}\r\n\r\nnamespace b\r\n{\r\n}"); @@ -367,6 +511,13 @@ public void TestNormalizeDeclaration1() TestNormalizeDeclaration("class c{void M([a]int x,[b] [c,d]int y){}}", "class c\r\n{\r\n void M([a] int x, [b][c, d] int y)\r\n {\r\n }\r\n}"); } + [Fact] + public void TestSpacingOnRecord() + { + TestNormalizeDeclaration("record class C(int I, int J);", "record class C(int I, int J);"); + TestNormalizeDeclaration("record struct S(int I, int J);", "record struct S(int I, int J);"); + } + [Fact] [WorkItem(23618, "https://github.com/dotnet/roslyn/issues/23618")] public void TestSpacingOnInvocationLikeKeywords() @@ -389,12 +540,12 @@ public void TestSpacingOnInvocationLikeKeywords() // no space between this and ( TestNormalizeDeclaration( "class C { C() : this () { } }", - "class C\r\n{\r\n C(): this()\r\n {\r\n }\r\n}"); + "class C\r\n{\r\n C() : this()\r\n {\r\n }\r\n}"); // no space between base and ( TestNormalizeDeclaration( "class C { C() : base () { } }", - "class C\r\n{\r\n C(): base()\r\n {\r\n }\r\n}"); + "class C\r\n{\r\n C() : base()\r\n {\r\n }\r\n}"); // no space between checked and ( TestNormalizeExpression("checked (a)", "checked(a)"); @@ -769,6 +920,35 @@ public void TestNormalizeFunctionPointerWithUnmanagedCallingConventionAndSpecifi TestNormalizeDeclaration(content, expected); } + [Fact] + [WorkItem(53254, "https://github.com/dotnet/roslyn/issues/53254")] + public void TestNormalizeColonInConstructorInitializer() + { + var content = +@"class Base +{ +} + +class Derived : Base +{ + public Derived():base(){} +}"; + + var expected = +@"class Base +{ +} + +class Derived : Base +{ + public Derived() : base() + { + } +}"; + + TestNormalizeDeclaration(content, expected); + } + [Fact] [WorkItem(49732, "https://github.com/dotnet/roslyn/issues/49732")] public void TestNormalizeXmlInDocComment() diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTreeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTreeTests.cs index ceeb8d3f04c6f..806ada148b529 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTreeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTreeTests.cs @@ -6,16 +6,31 @@ using System.Collections.Immutable; using System.Text; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; +using Xunit.Abstractions; using static Roslyn.Test.Utilities.TestHelpers; using KeyValuePair = Roslyn.Utilities.KeyValuePairUtil; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { - public class SyntaxTreeTests + public class SyntaxTreeTests : ParsingTests { + public SyntaxTreeTests(ITestOutputHelper output) : base(output) { } + + protected SyntaxTree UsingTree(string text, CSharpParseOptions options, params DiagnosticDescription[] expectedErrors) + { + var tree = base.UsingTree(text, options); + + var actualErrors = tree.GetDiagnostics(); + actualErrors.Verify(expectedErrors); + + return tree; + } + // Diagnostic options on syntax trees are now obsolete #pragma warning disable CS0618 [Fact] @@ -227,5 +242,911 @@ public void WithFilePath_Null() Assert.Equal(string.Empty, SyntaxFactory.ParseSyntaxTree("", path: null).FilePath); Assert.Equal(string.Empty, CSharpSyntaxTree.Create((CSharpSyntaxNode)oldTree.GetRoot(), path: null).FilePath); } + + [Fact] + public void GlobalUsingDirective_01() + { + var test = "global using ns1;"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_02() + { + var test = "global using ns1;"; + + UsingTree(test, TestOptions.Regular9, + // (1,1): error CS8652: The feature 'global using directive' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // global using ns1; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "global using ns1;").WithArguments("global using directive").WithLocation(1, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_03() + { + var test = "namespace ns { global using ns1; }"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_04() + { + var test = "namespace ns { global using ns1; }"; + + UsingTree(test, TestOptions.Regular9, + // (1,16): error CS8652: The feature 'global using directive' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // namespace ns { global using ns1; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "global using ns1;").WithArguments("global using directive").WithLocation(1, 16) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_05() + { + var test = "global using static ns1;"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_06() + { + var test = "global using static ns1;"; + + UsingTree(test, TestOptions.Regular9, + // (1,1): error CS8652: The feature 'global using directive' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // global using static ns1; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "global using static ns1;").WithArguments("global using directive").WithLocation(1, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_07() + { + var test = "namespace ns { global using static ns1; }"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_08() + { + var test = "namespace ns { global using static ns1; }"; + + UsingTree(test, TestOptions.Regular9, + // (1,16): error CS8652: The feature 'global using directive' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // namespace ns { global using static ns1; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "global using static ns1;").WithArguments("global using directive").WithLocation(1, 16) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_09() + { + var test = "global using alias = ns1;"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.NameEquals); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "alias"); + } + N(SyntaxKind.EqualsToken); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_10() + { + var test = "global using alias = ns1;"; + + UsingTree(test, TestOptions.Regular9, + // (1,1): error CS8652: The feature 'global using directive' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // global using alias = ns1; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "global using alias = ns1;").WithArguments("global using directive").WithLocation(1, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.NameEquals); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "alias"); + } + N(SyntaxKind.EqualsToken); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_11() + { + var test = "namespace ns { global using alias = ns1; }"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.NameEquals); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "alias"); + } + N(SyntaxKind.EqualsToken); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_12() + { + var test = "namespace ns { global using alias = ns1; }"; + + UsingTree(test, TestOptions.Regular9, + // (1,16): error CS8652: The feature 'global using directive' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // namespace ns { global using alias = ns1; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "global using alias = ns1;").WithArguments("global using directive").WithLocation(1, 16) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.NameEquals); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "alias"); + } + N(SyntaxKind.EqualsToken); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_13() + { + var test = @" +namespace ns {} +global using ns1; +"; + + UsingTree(test, TestOptions.RegularPreview, + // (3,1): error CS1529: A using clause must precede all other elements defined in the namespace except extern alias declarations + // global using ns1; + Diagnostic(ErrorCode.ERR_UsingAfterElements, "global using ns1;").WithLocation(3, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_14() + { + var test = @" +global using ns1; +extern alias a; +"; + + UsingTree(test, TestOptions.RegularPreview, + // (3,1): error CS0439: An extern alias declaration must precede all other elements defined in the namespace + // extern alias a; + Diagnostic(ErrorCode.ERR_ExternAfterElements, "extern").WithLocation(3, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_15() + { + var test = @" +namespace ns2 +{ + namespace ns {} + global using ns1; +} +"; + + UsingTree(test, TestOptions.RegularPreview, + // (5,5): error CS1529: A using clause must precede all other elements defined in the namespace except extern alias declarations + // global using ns1; + Diagnostic(ErrorCode.ERR_UsingAfterElements, "global using ns1;").WithLocation(5, 5) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns2"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_16() + { + var test = @" +global using ns1; +namespace ns {} +"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.NamespaceDeclaration); + { + N(SyntaxKind.NamespaceKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns"); + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void UsingDirective_01() + { + var test = "d using ns1;"; + + UsingTree(test, TestOptions.Regular, + // (1,1): error CS0116: A namespace cannot directly contain members such as fields or methods + // d using ns1; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "d").WithLocation(1, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_17() + { + var test = "d global using ns1;"; + + UsingTree(test, TestOptions.RegularPreview, + // (1,1): error CS0116: A namespace cannot directly contain members such as fields or methods + // d global using ns1; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "d").WithLocation(1, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void UsingDirective_02() + { + var test = "using ns1; p using ns2;"; + + UsingTree(test, TestOptions.Regular, + // (1,12): error CS0116: A namespace cannot directly contain members such as fields or methods + // using ns1; p using ns2; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "p").WithLocation(1, 12) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns2"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_18() + { + var test = "global using ns1; p global using ns2;"; + + UsingTree(test, TestOptions.RegularPreview, + // (1,19): error CS0116: A namespace cannot directly contain members such as fields or methods + // global using ns1; p global using ns2; + Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "p").WithLocation(1, 19) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns2"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_19() + { + var test = @" +M(); +global using ns1; +"; + + UsingTree(test, TestOptions.RegularPreview, + // (3,1): error CS1529: A using clause must precede all other elements defined in the namespace except extern alias declarations + // global using ns1; + Diagnostic(ErrorCode.ERR_UsingAfterElements, "global using ns1;").WithLocation(3, 1) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_20() + { + var test = @" +global using ns1; +using ns2; +M(); +"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns2"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_21() + { + var test = @" +global using alias1 = ns1; +using alias2 = ns2; +M(); +"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.NameEquals); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "alias1"); + } + N(SyntaxKind.EqualsToken); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.NameEquals); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "alias2"); + } + N(SyntaxKind.EqualsToken); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns2"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void GlobalUsingDirective_22() + { + var test = @" +global using static ns1; +using static ns2; +M(); +"; + + UsingTree(test, TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.GlobalKeyword); + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns1"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.UsingDirective); + { + N(SyntaxKind.UsingKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ns2"); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } } } diff --git a/src/Compilers/CSharp/csc/Program.cs b/src/Compilers/CSharp/csc/Program.cs index 1051f25d7ed4b..9028512d5428b 100644 --- a/src/Compilers/CSharp/csc/Program.cs +++ b/src/Compilers/CSharp/csc/Program.cs @@ -29,12 +29,14 @@ public static int Main(string[] args) private static int MainCore(string[] args) { + var requestId = Guid.NewGuid(); + using var logger = new CompilerServerLogger($"csc {requestId}"); + #if BOOTSTRAP - ExitingTraceListener.Install(); + ExitingTraceListener.Install(logger); #endif - using var logger = new CompilerServerLogger(); - return BuildClient.Run(args, RequestLanguage.CSharpCompile, Csc.Run, logger); + return BuildClient.Run(args, RequestLanguage.CSharpCompile, Csc.Run, logger, requestId); } public static int Run(string[] args, string clientDir, string workingDir, string sdkDir, string tempDir, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader) diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs index 1624752b3330e..b3b540ef660da 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs @@ -57,6 +57,42 @@ public void SimpleCase() Assert.Equal("/bogus", config.NormalizedDirectory); } + [Fact] + [WorkItem(52469, "https://github.com/dotnet/roslyn/issues/52469")] + public void ConfigWithEscapedValues() + { + var config = ParseConfigFile(@"is_global = true + +[c:/\{f\*i\?le1\}.cs] +build_metadata.Compile.ToRetrieve = abc123 + +[c:/f\,ile\#2.cs] +build_metadata.Compile.ToRetrieve = def456 + +[c:/f\;i\!le\[3\].cs] +build_metadata.Compile.ToRetrieve = ghi789 +"); + + var namedSections = config.NamedSections; + Assert.Equal("c:/\\{f\\*i\\?le1\\}.cs", namedSections[0].Name); + AssertEx.Equal( + new[] { KeyValuePair.Create("build_metadata.compile.toretrieve", "abc123") }, + namedSections[0].Properties + ); + + Assert.Equal("c:/f\\,ile\\#2.cs", namedSections[1].Name); + AssertEx.Equal( + new[] { KeyValuePair.Create("build_metadata.compile.toretrieve", "def456") }, + namedSections[1].Properties + ); + + Assert.Equal("c:/f\\;i\\!le\\[3\\].cs", namedSections[2].Name); + AssertEx.Equal( + new[] { KeyValuePair.Create("build_metadata.compile.toretrieve", "ghi789") }, + namedSections[2].Properties + ); + } + [ConditionalFact(typeof(WindowsOnly))] public void WindowsPath() { diff --git a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs index 16ecf206029d7..c660ffa62b568 100644 --- a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs @@ -383,5 +383,21 @@ public void CombinePaths_DifferentFromPathCombine(string expected, string path1, { Assert.Equal(expected, PathUtilities.CombinePaths(path1, path2)); } + + [ConditionalFact(typeof(WindowsOnly)), WorkItem(51602, @"https://github.com/dotnet/roslyn/issues/51602")] + public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Windows() + { + var expected = ""; + var result = PathUtilities.GetRelativePath(@"C:\A\B\", @"C:\A\B"); + Assert.Equal(expected, result); + } + + [ConditionalFact(typeof(UnixLikeOnly)), WorkItem(51602, @"https://github.com/dotnet/roslyn/issues/51602")] + public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Unix() + { + var expected = ""; + var result = PathUtilities.GetRelativePath(@"/A/B/", @"/A/B"); + Assert.Equal(expected, result); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/OneOrManyTests.cs b/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/OneOrManyTests.cs index b97b592703bc5..0d039583f9083 100644 --- a/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/OneOrManyTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/OneOrManyTests.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.Collections.Immutable; using Roslyn.Test.Utilities; @@ -14,15 +12,34 @@ namespace Microsoft.CodeAnalysis.UnitTests.InternalUtilities { public class OneOrManyTests : TestBase { + private static void Verify(OneOrMany actual, params T[] expected) + where T : notnull + { + Assert.Equal(actual.Count, expected.Length); + int n = actual.Count; + int i; + for (i = 0; i < n; i++) + { + Assert.Equal(actual[i], expected[i]); + } + i = 0; + foreach (var value in actual) + { + Assert.Equal(value, expected[i]); + i++; + } + Assert.Equal(n, i); + } + [Fact] - public void Zero() + public void CreateZero() { Verify(OneOrMany.Create(ImmutableArray.Empty)); Verify(new OneOrMany(ImmutableArray.Empty)); } [Fact] - public void One() + public void CreateOne() { Verify(OneOrMany.Create(1), 1); Verify(OneOrMany.Create(ImmutableArray.Create(2)), 2); @@ -33,7 +50,7 @@ public void One() } [Fact] - public void Many() + public void CreateArray() { Verify(OneOrMany.Create(ImmutableArray.Create(1, 2, 3)).Add(4), 1, 2, 3, 4); Verify(OneOrMany.Create(ImmutableArray.Create(1, 2, 3, 4)), 1, 2, 3, 4); @@ -41,24 +58,54 @@ public void Many() Verify(new OneOrMany(ImmutableArray.Create(1, 2, 3)).Add(4), 1, 2, 3, 4); Verify(new OneOrMany(ImmutableArray.Create(1, 2, 3, 4)), 1, 2, 3, 4); Verify(new OneOrMany(ImmutableArray.Empty).Add(1).Add(2).Add(3).Add(4), 1, 2, 3, 4); + Verify(OneOrMany.Create(ImmutableArray.Create(1)).Add(4), 1, 4); + Verify(OneOrMany.Create(ImmutableArray.Create(1)), 1); } - private static void Verify(OneOrMany actual, params T[] expected) + [Fact] + public void Contains() { - Assert.Equal(actual.Count, expected.Length); - int n = actual.Count; - int i; - for (i = 0; i < n; i++) - { - Assert.Equal(actual[i], expected[i]); - } - i = 0; - foreach (var value in actual) - { - Assert.Equal(value, expected[i]); - i++; - } - Assert.Equal(n, i); + Assert.True(OneOrMany.Create(1).Contains(1)); + Assert.False(OneOrMany.Create(1).Contains(0)); + + Assert.False(OneOrMany.Create(ImmutableArray.Empty).Contains(0)); + + Assert.True(OneOrMany.Create(ImmutableArray.Create(1)).Contains(1)); + Assert.False(OneOrMany.Create(ImmutableArray.Create(1)).Contains(0)); + + Assert.True(OneOrMany.Create(ImmutableArray.Create(1, 2)).Contains(1)); + Assert.True(OneOrMany.Create(ImmutableArray.Create(1, 2)).Contains(2)); + Assert.False(OneOrMany.Create(ImmutableArray.Create(1, 2)).Contains(0)); + } + + [Fact] + public void Select() + { + Verify(OneOrMany.Create(1).Select(i => i + 1), 2); + Verify(OneOrMany.Create(ImmutableArray.Empty).Select(i => i + 1)); + Verify(OneOrMany.Create(ImmutableArray.Create(1)).Select(i => i + 1), 2); + Verify(OneOrMany.Create(ImmutableArray.Create(1, 2)).Select(i => i + 1), 2, 3); + } + + [Fact] + public void SelectWithArg() + { + Verify(OneOrMany.Create(1).Select((i, a) => i + a, 1), 2); + Verify(OneOrMany.Create(ImmutableArray.Empty).Select((i, a) => i + a, 1)); + Verify(OneOrMany.Create(ImmutableArray.Create(1)).Select((i, a) => i + a, 1), 2); + Verify(OneOrMany.Create(ImmutableArray.Create(1, 2)).Select((i, a) => i + a, 1), 2, 3); + } + + [Fact] + public void FirstOrDefault() + { + Assert.Equal(1, OneOrMany.Create(1).FirstOrDefault(i => i < 2)); + Assert.Equal(0, OneOrMany.Create(1).FirstOrDefault(i => i > 2)); + Assert.Equal(0, OneOrMany.Create(ImmutableArray.Empty).FirstOrDefault(i => i > 2)); + Assert.Equal(1, OneOrMany.Create(ImmutableArray.Create(1)).FirstOrDefault(i => i < 2)); + Assert.Equal(0, OneOrMany.Create(ImmutableArray.Create(1)).FirstOrDefault(i => i > 2)); + Assert.Equal(1, OneOrMany.Create(ImmutableArray.Create(1, 3)).FirstOrDefault(i => i < 2)); + Assert.Equal(3, OneOrMany.Create(ImmutableArray.Create(1, 3)).FirstOrDefault(i => i > 2)); } [Fact] diff --git a/src/Compilers/Core/CommandLine/BuildProtocol.cs b/src/Compilers/Core/CommandLine/BuildProtocol.cs index 6da53beb1f746..4ad65f3c4db6d 100644 --- a/src/Compilers/Core/CommandLine/BuildProtocol.cs +++ b/src/Compilers/Core/CommandLine/BuildProtocol.cs @@ -33,7 +33,7 @@ namespace Microsoft.CodeAnalysis.CommandLine /// Field Name Type Size (bytes) /// ---------------------------------------------------- /// Length Integer 4 - /// ProtocolVersion Integer 4 + /// RequestId Guid 16 /// Language RequestLanguage 4 /// CompilerHash String Variable /// Argument Count UInteger 4 @@ -45,17 +45,25 @@ namespace Microsoft.CodeAnalysis.CommandLine /// internal class BuildRequest { - public readonly uint ProtocolVersion; + /// + /// The maximum size of a request supported by the compiler server. + /// + /// + /// Currently this limit is 5MB. + /// + private const int MaximumRequestSize = 0x500000; + + public readonly Guid RequestId; public readonly RequestLanguage Language; public readonly ReadOnlyCollection Arguments; public readonly string CompilerHash; - public BuildRequest(uint protocolVersion, - RequestLanguage language, + public BuildRequest(RequestLanguage language, string compilerHash, - IEnumerable arguments) + IEnumerable arguments, + Guid? requestId = null) { - ProtocolVersion = protocolVersion; + RequestId = requestId ?? Guid.Empty; Language = language; Arguments = new ReadOnlyCollection(arguments.ToList()); CompilerHash = compilerHash; @@ -75,6 +83,7 @@ public static BuildRequest Create(RequestLanguage language, string workingDirectory, string tempDirectory, string compilerHash, + Guid? requestId = null, string? keepAlive = null, string? libDirectory = null) { @@ -102,19 +111,19 @@ public static BuildRequest Create(RequestLanguage language, requestArgs.Add(new Argument(ArgumentId.CommandLineArgument, i, arg)); } - return new BuildRequest(BuildProtocolConstants.ProtocolVersion, language, compilerHash, requestArgs); + return new BuildRequest(language, compilerHash, requestArgs, requestId); } public static BuildRequest CreateShutdown() { var requestArgs = new[] { new Argument(ArgumentId.Shutdown, argumentIndex: 0, value: "") }; - return new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, GetCommitHash() ?? "", requestArgs); + return new BuildRequest(RequestLanguage.CSharpCompile, GetCommitHash() ?? "", requestArgs); } /// /// Read a Request from the given stream. /// - /// The total request size must be less than 1MB. + /// The total request size must be less than . /// /// null if the Request was too large, the Request otherwise. public static async Task ReadAsync(Stream inStream, CancellationToken cancellationToken) @@ -124,10 +133,10 @@ public static async Task ReadAsync(Stream inStream, CancellationTo await ReadAllAsync(inStream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToInt32(lengthBuffer, 0); - // Back out if the request is > 1MB - if (length > 0x100000) + // Back out if the request is too large + if (length > MaximumRequestSize) { - throw new ArgumentException("Request is over 1MB in length"); + throw new ArgumentException($"Request is over {MaximumRequestSize >> 20}MB in length"); } cancellationToken.ThrowIfCancellationRequested(); @@ -139,25 +148,34 @@ public static async Task ReadAsync(Stream inStream, CancellationTo cancellationToken.ThrowIfCancellationRequested(); // Parse the request into the Request data structure. - using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode)) + using var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode); + var requestId = readGuid(reader); + var language = (RequestLanguage)reader.ReadUInt32(); + var compilerHash = reader.ReadString(); + uint argumentCount = reader.ReadUInt32(); + var argumentsBuilder = new List((int)argumentCount); + + for (int i = 0; i < argumentCount; i++) { - var protocolVersion = reader.ReadUInt32(); - var language = (RequestLanguage)reader.ReadUInt32(); - var compilerHash = reader.ReadString(); - uint argumentCount = reader.ReadUInt32(); + cancellationToken.ThrowIfCancellationRequested(); + argumentsBuilder.Add(BuildRequest.Argument.ReadFromBinaryReader(reader)); + } - var argumentsBuilder = new List((int)argumentCount); + return new BuildRequest(language, + compilerHash, + argumentsBuilder, + requestId); - for (int i = 0; i < argumentCount; i++) + static Guid readGuid(BinaryReader reader) + { + const int size = 16; + var bytes = new byte[size]; + if (size != reader.Read(bytes, 0, size)) { - cancellationToken.ThrowIfCancellationRequested(); - argumentsBuilder.Add(BuildRequest.Argument.ReadFromBinaryReader(reader)); + throw new InvalidOperationException(); } - return new BuildRequest(protocolVersion, - language, - compilerHash, - argumentsBuilder); + return new Guid(bytes); } } @@ -166,37 +184,35 @@ public static async Task ReadAsync(Stream inStream, CancellationTo /// public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default(CancellationToken)) { - using (var memoryStream = new MemoryStream()) - using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream, Encoding.Unicode); + writer.Write(RequestId.ToByteArray()); + writer.Write((uint)Language); + writer.Write(CompilerHash); + writer.Write(Arguments.Count); + foreach (Argument arg in Arguments) { - writer.Write(ProtocolVersion); - writer.Write((uint)Language); - writer.Write(CompilerHash); - writer.Write(Arguments.Count); - foreach (Argument arg in Arguments) - { - cancellationToken.ThrowIfCancellationRequested(); - arg.WriteToBinaryWriter(writer); - } - writer.Flush(); - cancellationToken.ThrowIfCancellationRequested(); + arg.WriteToBinaryWriter(writer); + } + writer.Flush(); - // Write the length of the request - int length = checked((int)memoryStream.Length); - - // Back out if the request is > 1 MB - if (memoryStream.Length > 0x100000) - { - throw new ArgumentOutOfRangeException("Request is over 1MB in length"); - } + cancellationToken.ThrowIfCancellationRequested(); - await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4, - cancellationToken).ConfigureAwait(false); + // Write the length of the request + int length = checked((int)memoryStream.Length); - memoryStream.Position = 0; - await memoryStream.CopyToAsync(outStream, bufferSize: length, cancellationToken: cancellationToken).ConfigureAwait(false); + // Back out if the request is too large + if (memoryStream.Length > MaximumRequestSize) + { + throw new ArgumentOutOfRangeException($"Request is over {MaximumRequestSize >> 20}MB in length"); } + + await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4, + cancellationToken).ConfigureAwait(false); + + memoryStream.Position = 0; + await memoryStream.CopyToAsync(outStream, bufferSize: length, cancellationToken: cancellationToken).ConfigureAwait(false); } /// @@ -521,11 +537,6 @@ internal enum RequestLanguage /// internal static class BuildProtocolConstants { - /// - /// The version number for this protocol. - /// - public const uint ProtocolVersion = 3; - // Arguments for CSharp and VB Compiler public enum ArgumentId { diff --git a/src/Compilers/Core/CommandLine/CompilerServerLogger.cs b/src/Compilers/Core/CommandLine/CompilerServerLogger.cs index a08aabfc9f591..d13c363c03446 100644 --- a/src/Compilers/Core/CommandLine/CompilerServerLogger.cs +++ b/src/Compilers/Core/CommandLine/CompilerServerLogger.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using System.Text; namespace Microsoft.CodeAnalysis.CommandLine @@ -102,16 +101,16 @@ internal sealed class CompilerServerLogger : ICompilerServerLogger, IDisposable internal const string LoggingPrefix = "---"; private Stream? _loggingStream; - private readonly int _processId; + private readonly string _identifier; public bool IsLogging => _loggingStream is object; /// /// Static class initializer that initializes logging. /// - public CompilerServerLogger() + public CompilerServerLogger(string identifier) { - _processId = Process.GetCurrentProcess().Id; + _identifier = identifier; try { @@ -123,7 +122,8 @@ public CompilerServerLogger() // Otherwise, assume that the environment variable specifies the name of the log file. if (Directory.Exists(loggingFileName)) { - loggingFileName = Path.Combine(loggingFileName, $"server.{_processId}.log"); + var processId = Process.GetCurrentProcess().Id; + loggingFileName = Path.Combine(loggingFileName, $"server.{processId}.log"); } // Open allowing sharing. We allow multiple processes to log to the same file, so we use share mode to allow that. @@ -147,7 +147,7 @@ public void Log(string message) if (_loggingStream is object) { var threadId = Environment.CurrentManagedThreadId; - var prefix = $"PID={_processId} TID={threadId} Ticks={Environment.TickCount} "; + var prefix = $"ID={_identifier} TID={threadId}: "; string output = prefix + message + Environment.NewLine; byte[] bytes = Encoding.UTF8.GetBytes(output); diff --git a/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs b/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs index 85c4f573c0f3c..d0eead0082aa4 100644 --- a/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs +++ b/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs @@ -77,9 +77,9 @@ public override bool Execute() { // write the section for this item builder.AppendLine() - .Append("[") - .Append(group.Key) - .AppendLine("]"); + .Append("["); + EncodeString(builder, group.Key); + builder.AppendLine("]"); foreach (var item in group) { @@ -101,6 +101,27 @@ public override bool Execute() return true; } + /// + /// Filenames with special characters like '#' and'{' get written + /// into the section names in the resulting .editorconfig file. Later, + /// when the file is parsed in configuration options these special + /// characters are interpretted as invalid values and ignored by the + /// processor. We encode the special characters in these strings + /// before writing them here. + /// + + private static void EncodeString(StringBuilder builder, string value) + { + foreach (var c in value) + { + if (c is '*' or '?' or '{' or ',' or ';' or '}' or '[' or ']' or '#' or '!') + { + builder.Append("\\"); + } + builder.Append(c); + } + } + /// /// Equivalent to Roslyn.Utilities.PathUtilities.NormalizeWithForwardSlash /// Both methods should be kept in sync. diff --git a/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs b/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs index 2a988719ab00c..5683e1e40b0bc 100644 --- a/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs +++ b/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs @@ -19,8 +19,8 @@ public abstract class InteractiveCompiler : ManagedToolTask internal readonly PropertyDictionary _store = new PropertyDictionary(); public InteractiveCompiler() + : base(ErrorString.ResourceManager) { - TaskResources = ErrorString.ResourceManager; } #region Properties - Please keep these alphabetized. diff --git a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs index 4832c81ef61e9..71017a0904bd2 100644 --- a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs +++ b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -55,9 +56,8 @@ private enum CompilationKind internal abstract RequestLanguage Language { get; } public ManagedCompiler() + : base(ErrorString.ResourceManager) { - TaskResources = ErrorString.ResourceManager; - // If there is a crash, the runtime error is output to stderr and // we want MSBuild to print it out regardless of verbosity. LogStandardErrorAsError = true; @@ -487,6 +487,12 @@ public string GeneratePathToTool() protected sealed override string PathToNativeTool => Path.Combine(ToolPath ?? "", ToolExe); protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) + { + using var logger = new CompilerServerLogger($"MSBuild {Process.GetCurrentProcess().Id}"); + return ExecuteTool(pathToTool, responseFileCommands, commandLineCommands, logger); + } + + internal int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands, ICompilerServerLogger logger) { if (ProvideCommandLineArgs) { @@ -501,7 +507,9 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand try { - using var logger = new CompilerServerLogger(); + var requestId = Guid.NewGuid(); + logger.Log($"Compilation request {requestId}, PathToTool={pathToTool}"); + string workingDir = CurrentDirectoryToUse(); string? tempDir = BuildServerConnection.GetTempPath(workingDir); @@ -509,7 +517,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand HasToolBeenOverridden || !BuildServerConnection.IsCompilerServerSupported) { - LogCompilationMessage(logger, CompilationKind.Tool, $"using command line tool by design '{pathToTool}'"); + LogCompilationMessage(logger, requestId, CompilationKind.Tool, $"using command line tool by design '{pathToTool}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -520,7 +528,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand var clientDir = Path.GetDirectoryName(PathToManagedTool); if (clientDir is null || tempDir is null) { - LogCompilationMessage(logger, CompilationKind.Tool, $"using command line tool because we could not find client directory '{PathToManagedTool}'"); + LogCompilationMessage(logger, requestId, CompilationKind.Tool, $"using command line tool because we could not find client directory '{PathToManagedTool}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -535,6 +543,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand // commandLineCommands (the parameter) may have been mucked with // (to support using the dotnet cli) var responseTask = BuildServerConnection.RunServerCompilationAsync( + requestId, Language, RoslynString.IsNullOrEmpty(SharedCompilationId) ? null : SharedCompilationId, GetArguments(ToolArguments, responseFileCommands).ToList(), @@ -546,7 +555,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand responseTask.Wait(_sharedCompileCts.Token); - ExitCode = HandleResponse(responseTask.Result, pathToTool, responseFileCommands, commandLineCommands, logger); + ExitCode = HandleResponse(requestId, responseTask.Result, pathToTool, responseFileCommands, commandLineCommands, logger); } catch (OperationCanceledException) { @@ -554,9 +563,8 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand } catch (Exception e) { - var util = new TaskLoggingHelper(this); - util.LogErrorWithCodeFromResources("Compiler_UnexpectedException"); - util.LogErrorFromException(e, showStackTrace: true, showDetail: true, file: null); + Log.LogErrorWithCodeFromResources("Compiler_UnexpectedException"); + Log.LogErrorFromException(e); ExitCode = -1; } finally @@ -630,11 +638,11 @@ private string CurrentDirectoryToUse() /// Handle a response from the server, reporting messages and returning /// the appropriate exit code. /// - private int HandleResponse(BuildResponse? response, string pathToTool, string responseFileCommands, string commandLineCommands, ICompilerServerLogger logger) + private int HandleResponse(Guid requestId, BuildResponse? response, string pathToTool, string responseFileCommands, string commandLineCommands, ICompilerServerLogger logger) { if (response is null) { - LogCompilationMessage(logger, CompilationKind.ToolFallback, "could not launch server"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, "could not launch server"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -648,30 +656,30 @@ private int HandleResponse(BuildResponse? response, string pathToTool, string re case BuildResponse.ResponseType.Completed: var completedResponse = (CompletedBuildResponse)response; LogCompilerOutput(completedResponse.Output, StandardOutputImportanceToUse); - LogCompilationMessage(logger, CompilationKind.Server, "server processed compilation"); + LogCompilationMessage(logger, requestId, CompilationKind.Server, "server processed compilation"); return completedResponse.ReturnCode; case BuildResponse.ResponseType.MismatchedVersion: - LogCompilationMessage(logger, CompilationKind.FatalError, "server reports different protocol version than build task"); + LogCompilationMessage(logger, requestId, CompilationKind.FatalError, "server reports different protocol version than build task"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); case BuildResponse.ResponseType.IncorrectHash: - LogCompilationMessage(logger, CompilationKind.FatalError, "server reports different hash version than build task"); + LogCompilationMessage(logger, requestId, CompilationKind.FatalError, "server reports different hash version than build task"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); case BuildResponse.ResponseType.Rejected: var rejectedResponse = (RejectedBuildResponse)response; - LogCompilationMessage(logger, CompilationKind.ToolFallback, $"server rejected the request '{rejectedResponse.Reason}'"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, $"server rejected the request '{rejectedResponse.Reason}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); case BuildResponse.ResponseType.AnalyzerInconsistency: var analyzerResponse = (AnalyzerInconsistencyBuildResponse)response; var combinedMessage = string.Join(", ", analyzerResponse.ErrorMessages.ToArray()); - LogCompilationMessage(logger, CompilationKind.ToolFallback, $"server rejected the request due to analyzer / generator issues '{combinedMessage}'"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, $"server rejected the request due to analyzer / generator issues '{combinedMessage}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); default: - LogCompilationMessage(logger, CompilationKind.ToolFallback, $"server gave an unrecognized response"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, $"server gave an unrecognized response"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } } @@ -689,7 +697,7 @@ private int HandleResponse(BuildResponse? response, string pathToTool, string re /// These are intended to be processed by automation in the binlog hence do not change the structure of /// the messages here. /// - private void LogCompilationMessage(ICompilerServerLogger logger, CompilationKind kind, string diagnostic) + private void LogCompilationMessage(ICompilerServerLogger logger, Guid requestId, CompilationKind kind, string diagnostic) { var category = kind switch { @@ -700,14 +708,15 @@ private void LogCompilationMessage(ICompilerServerLogger logger, CompilationKind _ => throw new Exception($"Unexpected value {kind}"), }; - var message = $"CompilerServer: {category} - {diagnostic}"; - logger.LogError(message); + var message = $"CompilerServer: {category} - {diagnostic} - {requestId}"; if (kind == CompilationKind.FatalError) { + logger.LogError(message); Log.LogError(message); } else { + logger.Log(message); Log.LogMessage(message); } } diff --git a/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs b/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs index e99ba6157a77f..0502d880fe3fa 100644 --- a/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs +++ b/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Resources; using Microsoft.Build.Utilities; namespace Microsoft.CodeAnalysis.BuildTasks @@ -35,6 +36,11 @@ protected string PathToManagedToolWithoutExtension /// protected abstract string PathToNativeTool { get; } + protected ManagedToolTask(ResourceManager resourceManager) + : base(resourceManager) + { + } + /// /// GenerateCommandLineCommands generates the actual OS-level arguments: /// if dotnet needs to be executed and the managed assembly is the first argument, diff --git a/src/Compilers/Core/MSBuildTaskTests/CscTests.cs b/src/Compilers/Core/MSBuildTaskTests/CscTests.cs index af98c043ca563..f3a6c128630c2 100644 --- a/src/Compilers/Core/MSBuildTaskTests/CscTests.cs +++ b/src/Compilers/Core/MSBuildTaskTests/CscTests.cs @@ -10,6 +10,7 @@ using Moq; using System.IO; using Roslyn.Test.Utilities; +using Microsoft.CodeAnalysis.BuildTasks.UnitTests.TestUtilities; namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests { @@ -498,5 +499,22 @@ public void SkipAnalyzersFlag() csc.Sources = MSBuildUtil.CreateTaskItems("test.cs"); Assert.Equal("/out:test.exe test.cs", csc.GenerateResponseFileContents()); } + + [Fact] + [WorkItem(52467, "https://github.com/dotnet/roslyn/issues/52467")] + public void UnexpectedExceptionLogsMessage() + { + var engine = new MockEngine(); + var csc = new Csc() + { + BuildEngine = engine, + }; + + csc.ExecuteTool(@"q:\path\csc.exe", "", "", new TestableCompilerServerLogger() + { + LogFunc = delegate { throw new Exception(""); } + }); + Assert.False(string.IsNullOrEmpty(engine.Log)); + } } } diff --git a/src/Compilers/Core/MSBuildTaskTests/DotNetSdkTests.cs b/src/Compilers/Core/MSBuildTaskTests/DotNetSdkTests.cs index f8c1c76501c74..0261f3bf7ba54 100644 --- a/src/Compilers/Core/MSBuildTaskTests/DotNetSdkTests.cs +++ b/src/Compilers/Core/MSBuildTaskTests/DotNetSdkTests.cs @@ -324,7 +324,7 @@ public void InitializeSourceRootMappedPathsReturnsSourceMap(bool deterministicSo VerifyValues( customProps: $@" - + ", customTargets: null, @@ -407,7 +407,11 @@ public void TestDiscoverEditorConfigFiles() var editorConfigFile2 = subdir.CreateFile(".editorconfig").WriteAllText(@"[*.cs] some_prop = some_val"); VerifyValues( - customProps: null, + customProps: @" + + + false +", customTargets: null, targets: new[] { @@ -436,7 +440,9 @@ public void TestDiscoverEditorConfigFilesCanBeDisabled() VerifyValues( customProps: @" - false + false + + false ", customTargets: null, targets: new[] @@ -462,7 +468,11 @@ public void TestDiscoverGlobalConfigFiles() some_prop = some_val"); VerifyValues( - customProps: null, + customProps: @" + + + false +", customTargets: null, targets: new[] { @@ -494,7 +504,9 @@ public void TestDiscoverGlobalConfigFilesCanBeDisabled() VerifyValues( customProps: @" - false + false + + false ", customTargets: null, targets: new[] @@ -525,7 +537,9 @@ public void TestDiscoverGlobalConfigFilesWhenEditorConfigDisabled() VerifyValues( customProps: @" - false + false + + false ", customTargets: null, targets: new[] @@ -585,10 +599,12 @@ public void TestDiscoverEditorAndGlobalConfigFilesCanBeDisabled() VerifyValues( customProps: @" - + false false -", + + false + ", customTargets: null, targets: new[] { @@ -610,9 +626,13 @@ public void TestGlobalConfigsCanBeManuallyAdded() VerifyValues( customProps: @" - + + + false + + -", + ", customTargets: null, targets: new[] { diff --git a/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs b/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs index 19a0af562c043..5ce1dc1604995 100644 --- a/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs +++ b/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs @@ -97,6 +97,35 @@ public void MultipleItemMetaDataCreatesSections() ", result); } + [Fact] + [WorkItem(52469, "https://github.com/dotnet/roslyn/issues/52469")] + public void MultipleSpecialCharacterItemMetaDataCreatesSections() + { + ITaskItem item1 = MSBuildUtil.CreateTaskItem("c:/{f*i?le1}.cs", new Dictionary { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "abc123" } }); + ITaskItem item2 = MSBuildUtil.CreateTaskItem("c:/f,ile#2.cs", new Dictionary { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "def456" } }); + ITaskItem item3 = MSBuildUtil.CreateTaskItem("c:/f;i!le[3].cs", new Dictionary { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "ghi789" } }); + + GenerateMSBuildEditorConfig configTask = new GenerateMSBuildEditorConfig() + { + MetadataItems = new[] { item1, item2, item3 } + }; + configTask.Execute(); + + var result = configTask.ConfigFileContents; + + Assert.Equal(@"is_global = true + +[c:/\{f\*i\?le1\}.cs] +build_metadata.Compile.ToRetrieve = abc123 + +[c:/f\,ile\#2.cs] +build_metadata.Compile.ToRetrieve = def456 + +[c:/f\;i\!le\[3\].cs] +build_metadata.Compile.ToRetrieve = ghi789 +", result); + } + [Fact] public void DuplicateItemSpecsAreCombinedInSections() { diff --git a/src/Compilers/Core/MSBuildTaskTests/TestUtilities/TestableCompilerServerLogger.cs b/src/Compilers/Core/MSBuildTaskTests/TestUtilities/TestableCompilerServerLogger.cs new file mode 100644 index 0000000000000..5cb8fcfe2b20a --- /dev/null +++ b/src/Compilers/Core/MSBuildTaskTests/TestUtilities/TestableCompilerServerLogger.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.CommandLine; + +namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests.TestUtilities +{ + internal sealed class TestableCompilerServerLogger : ICompilerServerLogger + { + public bool IsLogging { get; set; } + public Action LogFunc { get; set; } = delegate { throw new InvalidOperationException(); }; + + public void Log(string message) => LogFunc(message); + } +} diff --git a/src/Compilers/Core/MSBuildTaskTests/VbcTests.cs b/src/Compilers/Core/MSBuildTaskTests/VbcTests.cs index f65aea8bc4198..e118161a64553 100644 --- a/src/Compilers/Core/MSBuildTaskTests/VbcTests.cs +++ b/src/Compilers/Core/MSBuildTaskTests/VbcTests.cs @@ -5,6 +5,7 @@ using System; using System.IO; using Microsoft.CodeAnalysis.BuildTasks; +using Microsoft.CodeAnalysis.BuildTasks.UnitTests.TestUtilities; using Roslyn.Test.Utilities; using Xunit; @@ -462,5 +463,22 @@ public void SkipAnalyzersFlag() vbc.Sources = MSBuildUtil.CreateTaskItems("test.vb"); Assert.Equal("/optionstrict:custom /out:test.exe test.vb", vbc.GenerateResponseFileContents()); } + + [Fact] + [WorkItem(52467, "https://github.com/dotnet/roslyn/issues/52467")] + public void UnexpectedExceptionLogsMessage() + { + var engine = new MockEngine(); + var vbc = new Vbc() + { + BuildEngine = engine, + }; + + vbc.ExecuteTool(@"q:\path\vbc.exe", "", "", new TestableCompilerServerLogger() + { + LogFunc = delegate { throw new Exception(""); } + }); + Assert.False(string.IsNullOrEmpty(engine.Log)); + } } } diff --git a/src/Compilers/Core/Portable/Collections/ArrayBuilderExtensions.cs b/src/Compilers/Core/Portable/Collections/ArrayBuilderExtensions.cs index 029c738ac7ddd..bc28d649303ca 100644 --- a/src/Compilers/Core/Portable/Collections/ArrayBuilderExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ArrayBuilderExtensions.cs @@ -137,6 +137,46 @@ public static ImmutableArray SelectAsArray(this A } } + /// + /// Maps an array builder to immutable array. + /// + /// + /// + /// + /// The sequence to map + /// The mapping delegate + /// The extra input used by mapping delegate + /// If the items's length is 0, this will return an empty immutable array. + public static ImmutableArray SelectAsArrayWithIndex(this ArrayBuilder items, Func map, TArg arg) + { + switch (items.Count) + { + case 0: + return ImmutableArray.Empty; + + case 1: + return ImmutableArray.Create(map(items[0], 0, arg)); + + case 2: + return ImmutableArray.Create(map(items[0], 0, arg), map(items[1], 1, arg)); + + case 3: + return ImmutableArray.Create(map(items[0], 0, arg), map(items[1], 1, arg), map(items[2], 2, arg)); + + case 4: + return ImmutableArray.Create(map(items[0], 0, arg), map(items[1], 1, arg), map(items[2], 2, arg), map(items[3], 3, arg)); + + default: + var builder = ArrayBuilder.GetInstance(items.Count); + foreach (var item in items) + { + builder.Add(map(item, builder.Count, arg)); + } + + return builder.ToImmutableAndFree(); + } + } + public static void AddOptional(this ArrayBuilder builder, T? item) where T : class { diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index 8c16096b12924..11f1d789a1bc1 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -223,7 +223,6 @@ public static ImmutableArray SelectAsArray(this Immutab return builder.ToImmutableAndFree(); } - /// /// Maps an immutable array through a function that returns ValueTasks, returning the new ImmutableArray. /// @@ -778,5 +777,50 @@ internal static bool SequenceEqual(this ImmutableArray internal static int IndexOf(this ImmutableArray array, T item, IEqualityComparer comparer) => array.IndexOf(item, startIndex: 0, comparer); + + internal static bool IsSorted(this ImmutableArray array, IComparer comparer) + { + for (var i = 1; i < array.Length; i++) + { + if (comparer.Compare(array[i - 1], array[i]) > 0) + { + return false; + } + } + + return true; + } + + // same as Array.BinarySearch but the ability to pass arbitrary value to the comparer without allocation + internal static int BinarySearch(this ImmutableArray array, TValue value, Func comparer) + => BinarySearch(array.AsSpan(), value, comparer); + + internal static int BinarySearch(this ReadOnlySpan array, TValue value, Func comparer) + { + int low = 0; + int high = array.Length - 1; + + while (low <= high) + { + int middle = low + ((high - low) >> 1); + int comparison = comparer(array[middle], value); + + if (comparison == 0) + { + return middle; + } + + if (comparison > 0) + { + high = middle - 1; + } + else + { + low = middle + 1; + } + } + + return ~low; + } } } diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index 960a07d8e35af..c936acfede87e 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -1115,6 +1115,15 @@ private void CompileAndEmit( } } + // Need to ensure the PDB file path validation is done on the original path as that is the + // file we will write out to disk, there is no guarantee that the file paths emitted into + // the PE / PDB are valid file paths because pathmap can be used to create deliberately + // illegal names + if (!PathUtilities.IsValidFilePath(finalPdbFilePath)) + { + diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.FTL_InvalidInputFileName, Location.None, finalPdbFilePath)); + } + var moduleBeingBuilt = compilation.CheckOptionsAndCreateModuleBuilder( diagnostics, Arguments.ManifestResources, @@ -1219,7 +1228,7 @@ private void CompileAndEmit( // only report unused usings if we have success. if (success) { - compilation.ReportUnusedImports(null, diagnostics, cancellationToken); + compilation.ReportUnusedImports(diagnostics, cancellationToken); } } @@ -1273,7 +1282,7 @@ private void CompileAndEmit( peStreamProvider, refPeStreamProviderOpt, pdbStreamProviderOpt, - pdbOptionsBlobReader: null, + rebuildData: null, testSymWriterFactory: null, diagnostics: diagnostics, emitOptions: emitOptions, diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index cba9e183cc399..767b815c8fb16 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -973,7 +973,6 @@ public IPointerTypeSymbol CreatePointerTypeSymbol(ITypeSymbol pointedAtType) protected abstract IPointerTypeSymbol CommonCreatePointerTypeSymbol(ITypeSymbol elementType); - /// /// Returns a new IFunctionPointerTypeSymbol representing a function pointer type tied to types in this /// Compilation. @@ -2254,7 +2253,6 @@ internal abstract bool GenerateResourcesAndDocumentationComments( /// Reports all unused imports/usings so far (and thus it must be called as a last step of Emit) /// internal abstract void ReportUnusedImports( - SyntaxTree? filterTree, DiagnosticBag diagnostics, CancellationToken cancellationToken); @@ -2468,14 +2466,13 @@ public EmitResult Emit( pdbStream, xmlDocumentationStream, win32Resources, - useRawWin32Resources: false, manifestResources, options, debugEntryPoint, sourceLinkStream, embeddedTexts, metadataPEStream, - pdbOptionsBlobReader: null, + rebuildData: null, cancellationToken); } @@ -2484,14 +2481,13 @@ internal EmitResult Emit( Stream? pdbStream, Stream? xmlDocumentationStream, Stream? win32Resources, - bool useRawWin32Resources, IEnumerable? manifestResources, EmitOptions? options, IMethodSymbol? debugEntryPoint, Stream? sourceLinkStream, IEnumerable? embeddedTexts, Stream? metadataPEStream, - BlobReader? pdbOptionsBlobReader, + RebuildData? rebuildData, CancellationToken cancellationToken) { if (peStream == null) @@ -2583,13 +2579,12 @@ internal EmitResult Emit( pdbStream, xmlDocumentationStream, win32Resources, - useRawWin32Resources, manifestResources, options, debugEntryPoint, sourceLinkStream, embeddedTexts, - pdbOptionsBlobReader, + rebuildData, testData: null, cancellationToken: cancellationToken); } @@ -2604,13 +2599,12 @@ internal EmitResult Emit( Stream? pdbStream, Stream? xmlDocumentationStream, Stream? win32Resources, - bool useRawWin32Resources, IEnumerable? manifestResources, EmitOptions? options, IMethodSymbol? debugEntryPoint, Stream? sourceLinkStream, IEnumerable? embeddedTexts, - BlobReader? pdbOptionsBlobReader, + RebuildData? rebuildData, CompilationTestData? testData, CancellationToken cancellationToken) { @@ -2655,7 +2649,7 @@ internal EmitResult Emit( moduleBeingBuilt, xmlDocumentationStream, win32Resources, - useRawWin32Resources, + useRawWin32Resources: rebuildData is object, options.OutputNameOverride, diagnostics, cancellationToken)) @@ -2665,7 +2659,7 @@ internal EmitResult Emit( if (success) { - ReportUnusedImports(null, diagnostics, cancellationToken); + ReportUnusedImports(diagnostics, cancellationToken); } } } @@ -2692,7 +2686,7 @@ internal EmitResult Emit( new SimpleEmitStreamProvider(peStream), (metadataPEStream != null) ? new SimpleEmitStreamProvider(metadataPEStream) : null, (pdbStream != null) ? new SimpleEmitStreamProvider(pdbStream) : null, - pdbOptionsBlobReader, + rebuildData, testData?.SymWriterFactory, diagnostics, emitOptions: options, @@ -2704,6 +2698,7 @@ internal EmitResult Emit( return new EmitResult(success, diagnostics.ToReadOnlyAndFree()); } +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters /// /// Emit the differences between the compilation and the previous generation /// for Edit and Continue. The differences are expressed as added and changed @@ -2711,6 +2706,7 @@ internal EmitResult Emit( /// of the current compilation is returned as an EmitBaseline for use in a /// subsequent Edit and Continue. /// + [Obsolete("UpdatedMethods is now part of EmitDifferenceResult, so you should use an overload that doesn't take it.")] public EmitDifferenceResult EmitDifference( EmitBaseline baseline, IEnumerable edits, @@ -2730,6 +2726,7 @@ public EmitDifferenceResult EmitDifference( /// of the current compilation is returned as an EmitBaseline for use in a /// subsequent Edit and Continue. /// + [Obsolete("UpdatedMethods is now part of EmitDifferenceResult, so you should use an overload that doesn't take it.")] public EmitDifferenceResult EmitDifference( EmitBaseline baseline, IEnumerable edits, @@ -2739,6 +2736,32 @@ public EmitDifferenceResult EmitDifference( Stream pdbStream, ICollection updatedMethods, CancellationToken cancellationToken = default(CancellationToken)) + { + var diff = EmitDifference(baseline, edits, isAddedSymbol, metadataStream, ilStream, pdbStream, cancellationToken); + + foreach (var token in diff.UpdatedMethods) + { + updatedMethods.Add(token); + } + + return diff; + } + + /// + /// Emit the differences between the compilation and the previous generation + /// for Edit and Continue. The differences are expressed as added and changed + /// symbols, and are emitted as metadata, IL, and PDB deltas. A representation + /// of the current compilation is returned as an EmitBaseline for use in a + /// subsequent Edit and Continue. + /// + public EmitDifferenceResult EmitDifference( + EmitBaseline baseline, + IEnumerable edits, + Func isAddedSymbol, + Stream metadataStream, + Stream ilStream, + Stream pdbStream, + CancellationToken cancellationToken = default(CancellationToken)) { if (baseline == null) { @@ -2773,8 +2796,9 @@ public EmitDifferenceResult EmitDifference( throw new ArgumentNullException(nameof(pdbStream)); } - return this.EmitDifference(baseline, edits, isAddedSymbol, metadataStream, ilStream, pdbStream, updatedMethods, testData: null, cancellationToken); + return this.EmitDifference(baseline, edits, isAddedSymbol, metadataStream, ilStream, pdbStream, testData: null, cancellationToken); } +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters internal abstract EmitDifferenceResult EmitDifference( EmitBaseline baseline, @@ -2783,7 +2807,6 @@ internal abstract EmitDifferenceResult EmitDifference( Stream metadataStream, Stream ilStream, Stream pdbStream, - ICollection updatedMethodHandles, CompilationTestData? testData, CancellationToken cancellationToken); @@ -2854,7 +2877,7 @@ internal bool SerializeToPeStream( EmitStreamProvider peStreamProvider, EmitStreamProvider? metadataPEStreamProvider, EmitStreamProvider? pdbStreamProvider, - BlobReader? pdbOptionsBlobReader, + RebuildData? rebuildData, Func? testSymWriterFactory, DiagnosticBag diagnostics, EmitOptions emitOptions, @@ -2928,7 +2951,7 @@ internal bool SerializeToPeStream( getPortablePdbStream, nativePdbWriter, pePdbFilePath, - pdbOptionsBlobReader, + rebuildData, emitOptions.EmitMetadataOnly, emitOptions.IncludePrivateMembers, deterministic, @@ -3010,7 +3033,7 @@ internal static bool SerializePeToStream( Func? getPortablePdbStreamOpt, Cci.PdbWriter? nativePdbWriterOpt, string? pdbPathOpt, - BlobReader? pdbOptionsBlobReader, + RebuildData? rebuildData, bool metadataOnly, bool includePrivateMembers, bool isDeterministic, @@ -3023,12 +3046,11 @@ internal static bool SerializePeToStream( bool includePrivateMembersOnPrimaryOutput = metadataOnly ? includePrivateMembers : true; bool deterministicPrimaryOutput = (metadataOnly && !includePrivateMembers) || isDeterministic; if (!Cci.PeWriter.WritePeToStream( - new EmitContext(moduleBeingBuilt, null, metadataDiagnostics, metadataOnly, includePrivateMembersOnPrimaryOutput), + new EmitContext(moduleBeingBuilt, metadataDiagnostics, metadataOnly, includePrivateMembersOnPrimaryOutput, rebuildData: rebuildData), messageProvider, getPeStream, getPortablePdbStreamOpt, nativePdbWriterOpt, - pdbOptionsBlobReader, pdbPathOpt, metadataOnly, deterministicPrimaryOutput, @@ -3046,12 +3068,11 @@ internal static bool SerializePeToStream( Debug.Assert(!includePrivateMembers); if (!Cci.PeWriter.WritePeToStream( - new EmitContext(moduleBeingBuilt, null, metadataDiagnostics, metadataOnly: true, includePrivateMembers: false), + new EmitContext(moduleBeingBuilt, syntaxNode: null, metadataDiagnostics, metadataOnly: true, includePrivateMembers: false), messageProvider, getMetadataPeStreamOpt, getPortablePdbStreamOpt: null, nativePdbWriterOpt: null, - pdbOptionsBlobReader: null, pdbPathOpt: null, metadataOnly: true, isDeterministic: true, @@ -3074,7 +3095,8 @@ internal static bool SerializePeToStream( Stream metadataStream, Stream ilStream, Stream pdbStream, - ICollection updatedMethods, + ArrayBuilder updatedMethods, + ArrayBuilder updatedTypes, DiagnosticBag diagnostics, Func? testSymWriterFactory, string? pdbFilePath, @@ -3088,7 +3110,7 @@ internal static bool SerializePeToStream( using (nativePdbWriter) { - var context = new EmitContext(moduleBeingBuilt, null, diagnostics, metadataOnly: false, includePrivateMembers: true); + var context = new EmitContext(moduleBeingBuilt, diagnostics, metadataOnly: false, includePrivateMembers: true); var encId = Guid.NewGuid(); try @@ -3107,10 +3129,11 @@ internal static bool SerializePeToStream( metadataStream, ilStream, (nativePdbWriter == null) ? pdbStream : null, - pdbOptionsBlobReader: null, out MetadataSizes metadataSizes); - writer.GetMethodTokens(updatedMethods); + writer.GetUpdatedMethodTokens(updatedMethods); + + writer.GetUpdatedTypeTokens(updatedTypes); nativePdbWriter?.WriteTo(pdbStream); @@ -3153,7 +3176,7 @@ private ConcurrentDictionary TreeToUsedImp } } - internal void MarkImportDirectiveAsUsed(SyntaxNode node) + internal void MarkImportDirectiveAsUsed(SyntaxReference node) { MarkImportDirectiveAsUsed(node.SyntaxTree, node.Span.Start); } diff --git a/src/Compilers/Core/Portable/Compilation/ParseOptions.cs b/src/Compilers/Core/Portable/Compilation/ParseOptions.cs index e56f948a52dc7..51aa13c819159 100644 --- a/src/Compilers/Core/Portable/Compilation/ParseOptions.cs +++ b/src/Compilers/Core/Portable/Compilation/ParseOptions.cs @@ -32,9 +32,8 @@ public abstract class ParseOptions public SourceCodeKind SpecifiedKind { get; protected set; } /// - /// Gets a value indicating whether the documentation comments are parsed. + /// Gets a value indicating whether the documentation comments are parsed and analyzed. /// - /// true if documentation comments are parsed, false otherwise. public DocumentationMode DocumentationMode { get; protected set; } internal ParseOptions(SourceCodeKind kind, DocumentationMode documentationMode) diff --git a/src/Compilers/Core/Portable/Compilation/RebuildData.cs b/src/Compilers/Core/Portable/Compilation/RebuildData.cs new file mode 100644 index 0000000000000..bb63143324fb0 --- /dev/null +++ b/src/Compilers/Core/Portable/Compilation/RebuildData.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Reflection.Metadata; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis +{ + internal sealed class RebuildData + { + /// + /// This represents the set of document names for the #line / #ExternalSource directives + /// that we need to emit into the PDB (in the order specified in the array). + /// + internal ImmutableArray NonSourceFileDocumentNames { get; } + + internal BlobReader OptionsBlobReader { get; } + + internal RebuildData( + BlobReader optionsBlobReader, + ImmutableArray nonSourceFileDocumentNames) + { + OptionsBlobReader = optionsBlobReader; + NonSourceFileDocumentNames = nonSourceFileDocumentNames; + } + } +} diff --git a/src/Compilers/Core/Portable/Diagnostic/FileLinePositionSpan.cs b/src/Compilers/Core/Portable/Diagnostic/FileLinePositionSpan.cs index f5a7c9e586f52..df7d06cf2f3f3 100644 --- a/src/Compilers/Core/Portable/Diagnostic/FileLinePositionSpan.cs +++ b/src/Compilers/Core/Portable/Diagnostic/FileLinePositionSpan.cs @@ -12,19 +12,15 @@ namespace Microsoft.CodeAnalysis /// Represents a span of text in a source code file in terms of file name, line number, and offset within line. /// However, the file is actually whatever was passed in when asked to parse; there may not really be a file. /// - public struct FileLinePositionSpan : IEquatable + public readonly struct FileLinePositionSpan : IEquatable { - private readonly string _path; - private readonly LinePositionSpan _span; - private readonly bool _hasMappedPath; - /// /// Path, or null if the span represents an invalid value. /// /// /// Path may be if not available. /// - public string Path { get { return _path; } } + public string Path { get; } /// /// True if the is a mapped path. @@ -32,30 +28,12 @@ public struct FileLinePositionSpan : IEquatable /// /// A mapped path is a path specified in source via #line (C#) or #ExternalSource (VB) directives. /// - public bool HasMappedPath { get { return _hasMappedPath; } } - - /// - /// Gets the of the start of the span. - /// - /// - public LinePosition StartLinePosition { get { return _span.Start; } } - - /// - /// Gets the of the end of the span. - /// - /// - public LinePosition EndLinePosition { get { return _span.End; } } + public bool HasMappedPath { get; } /// /// Gets the span. /// - public LinePositionSpan Span - { - get - { - return _span; - } - } + public LinePositionSpan Span { get; } /// /// Initializes the instance. @@ -77,34 +55,35 @@ public FileLinePositionSpan(string path, LinePosition start, LinePosition end) /// is null. public FileLinePositionSpan(string path, LinePositionSpan span) { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } - - _path = path; - _span = span; - _hasMappedPath = false; + Path = path ?? throw new ArgumentNullException(nameof(path)); + Span = span; + HasMappedPath = false; } internal FileLinePositionSpan(string path, LinePositionSpan span, bool hasMappedPath) { - _path = path; - _span = span; - _hasMappedPath = hasMappedPath; + Path = path; + Span = span; + HasMappedPath = hasMappedPath; } + /// + /// Gets the of the start of the span. + /// + /// + public LinePosition StartLinePosition => Span.Start; + + /// + /// Gets the of the end of the span. + /// + /// + public LinePosition EndLinePosition => Span.End; + /// /// Returns true if the span represents a valid location. /// public bool IsValid - { - get - { - // invalid span can be constructed by new FileLinePositionSpan() - return _path != null; - } - } + => Path != null; // invalid span can be constructed by new FileLinePositionSpan() /// /// Determines if two FileLinePositionSpan objects are equal. @@ -113,19 +92,15 @@ public bool IsValid /// The path is treated as an opaque string, i.e. a case-sensitive comparison is used. /// public bool Equals(FileLinePositionSpan other) - { - return _span.Equals(other._span) - && _hasMappedPath == other._hasMappedPath - && string.Equals(_path, other._path, StringComparison.Ordinal); - } + => Span.Equals(other.Span) && + HasMappedPath == other.HasMappedPath && + string.Equals(Path, other.Path, StringComparison.Ordinal); /// /// Determines if two FileLinePositionSpan objects are equal. /// public override bool Equals(object? other) - { - return other is FileLinePositionSpan && Equals((FileLinePositionSpan)other); - } + => other is FileLinePositionSpan span && Equals(span); /// /// Serves as a hash function for FileLinePositionSpan. @@ -135,18 +110,20 @@ public override bool Equals(object? other) /// The path is treated as an opaque string, i.e. a case-sensitive hash is calculated. /// public override int GetHashCode() - { - return Hash.Combine(_path, Hash.Combine(_hasMappedPath, _span.GetHashCode())); - } + => Hash.Combine(Path, Hash.Combine(HasMappedPath, Span.GetHashCode())); /// - /// Returns a that represents FileLinePositionSpan. + /// Returns a that represents . /// - /// The string representation of FileLinePositionSpan. + /// The string representation of . /// Path: (0,0)-(5,6) public override string ToString() - { - return _path + ": " + _span; - } + => Path + ": " + Span; + + public static bool operator ==(FileLinePositionSpan left, FileLinePositionSpan right) + => left.Equals(right); + + public static bool operator !=(FileLinePositionSpan left, FileLinePositionSpan right) + => !(left == right); } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerOptionsExtensions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerOptionsExtensions.cs index 8cfaa845e9e49..cbb15d214eafa 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerOptionsExtensions.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerOptionsExtensions.cs @@ -51,7 +51,8 @@ public static bool TryGetSeverityFromBulkConfiguration( // bulk configuration should not be applied. // For example, 'dotnet_diagnostic.CA1000.severity = error' if (compilation.Options.SpecificDiagnosticOptions.ContainsKey(descriptor.Id) || - compilation.Options.SyntaxTreeOptionsProvider?.TryGetDiagnosticValue(tree, descriptor.Id, cancellationToken, out _) == true) + compilation.Options.SyntaxTreeOptionsProvider?.TryGetDiagnosticValue(tree, descriptor.Id, cancellationToken, out _) == true || + compilation.Options.SyntaxTreeOptionsProvider?.TryGetGlobalDiagnosticValue(descriptor.Id, cancellationToken, out _) == true) { severity = default; return false; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetSymbolResolver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetSymbolResolver.cs index 3e28059861609..999c8c5515218 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetSymbolResolver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetSymbolResolver.cs @@ -144,7 +144,7 @@ public ImmutableArray Resolve(out bool resolvedWithDocCommentIdFormat) break; } } - else if (nextChar == '.' || nextChar == '+') + else if (nextChar is '.' or '+') { ++_index; @@ -219,8 +219,8 @@ public ImmutableArray Resolve(out bool resolvedWithDocCommentIdFormat) else { singleResult = candidateMembers.FirstOrDefault(s => - s.Kind != SymbolKind.Namespace && - s.Kind != SymbolKind.NamedType); + s.Kind is not SymbolKind.Namespace and + not SymbolKind.NamedType); } break; @@ -305,11 +305,11 @@ private int ReadNextInteger() private ParameterInfo[] ParseParameterList() { // Consume the opening parenthesis or bracket - Debug.Assert(PeekNextChar() == '(' || PeekNextChar() == '['); + Debug.Assert(PeekNextChar() is '(' or '['); ++_index; var nextChar = PeekNextChar(); - if (nextChar == ')' || nextChar == ']') + if (nextChar is ')' or ']') { // Empty parameter list ++_index; @@ -342,7 +342,7 @@ private ParameterInfo[] ParseParameterList() } nextChar = PeekNextChar(); - if (nextChar == ')' || nextChar == ']') + if (nextChar is ')' or ']') { // Consume the closing parenthesis or bracket ++_index; @@ -612,7 +612,7 @@ private void IgnorePointerAndArraySpecifiers() } var nextChar = PeekNextChar(); - if (nextChar == '.' || nextChar == '+') + if (nextChar is '.' or '+') { ++_index; @@ -829,8 +829,8 @@ private static INamespaceOrTypeSymbol GetFirstMatchingNamespaceOrType(ImmutableA { return (INamespaceOrTypeSymbol)candidateMembers .FirstOrDefault(s => - s.Kind == SymbolKind.Namespace || - s.Kind == SymbolKind.NamedType); + s.Kind is SymbolKind.Namespace or + SymbolKind.NamedType); } private static ITypeParameterSymbol GetNthTypeParameter(INamedTypeSymbol typeSymbol, int n) diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index 628615eff3261..45b56ed959199 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -965,7 +965,7 @@ public sealed override Cci.ITypeReference GetPlatformType(Cci.PlatformType platf throw ExceptionUtilities.UnexpectedValue(platformType); default: - return GetSpecialType((SpecialType)platformType, (TSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return GetSpecialType((SpecialType)platformType, (TSyntaxNode)context.SyntaxNode, context.Diagnostics); } } } diff --git a/src/Compilers/Core/Portable/Emit/Context.cs b/src/Compilers/Core/Portable/Emit/Context.cs index 737516762c08b..30967d675e6cd 100644 --- a/src/Compilers/Core/Portable/Emit/Context.cs +++ b/src/Compilers/Core/Portable/Emit/Context.cs @@ -2,17 +2,16 @@ // 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.Diagnostics; namespace Microsoft.CodeAnalysis.Emit { - internal struct EmitContext + internal readonly struct EmitContext { public readonly CommonPEModuleBuilder Module; - public readonly SyntaxNode SyntaxNodeOpt; + public readonly SyntaxNode? SyntaxNode; + public readonly RebuildData? RebuildData; public readonly DiagnosticBag Diagnostics; private readonly Flags _flags; @@ -20,14 +19,28 @@ internal struct EmitContext public bool MetadataOnly => (_flags & Flags.MetadataOnly) != 0; public bool IsRefAssembly => MetadataOnly && !IncludePrivateMembers; - public EmitContext(CommonPEModuleBuilder module, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics, bool metadataOnly, bool includePrivateMembers) + public EmitContext(CommonPEModuleBuilder module, SyntaxNode? syntaxNode, DiagnosticBag diagnostics, bool metadataOnly, bool includePrivateMembers) + : this(module, diagnostics, metadataOnly, includePrivateMembers, syntaxNode, rebuildData: null) + { + } + + public EmitContext( + CommonPEModuleBuilder module, + DiagnosticBag diagnostics, + bool metadataOnly, + bool includePrivateMembers, + SyntaxNode? syntaxNode = null, + RebuildData? rebuildData = null) { + Debug.Assert(rebuildData is null || !metadataOnly); + RebuildData = rebuildData; Debug.Assert(module != null); Debug.Assert(diagnostics != null); Debug.Assert(includePrivateMembers || metadataOnly); Module = module; - SyntaxNodeOpt = syntaxNodeOpt; + SyntaxNode = syntaxNode; + RebuildData = rebuildData; Diagnostics = diagnostics; Flags flags = Flags.None; diff --git a/src/Compilers/Core/Portable/Emit/DebugDocumentsBuilder.cs b/src/Compilers/Core/Portable/Emit/DebugDocumentsBuilder.cs index 30635a8d71216..c57bd62d12305 100644 --- a/src/Compilers/Core/Portable/Emit/DebugDocumentsBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/DebugDocumentsBuilder.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 Roslyn.Utilities; using System; using System.Collections.Concurrent; @@ -20,19 +18,19 @@ internal sealed class DebugDocumentsBuilder // NOTE: We are not considering how filesystem or debuggers do the comparisons, but how native implementations did. // Deviating from that may result in unexpected warnings or different behavior (possibly without warnings). private readonly ConcurrentDictionary _debugDocuments; - private readonly ConcurrentCache<(string, string), string> _normalizedPathsCache; - private readonly SourceReferenceResolver _resolverOpt; + private readonly ConcurrentCache<(string, string?), string> _normalizedPathsCache; + private readonly SourceReferenceResolver? _resolver; - public DebugDocumentsBuilder(SourceReferenceResolver resolverOpt, bool isDocumentNameCaseSensitive) + public DebugDocumentsBuilder(SourceReferenceResolver? resolver, bool isDocumentNameCaseSensitive) { - _resolverOpt = resolverOpt; + _resolver = resolver; _debugDocuments = new ConcurrentDictionary( isDocumentNameCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); - _normalizedPathsCache = new ConcurrentCache<(string, string), string>(16); + _normalizedPathsCache = new ConcurrentCache<(string, string?), string>(16); } internal int DebugDocumentCount => _debugDocuments.Count; @@ -45,14 +43,14 @@ internal void AddDebugDocument(Cci.DebugSourceDocument document) internal IReadOnlyDictionary DebugDocuments => _debugDocuments; - internal Cci.DebugSourceDocument TryGetDebugDocument(string path, string basePath) + internal Cci.DebugSourceDocument? TryGetDebugDocument(string path, string basePath) { return TryGetDebugDocumentForNormalizedPath(NormalizeDebugDocumentPath(path, basePath)); } - internal Cci.DebugSourceDocument TryGetDebugDocumentForNormalizedPath(string normalizedPath) + internal Cci.DebugSourceDocument? TryGetDebugDocumentForNormalizedPath(string normalizedPath) { - Cci.DebugSourceDocument document; + Cci.DebugSourceDocument? document; _debugDocuments.TryGetValue(normalizedPath, out document); return document; } @@ -62,18 +60,18 @@ internal Cci.DebugSourceDocument GetOrAddDebugDocument(string path, string baseP return _debugDocuments.GetOrAdd(NormalizeDebugDocumentPath(path, basePath), factory); } - internal string NormalizeDebugDocumentPath(string path, string basePath) + internal string NormalizeDebugDocumentPath(string path, string? basePath) { - if (_resolverOpt == null) + if (_resolver == null) { return path; } var key = (path, basePath); - string normalizedPath; + string? normalizedPath; if (!_normalizedPathsCache.TryGetValue(key, out normalizedPath)) { - normalizedPath = _resolverOpt.NormalizePath(path, basePath) ?? path; + normalizedPath = _resolver.NormalizePath(path, basePath) ?? path; _normalizedPathsCache.TryAdd(key, normalizedPath); } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 8b0536a6bdbaf..353c61b7f2e88 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -25,6 +25,7 @@ internal sealed class DeltaMetadataWriter : MetadataWriter private readonly DefinitionMap _definitionMap; private readonly SymbolChanges _changes; + private readonly List _updatedTypeDefs; private readonly DefinitionIndex _typeDefs; private readonly DefinitionIndex _eventDefs; private readonly DefinitionIndex _fieldDefs; @@ -76,6 +77,7 @@ public DeltaMetadataWriter( var sizes = previousGeneration.TableSizes; + _updatedTypeDefs = new List(); _typeDefs = new DefinitionIndex(this.TryGetExistingTypeDefIndex, sizes[(int)TableIndex.TypeDef]); _eventDefs = new DefinitionIndex(this.TryGetExistingEventDefIndex, sizes[(int)TableIndex.Event]); _fieldDefs = new DefinitionIndex(this.TryGetExistingFieldDefIndex, sizes[(int)TableIndex.Field]); @@ -216,7 +218,7 @@ private static IReadOnlyDictionary AddRange(IReadOnlyDictionary /// Return tokens for all modified debuggable methods. /// - public void GetMethodTokens(ICollection methods) + public void GetUpdatedMethodTokens(ArrayBuilder methods) { foreach (var def in _methodDefs.GetRows()) { @@ -228,6 +230,17 @@ public void GetMethodTokens(ICollection methods) } } + /// + /// Return tokens for all modified debuggable types. + /// + public void GetUpdatedTypeTokens(ArrayBuilder types) + { + foreach (var def in _updatedTypeDefs) + { + types.Add(MetadataTokens.TypeDefinitionHandle(_typeDefs[def])); + } + } + protected override ushort Generation { get { return (ushort)(_previousGeneration.Ordinal + 1); } @@ -444,10 +457,15 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) case SymbolChange.Updated: _typeDefs.AddUpdated(typeDef); + _updatedTypeDefs.Add(typeDef); break; case SymbolChange.ContainsChanges: // Members changed. + // We keep this list separately because we don't want to output duplicate typedef entries in the EnC log, + // which uses _typeDefs, but it's simpler to let the members output those rows for the updated typedefs + // with the right update type. + _updatedTypeDefs.Add(typeDef); break; case SymbolChange.None: diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitDifferenceResult.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitDifferenceResult.cs index 40af43efb470d..02e8e74297ab4 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitDifferenceResult.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitDifferenceResult.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Reflection.Metadata; namespace Microsoft.CodeAnalysis.Emit { @@ -10,10 +11,16 @@ public sealed class EmitDifferenceResult : EmitResult { public EmitBaseline? Baseline { get; } - internal EmitDifferenceResult(bool success, ImmutableArray diagnostics, EmitBaseline? baseline) : - base(success, diagnostics) + public ImmutableArray UpdatedMethods { get; } + + public ImmutableArray UpdatedTypes { get; } + + internal EmitDifferenceResult(bool success, ImmutableArray diagnostics, EmitBaseline? baseline, ImmutableArray updatedMethods, ImmutableArray updatedTypes) + : base(success, diagnostics) { Baseline = baseline; + UpdatedMethods = updatedMethods; + UpdatedTypes = updatedTypes; } } } diff --git a/src/Compilers/Core/Portable/Emit/EmitOptions.cs b/src/Compilers/Core/Portable/Emit/EmitOptions.cs index 4bd822203002c..b8234bc4790f3 100644 --- a/src/Compilers/Core/Portable/Emit/EmitOptions.cs +++ b/src/Compilers/Core/Portable/Emit/EmitOptions.cs @@ -366,11 +366,6 @@ internal void ValidateOptions(DiagnosticBag diagnostics, CommonMessageProvider m { diagnostics.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_InvalidHashAlgorithmName, Location.None, "")); } - - if (PdbFilePath != null && !PathUtilities.IsValidFilePath(PdbFilePath)) - { - diagnostics.Add(messageProvider.CreateDiagnostic(messageProvider.FTL_InvalidInputFileName, Location.None, PdbFilePath)); - } } internal bool EmitTestCoverageData => InstrumentationKinds.Contains(InstrumentationKind.TestCoverage); diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedEvent.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedEvent.cs index 4d45c6dbb7809..5d20dc1ed8a01 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedEvent.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedEvent.cs @@ -142,7 +142,7 @@ bool Cci.IEventDefinition.IsSpecialName Cci.ITypeReference Cci.IEventDefinition.GetType(EmitContext context) { - return GetType((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return GetType((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNode, context.Diagnostics); } protected TEmbeddedMethod AnAccessor diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs index 819e8ed97ac0f..fc8127092658e 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs @@ -90,7 +90,7 @@ private ImmutableArray GetAttributes(TPEModuleBuilder moduleBuil if (_lazyAttributes.IsDefault) { var diagnostics = DiagnosticBag.GetInstance(); - var attributes = GetAttributes((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNodeOpt, diagnostics); + var attributes = GetAttributes((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNode, diagnostics); if (ImmutableInterlocked.InterlockedInitialize(ref _lazyAttributes, attributes)) { diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs index b7b6c380bd291..6794f9e95faf8 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs @@ -194,7 +194,7 @@ ImmutableArray Cci.IParameterDefinition.MarshallingDescriptor if (_lazyAttributes.IsDefault) { var diagnostics = DiagnosticBag.GetInstance(); - var attributes = GetAttributes((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNodeOpt, diagnostics); + var attributes = GetAttributes((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNode, diagnostics); if (ImmutableInterlocked.InterlockedInitialize(ref _lazyAttributes, attributes)) { diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs index cc20e208aacfe..c5bf550d98e2a 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs @@ -236,7 +236,7 @@ bool Cci.INamespaceTypeDefinition.IsPublic Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) { - return GetBaseClass((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNodeOpt, context.Diagnostics); + return GetBaseClass((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNode, context.Diagnostics); } IEnumerable Cci.ITypeDefinition.GetEvents(EmitContext context) @@ -533,7 +533,7 @@ System.Runtime.InteropServices.CharSet Cci.ITypeDefinition.StringFormat if (_lazyAttributes.IsDefault) { var diagnostics = DiagnosticBag.GetInstance(); - var attributes = GetAttributes((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNodeOpt, diagnostics); + var attributes = GetAttributes((TPEModuleBuilder)context.Module, (TSyntaxNode)context.SyntaxNode, diagnostics); if (ImmutableInterlocked.InterlockedInitialize(ref _lazyAttributes, attributes)) { diff --git a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs index 297dec288f528..b3b7cedccfe78 100644 --- a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs @@ -534,6 +534,9 @@ public static string GetRelativePath(string directory, string fullPath) { string relativePath = string.Empty; + directory = TrimTrailingSeparators(directory); + fullPath = TrimTrailingSeparators(fullPath); + if (IsChildPath(directory, fullPath)) { return GetRelativeChildPath(directory, fullPath); diff --git a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs index c9700d1aaf961..54e5d98317ec5 100644 --- a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. // < auto-generated /> +#nullable enable using System; using System.ComponentModel; using Microsoft.CodeAnalysis.FlowAnalysis; diff --git a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs index d0e223d73de05..9b6a2dc961b59 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs @@ -2999,6 +2999,10 @@ public interface ISwitchExpressionOperation : IOperation /// Arms of the switch expression. /// ImmutableArray Arms { get; } + /// + /// True if the switch expressions arms cover every possible input value. + /// + bool IsExhaustive { get; } } /// /// Represents one arm of a switch expression. @@ -7062,15 +7066,17 @@ internal DiscardPatternOperation(ITypeSymbol inputType, ITypeSymbol narrowedType } internal sealed partial class SwitchExpressionOperation : Operation, ISwitchExpressionOperation { - internal SwitchExpressionOperation(IOperation value, ImmutableArray arms, SemanticModel? semanticModel, SyntaxNode syntax, ITypeSymbol? type, bool isImplicit) + internal SwitchExpressionOperation(IOperation value, ImmutableArray arms, bool isExhaustive, SemanticModel? semanticModel, SyntaxNode syntax, ITypeSymbol? type, bool isImplicit) : base(semanticModel, syntax, isImplicit) { Value = SetParentOperation(value, this); Arms = SetParentOperation(arms, this); + IsExhaustive = isExhaustive; Type = type; } public IOperation Value { get; } public ImmutableArray Arms { get; } + public bool IsExhaustive { get; } protected override IOperation GetCurrent(int slot, int index) => slot switch { @@ -8087,7 +8093,7 @@ public override IOperation VisitDiscardPattern(IDiscardPatternOperation operatio public override IOperation VisitSwitchExpression(ISwitchExpressionOperation operation, object? argument) { var internalOperation = (SwitchExpressionOperation)operation; - return new SwitchExpressionOperation(Visit(internalOperation.Value), VisitArray(internalOperation.Arms), internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.Type, internalOperation.IsImplicit); + return new SwitchExpressionOperation(Visit(internalOperation.Value), VisitArray(internalOperation.Arms), internalOperation.IsExhaustive, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.Type, internalOperation.IsImplicit); } public override IOperation VisitSwitchExpressionArm(ISwitchExpressionArmOperation operation, object? argument) { diff --git a/src/Compilers/Core/Portable/InternalUtilities/Debug.cs b/src/Compilers/Core/Portable/InternalUtilities/Debug.cs index cd43fd76985fa..298106307719c 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/Debug.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/Debug.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -23,5 +24,43 @@ public static void AssertNotNull([NotNull] T value) { Assert(value is object, "Unexpected null reference"); } + + /// + /// Generally is a sufficient method for enforcing DEBUG + /// only invariants in our code. When it triggers that providse a nice stack trace for + /// investigation. Generally that is enough. + /// + /// There are cases for which a stack is not enough and we need a full heap dump to + /// investigate the failure. This method takes care of that. The behavior is that when running + /// in our CI environment if the assert triggers we will rudely crash the process and + /// produce a heap dump for investigation. + /// + [Conditional("DEBUG")] + internal static void AssertOrFailFast([DoesNotReturnIf(false)] bool condition, string? message = null) + { +#if NET20 || NETSTANDARD1_3 + Debug.Assert(condition); +#else + if (!condition) + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER"))) + { + message ??= $"{nameof(AssertOrFailFast)} failed"; + var stackTrace = new StackTrace(); + Console.WriteLine(message); + Console.WriteLine(stackTrace); + + // Use FailFast so that the process fails rudely and goes through + // windows error reporting (on Windows at least). This will allow our + // Helix environment to capture crash dumps for future investigation + Environment.FailFast(message); + } + else + { + Debug.Assert(false, message); + } + } +#endif + } } } diff --git a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs index 28873ec7c8c1c..b7b5de2c77c54 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs @@ -8,9 +8,11 @@ using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; +using System.Threading; #if DEBUG using System.Diagnostics; @@ -51,6 +53,41 @@ public static IEnumerable Do(this IEnumerable source, Action action) return source; } + public static ImmutableArray ToImmutableArrayOrEmpty(this IEnumerable? items) + { + if (items == null) + { + return ImmutableArray.Create(); + } + + if (items is ImmutableArray array) + { + return array.NullToEmpty(); + } + + return ImmutableArray.CreateRange(items); + } + + public static IReadOnlyList ToBoxedImmutableArray(this IEnumerable? items) + { + if (items is null) + { + return SpecializedCollections.EmptyBoxedImmutableArray(); + } + + if (items is ImmutableArray array) + { + return array.IsDefaultOrEmpty ? SpecializedCollections.EmptyBoxedImmutableArray() : (IReadOnlyList)items; + } + + if (items is ICollection collection && collection.Count == 0) + { + return SpecializedCollections.EmptyBoxedImmutableArray(); + } + + return ImmutableArray.CreateRange(items); + } + public static ReadOnlyCollection ToReadOnlyCollection(this IEnumerable source) { if (source == null) @@ -274,6 +311,36 @@ public static ImmutableArray SelectAsArray(this IEnum return builder.ToImmutableAndFree(); } + /// + /// Maps an immutable array through a function that returns ValueTask, returning the new ImmutableArray. + /// + public static async ValueTask> SelectAsArrayAsync(this IEnumerable source, Func> selector) + { + var builder = ArrayBuilder.GetInstance(); + + foreach (var item in source) + { + builder.Add(await selector(item).ConfigureAwait(false)); + } + + return builder.ToImmutableAndFree(); + } + + /// + /// Maps an immutable array through a function that returns ValueTask, returning the new ImmutableArray. + /// + public static async ValueTask> SelectAsArrayAsync(this IEnumerable source, Func> selector, TArg arg, CancellationToken cancellationToken) + { + var builder = ArrayBuilder.GetInstance(); + + foreach (var item in source) + { + builder.Add(await selector(item, arg, cancellationToken).ConfigureAwait(false)); + } + + return builder.ToImmutableAndFree(); + } + public static bool All(this IEnumerable source) { if (source == null) diff --git a/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs deleted file mode 100644 index 45bbcf4e23914..0000000000000 --- a/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Roslyn.Utilities -{ - internal static class ImmutableArrayExtensions - { - internal static ImmutableArray ToImmutableArrayOrEmpty(this IEnumerable? items) - => items == null ? ImmutableArray.Empty : ImmutableArray.CreateRange(items); - - internal static ImmutableArray ToImmutableArrayOrEmpty(this ImmutableArray items) - => items.IsDefault ? ImmutableArray.Empty : items; - - // same as Array.BinarySearch but the ability to pass arbitrary value to the comparer without allocation - internal static int BinarySearch(this ImmutableArray array, TValue value, Func comparer) - { - int low = 0; - int high = array.Length - 1; - - while (low <= high) - { - int middle = low + ((high - low) >> 1); - int comparison = comparer(array[middle], value); - - if (comparison == 0) - { - return middle; - } - - if (comparison > 0) - { - high = middle - 1; - } - else - { - low = middle + 1; - } - } - - return ~low; - } - - internal static ImmutableArray CastDown(this ImmutableArray array) where TDerived : class, TOriginal - { - return array.CastArray(); - } - } -} diff --git a/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs b/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs index 723d3280a57de..bdf29b7f006e7 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs @@ -4,19 +4,23 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis; namespace Roslyn.Utilities { /// - /// Represents a single item or many items. + /// Represents a single item or many items (including none). /// /// /// Used when a collection usually contains a single item but sometimes might contain multiple. /// - internal struct OneOrMany + internal readonly struct OneOrMany where T : notnull { + public static readonly OneOrMany Empty = new OneOrMany(ImmutableArray.Empty); + private readonly T? _one; private readonly ImmutableArray _many; @@ -37,18 +41,22 @@ public OneOrMany(ImmutableArray many) _many = many; } + [MemberNotNullWhen(true, nameof(_one))] + private bool HasOne + => _many.IsDefault; + public T this[int index] { get { - if (_many.IsDefault) + if (HasOne) { if (index != 0) { throw new IndexOutOfRangeException(); } - return _one!; + return _one; } else { @@ -58,24 +66,23 @@ public T this[int index] } public int Count - { - get - { - return _many.IsDefault ? 1 : _many.Length; - } - } + => HasOne ? 1 : _many.Length; + + public bool IsEmpty + => Count == 0; public OneOrMany Add(T one) { var builder = ArrayBuilder.GetInstance(); - if (_many.IsDefault) + if (HasOne) { - builder.Add(_one!); + builder.Add(_one); } else { builder.AddRange(_many); } + builder.Add(one); return new OneOrMany(builder.ToImmutableAndFree()); } @@ -83,7 +90,7 @@ public OneOrMany Add(T one) public bool Contains(T item) { RoslynDebug.Assert(item != null); - if (Count == 1) + if (HasOne) { return item.Equals(_one); } @@ -102,7 +109,7 @@ public bool Contains(T item) public OneOrMany RemoveAll(T item) { - if (_many.IsDefault) + if (HasOne) { return item.Equals(_one) ? default : this; } @@ -125,11 +132,61 @@ public OneOrMany RemoveAll(T item) return builder.Count == Count ? this : new OneOrMany(builder.ToImmutableAndFree()); } - public Enumerator GetEnumerator() + public OneOrMany Select(Func selector) + where TResult : notnull { - return new Enumerator(this); + return HasOne ? + OneOrMany.Create(selector(_one)) : + OneOrMany.Create(_many.SelectAsArray(selector)); } + public OneOrMany Select(Func selector, TArg arg) + where TResult : notnull + { + return HasOne ? + OneOrMany.Create(selector(_one, arg)) : + OneOrMany.Create(_many.SelectAsArray(selector, arg)); + } + + public T? FirstOrDefault(Func predicate) + { + if (HasOne) + { + return predicate(_one) ? _one : default; + } + + foreach (var item in _many) + { + if (predicate(item)) + { + return item; + } + } + + return default; + } + + public T? FirstOrDefault(Func predicate, TArg arg) + { + if (HasOne) + { + return predicate(_one, arg) ? _one : default; + } + + foreach (var item in _many) + { + if (predicate(item, arg)) + { + return item; + } + } + + return default; + } + + public Enumerator GetEnumerator() + => new(this); + internal struct Enumerator { private readonly OneOrMany _collection; diff --git a/src/Compilers/Core/Portable/InternalUtilities/PlatformAttributes.cs b/src/Compilers/Core/Portable/InternalUtilities/PlatformAttributes.cs index 4990bae8264d3..97eed09277620 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/PlatformAttributes.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/PlatformAttributes.cs @@ -4,7 +4,7 @@ #nullable enable -#if !NET5_0 +#if !NET5_0_OR_GREATER namespace System.Runtime.Versioning { diff --git a/src/Compilers/Core/Portable/Operations/CaptureId.cs b/src/Compilers/Core/Portable/Operations/CaptureId.cs index 148b3f0787724..35ceb6ba8f490 100644 --- a/src/Compilers/Core/Portable/Operations/CaptureId.cs +++ b/src/Compilers/Core/Portable/Operations/CaptureId.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.FlowAnalysis { @@ -13,6 +14,7 @@ public struct CaptureId : IEquatable { internal CaptureId(int value) { + Debug.Assert(value >= 0); Value = value; } diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 62885c64f7492..80be5b90c7f02 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -14,6 +15,15 @@ namespace Microsoft.CodeAnalysis.FlowAnalysis { + /// + /// Some basic concepts: + /// - Basic blocks are sequences of statements/operations with no branching. The only branching + /// allowed is at the end of the basic block. + /// - Regions group blocks together and represent the lifetime of locals and captures, loosely similar to scopes in C#. + /// There are different kinds of regions, . + /// - converts values on the stack into captures. + /// - Error scenarios from initial binding need to be handled. + /// internal sealed partial class ControlFlowGraphBuilder : OperationVisitor { private readonly Compilation _compilation; @@ -7130,24 +7140,189 @@ public override IOperation VisitArgument(IArgumentOperation operation, int? capt throw ExceptionUtilities.Unreachable; } - public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation operation, int? argument) + public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation operation, int? captureIdForResult) { throw ExceptionUtilities.Unreachable; } - public override IOperation VisitWith(IWithOperation operation, int? argument) + public override IOperation VisitWith(IWithOperation operation, int? captureIdForResult) { + if (operation.Type!.IsAnonymousType) + { + return handleAnonymousTypeWithExpression((WithOperation)operation, captureIdForResult); + } + EvalStackFrame frame = PushStackFrame(); // Initializer is removed from the tree and turned into a series of statements that assign to the cloned instance IOperation visitedInstance = VisitRequired(operation.Operand); + IOperation cloned; + if (operation.Type.IsValueType) + { + cloned = visitedInstance; + } + else + { + cloned = operation.CloneMethod is null + ? MakeInvalidOperation(visitedInstance.Type, visitedInstance) + : new InvocationOperation(operation.CloneMethod, visitedInstance, + isVirtual: true, arguments: ImmutableArray.Empty, + semanticModel: null, operation.Syntax, operation.Type, isImplicit: true); + } + + return PopStackFrame(frame, HandleObjectOrCollectionInitializer(operation.Initializer, cloned)); + + // For `old with { Property = ... }` we're going to do the same as `new { Property = ..., OtherProperty = old.OtherProperty }` + IOperation handleAnonymousTypeWithExpression(WithOperation operation, int? captureIdForResult) + { + Debug.Assert(operation.Type!.IsAnonymousType); + SpillEvalStack(); // before entering a new region, we ensure that anything that needs spilling was spilled + + // The outer region holds captures for all the values for the anonymous object creation + var outerCaptureRegion = CurrentRegionRequired; + + // The inner region holds the capture for the operand (ie. old value) + var innerCaptureRegion = new RegionBuilder(ControlFlowRegionKind.LocalLifetime); + EnterRegion(innerCaptureRegion); + + var initializers = operation.Initializer.Initializers; + + var properties = operation.Type.GetMembers() + .Where(m => m.Kind == SymbolKind.Property) + .Select(m => (IPropertySymbol)m); + + int oldValueCaptureId; + if (setsAllProperties(initializers, properties)) + { + // Avoid capturing the old value since we won't need it + oldValueCaptureId = -1; + AddStatement(VisitRequired(operation.Operand)); + } + else + { + oldValueCaptureId = GetNextCaptureId(innerCaptureRegion); + VisitAndCapture(operation.Operand, oldValueCaptureId); + } + // calls to Visit may enter regions, so we reset things + LeaveRegionsUpTo(innerCaptureRegion); + + var explicitProperties = new Dictionary(SymbolEqualityComparer.IgnoreAll); + var initializerBuilder = ArrayBuilder.GetInstance(initializers.Length); + + // Visit and capture all the values, and construct assignments using capture references + foreach (IOperation initializer in initializers) + { + if (initializer is not ISimpleAssignmentOperation simpleAssignment) + { + AddStatement(VisitRequired(initializer)); + continue; + } + + if (simpleAssignment.Target.Kind != OperationKind.PropertyReference) + { + Debug.Assert(simpleAssignment.Target is InvalidOperation); + AddStatement(VisitRequired(simpleAssignment.Value)); + continue; + } + + var propertyReference = (IPropertyReferenceOperation)simpleAssignment.Target; + + Debug.Assert(propertyReference != null); + Debug.Assert(propertyReference.Arguments.IsEmpty); + Debug.Assert(propertyReference.Instance != null); + Debug.Assert(propertyReference.Instance.Kind == OperationKind.InstanceReference); + Debug.Assert(((IInstanceReferenceOperation)propertyReference.Instance).ReferenceKind == InstanceReferenceKind.ImplicitReceiver); + + var property = propertyReference.Property; + if (explicitProperties.ContainsKey(property)) + { + AddStatement(VisitRequired(simpleAssignment.Value)); + continue; + } + + int valueCaptureId = GetNextCaptureId(outerCaptureRegion); + VisitAndCapture(simpleAssignment.Value, valueCaptureId); + LeaveRegionsUpTo(innerCaptureRegion); + var valueCaptureRef = new FlowCaptureReferenceOperation(valueCaptureId, operation.Operand.Syntax, + operation.Operand.Type, constantValue: operation.Operand.GetConstantValue()); + + var assignment = makeAssignment(property, valueCaptureRef, operation); + + explicitProperties.Add(property, assignment); + } - IOperation cloned = operation.CloneMethod is null - ? MakeInvalidOperation(visitedInstance.Type, visitedInstance) - : new InvocationOperation(operation.CloneMethod, visitedInstance, - isVirtual: true, arguments: ImmutableArray.Empty, + // Make a sequence for all properties (in order), constructing assignments for the implicitly set properties + var type = (INamedTypeSymbol)operation.Type; + foreach (IPropertySymbol property in properties) + { + if (explicitProperties.TryGetValue(property, out var assignment)) + { + initializerBuilder.Add(assignment); + } + else + { + Debug.Assert(oldValueCaptureId >= 0); + + // `oldInstance` + var oldInstance = new FlowCaptureReferenceOperation(oldValueCaptureId, operation.Operand.Syntax, + operation.Operand.Type, constantValue: operation.Operand.GetConstantValue()); + + // `oldInstance.Property` + var visitedValue = new PropertyReferenceOperation(property, ImmutableArray.Empty, oldInstance, + semanticModel: null, operation.Syntax, property.Type, isImplicit: true); + + int extraValueCaptureId = GetNextCaptureId(outerCaptureRegion); + AddStatement(new FlowCaptureOperation(extraValueCaptureId, operation.Syntax, visitedValue)); + + var extraValueCaptureRef = new FlowCaptureReferenceOperation(extraValueCaptureId, operation.Operand.Syntax, + operation.Operand.Type, constantValue: operation.Operand.GetConstantValue()); + + assignment = makeAssignment(property, extraValueCaptureRef, operation); + initializerBuilder.Add(assignment); + } + } + LeaveRegionsUpTo(outerCaptureRegion); + + return new AnonymousObjectCreationOperation(initializerBuilder.ToImmutableAndFree(), semanticModel: null, operation.Syntax, operation.Type, operation.IsImplicit); + } + + // Build an operation for `.Property = ` + SimpleAssignmentOperation makeAssignment(IPropertySymbol property, IOperation capturedValue, WithOperation operation) + { + // + var implicitReceiver = new InstanceReferenceOperation(InstanceReferenceKind.ImplicitReceiver, semanticModel: null, operation.Syntax, operation.Type, isImplicit: true); - return PopStackFrame(frame, HandleObjectOrCollectionInitializer(operation.Initializer, cloned)); + // .Property + var target = new PropertyReferenceOperation(property, ImmutableArray.Empty, implicitReceiver, + semanticModel: null, operation.Syntax, property.Type, isImplicit: true); + + // .Property = + return new SimpleAssignmentOperation(isRef: false, target, capturedValue, + semanticModel: null, operation.Syntax, property.Type, constantValue: null, isImplicit: true); + } + + static bool setsAllProperties(ImmutableArray initializers, IEnumerable properties) + { + var set = new HashSet(SymbolEqualityComparer.IgnoreAll); + foreach (var initializer in initializers) + { + if (initializer is not ISimpleAssignmentOperation simpleAssignment) + { + continue; + } + if (simpleAssignment.Target.Kind != OperationKind.PropertyReference) + { + Debug.Assert(simpleAssignment.Target is InvalidOperation); + continue; + } + + var propertyReference = (IPropertyReferenceOperation)simpleAssignment.Target; + Debug.Assert(properties.Contains(propertyReference.Property, SymbolEqualityComparer.IgnoreAll)); + set.Add(propertyReference.Property); + } + + return set.Count == properties.Count(); + } } } } diff --git a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml index efd684a6add6f..0e5bd4528bf3d 100644 --- a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml +++ b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml @@ -2780,6 +2780,11 @@ Arms of the switch expression. + + + True if the switch expressions arms cover every possible input value. + + diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs index 20d29293160fa..f6b74ba32d890 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs @@ -745,8 +745,16 @@ private DocumentHandle AddDocument(DebugSourceDocument document, DictionaryThe version of the compilation options schema to be written to the PDB. - public const int CompilationOptionsSchemaVersion = 1; + private const int CompilationOptionsSchemaVersion = 2; /// /// Capture the set of compilation options to allow a compilation /// to be reconstructed from the pdb /// - private void EmbedCompilationOptions(BlobReader? pdbCompilationOptionsReader, CommonPEModuleBuilder module) + private void EmbedCompilationOptions(CommonPEModuleBuilder module) { var builder = new BlobBuilder(); - if (pdbCompilationOptionsReader is { } reader) + if (this.Context.RebuildData is { } rebuildData) { + var reader = rebuildData.OptionsBlobReader; builder.WriteBytes(reader.ReadBytes(reader.RemainingBytes)); } else diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index f7f370be93134..96e49d321b22f 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -425,6 +425,8 @@ private bool IsMinimalDelta // progress: private bool _tableIndicesAreComplete; + private bool _usingNonSourceDocumentNameEnumerator; + private ImmutableArray.Enumerator _nonSourceDocumentNameEnumerator; private EntityHandle[] _pseudoSymbolTokenToTokenMap; private object[] _pseudoSymbolTokenToReferenceMap; @@ -1697,7 +1699,7 @@ internal EntityHandle GetDefinitionHandle(IDefinition definition) }; } - public void WriteMetadataAndIL(PdbWriter nativePdbWriterOpt, Stream metadataStream, Stream ilStream, Stream portablePdbStreamOpt, BlobReader? pdbOptionsBlobReader, out MetadataSizes metadataSizes) + public void WriteMetadataAndIL(PdbWriter nativePdbWriterOpt, Stream metadataStream, Stream ilStream, Stream portablePdbStreamOpt, out MetadataSizes metadataSizes) { Debug.Assert(nativePdbWriterOpt == null ^ portablePdbStreamOpt == null); @@ -1724,7 +1726,6 @@ public void WriteMetadataAndIL(PdbWriter nativePdbWriterOpt, Stream metadataStre ilBuilder, mappedFieldDataBuilder, managedResourceDataBuilder, - pdbOptionsBlobReader, out Blob mvidFixup, out Blob mvidStringFixup); @@ -1780,7 +1781,6 @@ public void BuildMetadataAndIL( BlobBuilder ilBuilder, BlobBuilder mappedFieldDataBuilder, BlobBuilder managedResourceDataBuilder, - BlobReader? pdbOptionsBlobReader, out Blob mvidFixup, out Blob mvidStringFixup) { @@ -1800,6 +1800,12 @@ public void BuildMetadataAndIL( } } + if (Context.RebuildData is { } rebuildData) + { + _usingNonSourceDocumentNameEnumerator = true; + _nonSourceDocumentNameEnumerator = rebuildData.NonSourceFileDocumentNames.GetEnumerator(); + } + DefineModuleImportScope(); if (module.SourceLinkStreamOpt != null) @@ -1807,7 +1813,7 @@ public void BuildMetadataAndIL( EmbedSourceLink(module.SourceLinkStreamOpt); } - EmbedCompilationOptions(pdbOptionsBlobReader, module); + EmbedCompilationOptions(module); EmbedMetadataReferenceInformation(module); } diff --git a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs index bb7f062d3e88d..04317d1e2c752 100644 --- a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs @@ -41,7 +41,6 @@ internal static bool WritePeToStream( Func getPeStream, Func getPortablePdbStreamOpt, PdbWriter nativePdbWriterOpt, - BlobReader? pdbOptionsBlobReader, string pdbPathOpt, bool metadataOnly, bool isDeterministic, @@ -74,7 +73,6 @@ internal static bool WritePeToStream( ilBuilder, mappedFieldDataBuilder, managedResourceBuilder, - pdbOptionsBlobReader, out mvidFixup, out mvidStringFixup); diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 04b7d01e736e3..569874167b8fd 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,9 @@ +*REMOVED*Microsoft.CodeAnalysis.SyntaxNode.IsEquivalentTo(Microsoft.CodeAnalysis.SyntaxNode! other) -> bool +abstract Microsoft.CodeAnalysis.SyntaxTree.GetLineMappings(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IEnumerable! +const Microsoft.CodeAnalysis.WellKnownMemberNames.PrintMembersMethodName = "PrintMembers" -> string! +Microsoft.CodeAnalysis.Compilation.EmitDifference(Microsoft.CodeAnalysis.Emit.EmitBaseline! baseline, System.Collections.Generic.IEnumerable! edits, System.Func! isAddedSymbol, System.IO.Stream! metadataStream, System.IO.Stream! ilStream, System.IO.Stream! pdbStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Emit.EmitDifferenceResult! +Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedMethods.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedTypes.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! Microsoft.CodeAnalysis.IFieldSymbol.IsExplicitlyNamedTupleElement.get -> bool @@ -18,17 +24,39 @@ Microsoft.CodeAnalysis.GeneratorPostInitializationContext.CancellationToken.get Microsoft.CodeAnalysis.GeneratorPostInitializationContext.GeneratorPostInitializationContext() -> void Microsoft.CodeAnalysis.IMethodSymbol.MethodImplementationFlags.get -> System.Reflection.MethodImplAttributes Microsoft.CodeAnalysis.ITypeSymbol.IsRecord.get -> bool +Microsoft.CodeAnalysis.Operations.ISwitchExpressionOperation.IsExhaustive.get -> bool +Microsoft.CodeAnalysis.LineMapping +Microsoft.CodeAnalysis.LineMapping.Deconstruct(out Microsoft.CodeAnalysis.Text.LinePositionSpan span, out Microsoft.CodeAnalysis.FileLinePositionSpan mappedSpan) -> void +Microsoft.CodeAnalysis.LineMapping.Equals(Microsoft.CodeAnalysis.LineMapping other) -> bool +Microsoft.CodeAnalysis.LineMapping.IsHidden.get -> bool +Microsoft.CodeAnalysis.LineMapping.LineMapping() -> void +Microsoft.CodeAnalysis.LineMapping.LineMapping(Microsoft.CodeAnalysis.Text.LinePositionSpan span, Microsoft.CodeAnalysis.FileLinePositionSpan mappedSpan) -> void +Microsoft.CodeAnalysis.LineMapping.MappedSpan.get -> Microsoft.CodeAnalysis.FileLinePositionSpan +Microsoft.CodeAnalysis.LineMapping.Span.get -> Microsoft.CodeAnalysis.Text.LinePositionSpan +override Microsoft.CodeAnalysis.LineMapping.Equals(object? obj) -> bool +override Microsoft.CodeAnalysis.LineMapping.GetHashCode() -> int +override Microsoft.CodeAnalysis.LineMapping.ToString() -> string? Microsoft.CodeAnalysis.Operations.OperationWalker Microsoft.CodeAnalysis.Operations.OperationWalker.OperationWalker() -> void Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordClassName = 31 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind const Microsoft.CodeAnalysis.WellKnownDiagnosticTags.CompilationEnd = "CompilationEnd" -> string! +Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordStructName = 32 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind Microsoft.CodeAnalysis.SyntaxContextReceiverCreator +Microsoft.CodeAnalysis.SyntaxNode.IsEquivalentTo(Microsoft.CodeAnalysis.SyntaxNode? other) -> bool +Microsoft.CodeAnalysis.SyntaxNode.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode? other) -> bool +Microsoft.CodeAnalysis.SyntaxNodeOrToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNodeOrToken other) -> bool +Microsoft.CodeAnalysis.SyntaxToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxToken token) -> bool +override Microsoft.CodeAnalysis.Text.TextChangeRange.ToString() -> string! static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Compare(System.ReadOnlySpan left, System.ReadOnlySpan right) -> int static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Equals(System.ReadOnlySpan left, System.ReadOnlySpan right) -> bool override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators(string! language) -> System.Collections.Immutable.ImmutableArray override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGeneratorsForAllLanguages() -> System.Collections.Immutable.ImmutableArray override Microsoft.CodeAnalysis.Operations.OperationWalker.DefaultVisit(Microsoft.CodeAnalysis.IOperation! operation, TArgument argument) -> object? override Microsoft.CodeAnalysis.Operations.OperationWalker.Visit(Microsoft.CodeAnalysis.IOperation? operation, TArgument argument) -> object? +static Microsoft.CodeAnalysis.FileLinePositionSpan.operator !=(Microsoft.CodeAnalysis.FileLinePositionSpan left, Microsoft.CodeAnalysis.FileLinePositionSpan right) -> bool +static Microsoft.CodeAnalysis.FileLinePositionSpan.operator ==(Microsoft.CodeAnalysis.FileLinePositionSpan left, Microsoft.CodeAnalysis.FileLinePositionSpan right) -> bool +static Microsoft.CodeAnalysis.LineMapping.operator !=(Microsoft.CodeAnalysis.LineMapping left, Microsoft.CodeAnalysis.LineMapping right) -> bool +static Microsoft.CodeAnalysis.LineMapping.operator ==(Microsoft.CodeAnalysis.LineMapping left, Microsoft.CodeAnalysis.LineMapping right) -> bool virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGenerators(string! language) -> System.Collections.Immutable.ImmutableArray virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGeneratorsForAllLanguages() -> System.Collections.Immutable.ImmutableArray abstract Microsoft.CodeAnalysis.Compilation.GetUsedAssemblyReferences(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Immutable.ImmutableArray diff --git a/src/Compilers/Core/Portable/Serialization/ObjectReader.cs b/src/Compilers/Core/Portable/Serialization/ObjectReader.cs index 9e8c5d3d585d0..7ad967e5f8b24 100644 --- a/src/Compilers/Core/Portable/Serialization/ObjectReader.cs +++ b/src/Compilers/Core/Portable/Serialization/ObjectReader.cs @@ -187,13 +187,11 @@ public object ReadValue() object value; if (_recursionDepth % ObjectWriter.MaxRecursionDepth == 0) { + _cancellationToken.ThrowIfCancellationRequested(); + // If we're recursing too deep, move the work to another thread to do so we // don't blow the stack. - var task = Task.Factory.StartNew( - () => ReadValueWorker(), - _cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); + var task = SerializationThreadPool.RunOnBackgroundThreadAsync(() => ReadValueWorker()); // We must not proceed until the additional task completes. After returning from a read, the underlying // stream providing access to raw memory will be closed; if this occurs before the separate thread diff --git a/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs b/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs index 4b01b6baad3cf..d09fdba9cfc3e 100644 --- a/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs +++ b/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs @@ -568,15 +568,17 @@ private void WriteArray(Array array) if (_recursionDepth % MaxRecursionDepth == 0) { + _cancellationToken.ThrowIfCancellationRequested(); + // If we're recursing too deep, move the work to another thread to do so we - // don't blow the stack. 'LongRunning' ensures that we get a dedicated thread - // to do this work. That way we don't end up blocking the threadpool. - var task = Task.Factory.StartNew( - a => WriteArrayValues((Array)a!), - array, - _cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); + // don't blow the stack. + var task = SerializationThreadPool.RunOnBackgroundThreadAsync( + a => + { + WriteArrayValues((Array)a!); + return null; + }, + array); // We must not proceed until the additional task completes. After returning from a write, the underlying // stream providing access to raw memory will be closed; if this occurs before the separate thread @@ -888,15 +890,17 @@ private void WriteObject(object instance, IObjectWritable? instanceAsWritable) if (_recursionDepth % MaxRecursionDepth == 0) { + _cancellationToken.ThrowIfCancellationRequested(); + // If we're recursing too deep, move the work to another thread to do so we - // don't blow the stack. 'LongRunning' ensures that we get a dedicated thread - // to do this work. That way we don't end up blocking the threadpool. - var task = Task.Factory.StartNew( - obj => WriteObjectWorker((IObjectWritable)obj!), - writable, - _cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); + // don't blow the stack. + var task = SerializationThreadPool.RunOnBackgroundThreadAsync( + obj => + { + WriteObjectWorker((IObjectWritable)obj!); + return null; + }, + writable); // We must not proceed until the additional task completes. After returning from a write, the underlying // stream providing access to raw memory will be closed; if this occurs before the separate thread diff --git a/src/Compilers/Core/Portable/Serialization/SerializationThreadPool.cs b/src/Compilers/Core/Portable/Serialization/SerializationThreadPool.cs new file mode 100644 index 0000000000000..80cf43cefe929 --- /dev/null +++ b/src/Compilers/Core/Portable/Serialization/SerializationThreadPool.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Roslyn.Utilities +{ + internal static class SerializationThreadPool + { + public static Task RunOnBackgroundThreadAsync(Func start) + => ImmediateBackgroundThreadPool.QueueAsync(start); + + public static Task RunOnBackgroundThreadAsync(Func start, object? obj) + => ImmediateBackgroundThreadPool.QueueAsync(start, obj); + + /// + /// Naive thread pool focused on reducing the latency to execution of chunky work items as much as possible. + /// If a thread is ready to process a work item the moment a work item is queued, it's used, otherwise + /// a new thread is created. This is meant as a stop-gap measure for workloads that would otherwise be + /// creating a new thread for every work item. + /// + /// + /// This class is derived from dotnet/machinelearning. + /// + private static class ImmediateBackgroundThreadPool + { + /// How long should threads wait around for additional work items before retiring themselves. + private static readonly TimeSpan s_idleTimeout = TimeSpan.FromSeconds(1); + + /// The queue of work items. Also used as a lock to protect all relevant state. + private static readonly Queue<(Delegate function, object? state, TaskCompletionSource tcs)> s_queue = new(); + + /// The number of threads currently waiting in tryDequeue for work to arrive. + private static int s_availableThreads = 0; + + /// + /// Queues a delegate to be executed immediately on another thread, + /// and returns a that represents its eventual completion. The task will + /// always end in the state; if the delegate throws + /// an exception, it'll be allowed to propagate on the thread, crashing the process. + /// + public static Task QueueAsync(Func threadStart) => QueueAsync((Delegate)threadStart, state: null); + + /// + /// Queues a delegate and associated state to be executed immediately on + /// another thread, and returns a that represents its eventual completion. + /// + public static Task QueueAsync(Func threadStart, object? state) => QueueAsync((Delegate)threadStart, state); + + private static Task QueueAsync(Delegate threadStart, object? state) + { + // Create the TaskCompletionSource used to represent this work. 'RunContinuationsAsynchronously' ensures + // continuations do not also run on the threads created by 'createThread'. + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + // Queue the work for a thread to pick up. If no thread is immediately available, it will create one. + enqueue((threadStart, state, tcs)); + + // Return the task. + return tcs.Task; + + static void createThread() + { + // Create a new background thread to run the work. + var t = new Thread(() => + { + // Repeatedly get the next item and invoke it, setting its TCS when we're done. + // This will wait for up to the idle time before giving up and exiting. + while (tryDequeue(out var item)) + { + try + { + if (item.function is Func callbackWithState) + { + item.tcs.SetResult(callbackWithState(item.state)); + } + else + { + item.tcs.SetResult(((Func)item.function)()); + } + } + catch (OperationCanceledException ex) + { + item.tcs.TrySetCanceled(ex.CancellationToken); + } + catch (Exception ex) + { + item.tcs.TrySetException(ex); + } + } + }); + t.IsBackground = true; + t.Start(); + } + + static void enqueue((Delegate function, object? state, TaskCompletionSource tcs) item) + { + // Enqueue the work. If there are currently fewer threads waiting + // for work than there are work items in the queue, create another + // thread. This is a heuristic, in that we might end up creating + // more threads than are truly needed, but this whole type is being + // used to replace a previous solution where every work item created + // its own thread, so this is an improvement regardless of any + // such inefficiencies. + lock (s_queue) + { + s_queue.Enqueue(item); + + if (s_queue.Count <= s_availableThreads) + { + Monitor.Pulse(s_queue); + return; + } + } + + // No thread was currently available. Create one. + createThread(); + } + + static bool tryDequeue(out (Delegate function, object? state, TaskCompletionSource tcs) item) + { + // Dequeues the next item if one is available. Before checking, + // the available thread count is increased, so that enqueuers can + // see how many threads are currently waiting, with the count + // decreased after. Each time it waits, it'll wait for at most + // the idle timeout before giving up. + lock (s_queue) + { + s_availableThreads++; + try + { + while (s_queue.Count == 0) + { + if (!Monitor.Wait(s_queue, s_idleTimeout)) + { + if (s_queue.Count > 0) + { + // The wait timed out, but a new item was added to the queue between the time + // this thread entered the ready queue and the point where the lock was + // reacquired. Make sure to process the available item, since there is no + // guarantee another thread will exist or be notified to handle it separately. + // + // The following is one sequence which requires this path handle the queued + // element for correctness: + // + // 1. Thread A calls tryDequeue, and releases the lock in Wait + // 2. Thread B calls enqueue and holds the lock + // 3. Thread A times out and enters the ready thread queue + // 4. Thread B observes that s_queue.Count (1) <= s_availableThreads (1), so it + // calls Pulse instead of creating a new thread + // 5. Thread B releases the lock + // 6. Thread A acquires the lock, and Monitor.Wait returns false + // + // Since no new thread was created in step 4, we must handle the enqueued + // element or the thread will exit and the item will sit in the queue + // indefinitely. + break; + } + + item = default; + return false; + } + } + } + finally + { + s_availableThreads--; + } + + item = s_queue.Dequeue(); + return true; + } + } + } + } + } +} diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index d0f5b0248c38d..e100917237d3a 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -302,9 +302,10 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos continue; } - (var sources, var diagnostics) = context.ToImmutableAndFree(); - stateBuilder[i] = new GeneratorState(generatorState.Info, generatorState.PostInitTrees, ParseAdditionalSources(generator, sources, cancellationToken), diagnostics); - diagnosticsBag?.AddRange(diagnostics); + (var sources, var generatorDiagnostics) = context.ToImmutableAndFree(); + generatorDiagnostics = FilterDiagnostics(compilation, generatorDiagnostics, driverDiagnostics: diagnosticsBag, cancellationToken); + + stateBuilder[i] = new GeneratorState(generatorState.Info, generatorState.PostInitTrees, ParseAdditionalSources(generator, sources, cancellationToken), generatorDiagnostics); } state = state.With(generatorStates: stateBuilder.ToImmutableAndFree()); return state; @@ -438,6 +439,21 @@ private static GeneratorState SetGeneratorException(CommonMessageProvider provid return new GeneratorState(generatorState.Info, e, diagnostic); } + private static ImmutableArray FilterDiagnostics(Compilation compilation, ImmutableArray generatorDiagnostics, DiagnosticBag? driverDiagnostics, CancellationToken cancellationToken) + { + ArrayBuilder filteredDiagnostics = ArrayBuilder.GetInstance(); + foreach (var diag in generatorDiagnostics) + { + var filtered = compilation.Options.FilterDiagnostic(diag, cancellationToken); + if (filtered is object) + { + filteredDiagnostics.Add(filtered); + driverDiagnostics?.Add(filtered); + } + } + return filteredDiagnostics.ToImmutableAndFree(); + } + internal static string GetFilePathPrefixForGenerator(ISourceGenerator generator) { var type = generator.GetType(); diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayPartKind.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayPartKind.cs index 2c1b36610d9b5..017e2eac861ee 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayPartKind.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayPartKind.cs @@ -78,13 +78,15 @@ public enum SymbolDisplayPartKind ExtensionMethodName = 29, /// The name of a field or local constant. ConstantName = 30, - /// The name of a record. + /// The name of a record class. RecordClassName = 31, + /// The name of a record struct. + RecordStructName = 32, } internal static class InternalSymbolDisplayPartKind { - private const SymbolDisplayPartKind @base = SymbolDisplayPartKind.RecordClassName + 1; + private const SymbolDisplayPartKind @base = SymbolDisplayPartKind.RecordStructName + 1; public const SymbolDisplayPartKind Arity = @base + 0; public const SymbolDisplayPartKind Other = @base + 1; } @@ -93,7 +95,7 @@ internal static partial class EnumBounds { internal static bool IsValid(this SymbolDisplayPartKind value) { - return (value >= SymbolDisplayPartKind.AliasName && value <= SymbolDisplayPartKind.RecordClassName) || + return (value >= SymbolDisplayPartKind.AliasName && value <= SymbolDisplayPartKind.RecordStructName) || (value >= InternalSymbolDisplayPartKind.Arity && value <= InternalSymbolDisplayPartKind.Other); } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/MarshalPseudoCustomAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/MarshalPseudoCustomAttributeData.cs index be1cfa913c44e..8165381312ce2 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/MarshalPseudoCustomAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/MarshalPseudoCustomAttributeData.cs @@ -120,7 +120,7 @@ object Cci.IMarshallingInformation.GetCustomMarshaller(EmitContext context) var typeSymbol = _marshalTypeNameOrSymbol as ITypeSymbolInternal; if (typeSymbol != null) { - return ((CommonPEModuleBuilder)context.Module).Translate(typeSymbol, context.SyntaxNodeOpt, context.Diagnostics); + return ((CommonPEModuleBuilder)context.Module).Translate(typeSymbol, context.SyntaxNode, context.Diagnostics); } else { @@ -183,7 +183,7 @@ Cci.ITypeReference Cci.IMarshallingInformation.GetSafeArrayElementUserDefinedSub return null; } - return ((CommonPEModuleBuilder)context.Module).Translate((ITypeSymbolInternal)_marshalTypeNameOrSymbol, context.SyntaxNodeOpt, context.Diagnostics); + return ((CommonPEModuleBuilder)context.Module).Translate((ITypeSymbolInternal)_marshalTypeNameOrSymbol, context.SyntaxNode, context.Diagnostics); } /// diff --git a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index 72b9dd19a062a..7975906f8cbdd 100644 --- a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -343,7 +343,11 @@ public static class WellKnownMemberNames // internal until we settle on this long-term internal const string CloneMethodName = "$"; - internal const string PrintMembersMethodName = "PrintMembers"; + + /// + /// The required name for the PrintMembers method that is synthesized in a record. + /// + public const string PrintMembersMethodName = "PrintMembers"; /// /// The name of an entry point method synthesized for top-level statements. diff --git a/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.LineMappingEntry.cs b/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.LineMappingEntry.cs index eff56dd8b1c61..eff85cb1d1b61 100644 --- a/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.LineMappingEntry.cs +++ b/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.LineMappingEntry.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis { @@ -15,33 +14,33 @@ internal partial class LineDirectiveMap public enum PositionState : byte { /// - /// Used in VB when the position is not hidden, but it's not known yet that there is a (nonempty) #ExternalSource + /// Used in VB when the position is not hidden, but it's not known yet that there is a (nonempty) #ExternalSource /// following. /// Unknown, /// - /// Used in C# for spans outside of #line directives + /// Used in C# for spans preceding the first #line directive (if any) and for #line default spans /// Unmapped, /// - /// Used in C# for spans inside of "#line linenumber" directive + /// Used in C# for spans inside of #line linenumber directive /// Remapped, /// - /// Used in VB for spans inside of a "#ExternalSource" directive that followed an unknown span + /// Used in VB for spans inside of a #ExternalSource directive that followed an unknown span /// RemappedAfterUnknown, /// - /// Used in VB for spans inside of a "#ExternalSource" directive that followed a hidden span + /// Used in VB for spans inside of a #ExternalSource directive that followed a hidden span /// RemappedAfterHidden, /// - /// Used in C# and VB for spans that are inside of #line hidden (C#) or outside of #ExternalSource (VB) + /// Used in C# and VB for spans that are inside of #line hidden (C#) or outside of #ExternalSource (VB) /// directives /// Hidden @@ -49,7 +48,7 @@ public enum PositionState : byte // Struct that represents an entry in the line mapping table. Entries sort by the unmapped // line. - protected readonly struct LineMappingEntry : IComparable + internal readonly struct LineMappingEntry : IComparable { // 0-based line in this tree public readonly int UnmappedLine; @@ -84,9 +83,10 @@ public LineMappingEntry( } public int CompareTo(LineMappingEntry other) - { - return this.UnmappedLine.CompareTo(other.UnmappedLine); - } + => UnmappedLine.CompareTo(other.UnmappedLine); + + public bool IsHidden + => State == PositionState.Hidden; } } } diff --git a/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.cs b/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.cs index 6f1f9eb992968..3ad2f7bdbc6cc 100644 --- a/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.cs +++ b/src/Compilers/Core/Portable/Syntax/LineDirectiveMap.cs @@ -4,10 +4,11 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis { @@ -21,7 +22,7 @@ namespace Microsoft.CodeAnalysis internal abstract partial class LineDirectiveMap where TDirective : SyntaxNode { - protected readonly LineMappingEntry[] Entries; + internal readonly ImmutableArray Entries; // Get all active #line directives under trivia into the list, in source code order. protected abstract bool ShouldAddDirective(TDirective directive); @@ -40,7 +41,7 @@ protected LineDirectiveMap(SyntaxTree syntaxTree) Debug.Assert(directives != null); // Create the entry map. - this.Entries = CreateEntryMap(syntaxTree, directives); + Entries = CreateEntryMap(syntaxTree, directives); } // Given a span and a default file name, return a FileLinePositionSpan that is the mapped @@ -98,18 +99,18 @@ protected LineMappingEntry FindEntry(int lineNumber) // Find the index of the line mapped entry with the largest unmapped line number <= lineNumber. protected int FindEntryIndex(int lineNumber) { - int r = Array.BinarySearch(this.Entries, new LineMappingEntry(lineNumber)); + int r = Entries.BinarySearch(new LineMappingEntry(lineNumber)); return r >= 0 ? r : ((~r) - 1); } // Given the ordered list of all directives in the file, return the ordered line mapping // entry for the file. This always starts with the null mapped that maps line 0 to line 0. - private LineMappingEntry[] CreateEntryMap(SyntaxTree tree, IList directives) + private ImmutableArray CreateEntryMap(SyntaxTree tree, IList directives) { - var entries = new LineMappingEntry[directives.Count + 1]; + var entries = ArrayBuilder.GetInstance(directives.Count + 1); + var current = InitializeFirstEntry(); - var index = 0; - entries[index] = current; + entries.Add(current); if (directives.Count > 0) { @@ -117,20 +118,108 @@ private LineMappingEntry[] CreateEntryMap(SyntaxTree tree, IList dir foreach (var directive in directives) { current = GetEntry(directive, sourceText, current); - ++index; - entries[index] = current; + entries.Add(current); } } #if DEBUG // Make sure the entries array is correctly sorted. - for (int i = 0; i < entries.Length - 1; ++i) + for (int i = 0; i < entries.Count - 1; ++i) { Debug.Assert(entries[i].CompareTo(entries[i + 1]) < 0); } #endif - return entries; + return entries.ToImmutableAndFree(); + } + + protected abstract LineVisibility GetUnknownStateVisibility(int index); + + /// + /// The caller is expected to not call this if is empty. + /// + public IEnumerable GetLineMappings(TextLineCollection lines) + { + Debug.Assert(Entries.Length > 1); + + var current = Entries[0]; + + // the first entry is always initialized to unmapped: + Debug.Assert( + current.State is PositionState.Unmapped or PositionState.Unknown && + current.UnmappedLine == 0 && + current.MappedLine == 0 && + current.MappedPathOpt == null); + + LineMapping CreateCurrentEntryMapping(in LineMappingEntry entry, int unmappedEndLine, int lineLength, int currentIndex) + { + var unmapped = new LinePositionSpan( + new LinePosition(entry.UnmappedLine, character: 0), + new LinePosition(unmappedEndLine, lineLength)); + + var isHidden = + entry.State == PositionState.Hidden || + entry.State == PositionState.Unknown && GetUnknownStateVisibility(currentIndex) == LineVisibility.Hidden; + + var mapped = isHidden ? default : new FileLinePositionSpan( + entry.MappedPathOpt ?? string.Empty, + new LinePositionSpan( + new LinePosition(entry.MappedLine, character: 0), + new LinePosition(entry.MappedLine + unmappedEndLine - entry.UnmappedLine, lineLength)), + hasMappedPath: entry.MappedPathOpt != null); + + return new LineMapping(unmapped, mapped); + } + + for (int i = 1; i < Entries.Length; i++) + { + var next = Entries[i]; + + int unmappedEndLine = next.UnmappedLine - 2; + Debug.Assert(unmappedEndLine >= current.UnmappedLine - 1); + + // Skip empty spans - two consecutive #line directives or #line on the first line. + if (unmappedEndLine >= current.UnmappedLine) + { + // C#: Span ends just at the start of the line containing #line directive + // + // #line Current "file1" + // [|....\n + // ...........\n|] + // #line Next "file2" + // + // VB: Span starts at the beginning of the line following the #ExternalSource directive and ends at the start of the line preceding #End ExternalSource. + // #ExternalSource("file", 1) + // [|....\n + // ...........\n|] + // #End ExternalSource + + var endLine = lines[unmappedEndLine]; + int lineLength = endLine.EndIncludingLineBreak - endLine.Start; + + yield return CreateCurrentEntryMapping(current, unmappedEndLine, lineLength, currentIndex: i - 1); + } + + current = next; + } + + var lastLine = lines[^1]; + + // Last span (unless the last #line/#End ExternalSource is on the last line): + // #line Current "file1" + // [|....\n + // ...........\n|] + // + // #End ExternalSource + // [|....\n + // ...........\n|] + if (current.UnmappedLine <= lastLine.LineNumber) + { + int lineLength = lastLine.EndIncludingLineBreak - lastLine.Start; + int unmappedEndLine = lastLine.LineNumber; + + yield return CreateCurrentEntryMapping(current, unmappedEndLine, lineLength, currentIndex: Entries.Length - 1); + } } } } diff --git a/src/Compilers/Core/Portable/Syntax/LineMapping.cs b/src/Compilers/Core/Portable/Syntax/LineMapping.cs new file mode 100644 index 0000000000000..c6be68d43339d --- /dev/null +++ b/src/Compilers/Core/Portable/Syntax/LineMapping.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.Serialization; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + /// + /// Represents a line mapping defined by a single line mapping directive (#line in C# or #ExternalSource in VB). + /// + public readonly struct LineMapping : IEquatable + { + /// + /// The span in the syntax tree containing the line mapping directive. + /// + public readonly LinePositionSpan Span { get; } + + /// + /// If the line mapping directive maps the span into an explicitly specified file the is true. + /// If the path is not mapped is empty and is false. + /// If the line mapping directive marks hidden code is false. + /// + public readonly FileLinePositionSpan MappedSpan { get; } + + public LineMapping(LinePositionSpan span, FileLinePositionSpan mappedSpan) + { + Span = span; + MappedSpan = mappedSpan; + } + + public void Deconstruct(out LinePositionSpan span, out FileLinePositionSpan mappedSpan) + { + span = Span; + mappedSpan = MappedSpan; + } + + /// + /// True if the line mapping marks hidden code. + /// + public bool IsHidden + => !MappedSpan.IsValid; + + public override bool Equals(object? obj) + => obj is LineMapping other && Equals(other); + + public bool Equals(LineMapping other) + => Span.Equals(other.Span) && MappedSpan.Equals(other.MappedSpan); + + public override int GetHashCode() + => Hash.Combine(Span.GetHashCode(), MappedSpan.GetHashCode()); + + public static bool operator ==(LineMapping left, LineMapping right) + => left.Equals(right); + + public static bool operator !=(LineMapping left, LineMapping right) + => !(left == right); + + public override string? ToString() + => $"{Span} -> {MappedSpan}"; + } +} diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 99592e0644c85..17ea066d21b8e 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -333,7 +333,7 @@ public SourceText GetText(Encoding? encoding = null, SourceHashAlgorithm checksu /// /// Determine whether this node is structurally equivalent to another. /// - public bool IsEquivalentTo(SyntaxNode other) + public bool IsEquivalentTo([NotNullWhen(true)] SyntaxNode? other) { if (this == other) { @@ -348,6 +348,23 @@ public bool IsEquivalentTo(SyntaxNode other) return this.Green.IsEquivalentTo(other.Green); } + /// + /// Returns true if these two nodes are considered "incrementally identical". An incrementally identical node + /// occurs when a is incrementally parsed using + /// and the incremental parser is able to take the node from the original tree and use it in its entirety in the + /// new tree. In this case, the of each node will be the same, though + /// they could have different parents, and may occur at different positions in their respective trees. If two nodes are + /// incrementally identical, all children of each node will be incrementally identical as well. + /// + /// + /// Incrementally identical nodes can also appear within the same syntax tree, or syntax trees that did not arise + /// from . This can happen as the parser is allowed to construct parse + /// trees from shared nodes for efficiency. In all these cases though, it will still remain true that the incrementally + /// identical nodes could have different parents and may occur at different positions in their respective trees. + /// + public bool IsIncrementallyIdenticalTo([NotNullWhen(true)] SyntaxNode? other) + => this.Green != null && this.Green == other?.Green; + /// /// Determines whether the node represents a language construct that was actually parsed /// from the source code. Missing nodes are generated by the parser in error scenarios to diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNodeExtensions_Tracking.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNodeExtensions_Tracking.cs index 41d5b08ee93b6..4f2ccfa89c7f1 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNodeExtensions_Tracking.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNodeExtensions_Tracking.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -191,13 +192,14 @@ private static bool IsDescendant(SyntaxNode root, SyntaxNode node) private class CurrentNodes { - private readonly Dictionary> _idToNodeMap; + [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1320760", Constraint = "Avoid large object heap allocations")] + private readonly ImmutableSegmentedDictionary> _idToNodeMap; public CurrentNodes(SyntaxNode root) { // there could be multiple nodes with same annotation if a tree is rewritten with // same node injected multiple times. - var map = new Dictionary>(); + var map = new SegmentedDictionary>(); foreach (var node in root.GetAnnotatedNodesAndTokens(IdAnnotationKind).Select(n => n.AsNode()!)) { @@ -215,7 +217,7 @@ public CurrentNodes(SyntaxNode root) } } - _idToNodeMap = map.ToDictionary(kv => kv.Key, kv => (IReadOnlyList)ImmutableArray.CreateRange(kv.Value)); + _idToNodeMap = map.ToImmutableSegmentedDictionary(kv => kv.Key, kv => (IReadOnlyList)ImmutableArray.CreateRange(kv.Value)); } public IReadOnlyList GetNodes(SyntaxAnnotation id) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs index 9339deecdac41..9b55b1f47556f 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs @@ -758,6 +758,12 @@ public bool IsEquivalentTo(SyntaxNodeOrToken other) return (thisUnderlying == otherUnderlying) || (thisUnderlying != null && thisUnderlying.IsEquivalentTo(otherUnderlying)); } + /// + /// See and . + /// + public bool IsIncrementallyIdenticalTo(SyntaxNodeOrToken other) + => this.UnderlyingNode != null && this.UnderlyingNode == other.UnderlyingNode; + /// /// Returns a new that wraps the supplied token. /// diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs index b05a531ad3cb6..6053d33c739a1 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs @@ -686,5 +686,22 @@ public bool IsEquivalentTo(SyntaxToken token) (Node == null && token.Node == null) || (Node != null && token.Node != null && Node.IsEquivalentTo(token.Node)); } + + /// + /// Returns true if these two tokens are considered "incrementally identical". An incrementally identical token + /// occurs when a is incrementally parsed using + /// and the incremental parser is able to take the token from the original tree and use it in its entirety in the + /// new tree. In this case, the of each token will be the same, though + /// they could have different parents, and may occur at different positions in the respective trees. If two tokens are + /// incrementally identical, all trivial of each node will be incrementally identical as well. + /// + /// + /// Incrementally identical tokens can also appear within the same syntax tree, or syntax trees that did not arise + /// from . This can happen as the parser is allowed to construct parse + /// trees using shared tokens for efficiency. In all these cases though, it will still remain true that the incrementally + /// identical tokens could have different parents and may occur at different positions in their respective trees. + /// + public bool IsIncrementallyIdenticalTo(SyntaxToken token) + => this.Node != null && this.Node == token.Node; } } diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxTree.cs b/src/Compilers/Core/Portable/Syntax/SyntaxTree.cs index 1e35271c454cb..e442158832c52 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxTree.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxTree.cs @@ -220,13 +220,25 @@ public Task GetRootAsync(CancellationToken cancellationToken = defau /// A valid that contains path, line and column information. /// /// If the location path is mapped the resulting path is the path specified in the corresponding #line, - /// otherwise it's . + /// otherwise it's . /// - /// A location path is considered mapped if the first #line directive that precedes it and that - /// either specifies an explicit file path or is #line default exists and specifies an explicit path. + /// A location path is considered mapped if it is preceded by a line mapping directive that + /// either specifies an explicit file path or is #line default. /// public abstract FileLinePositionSpan GetMappedLineSpan(TextSpan span, CancellationToken cancellationToken = default); + /// + /// Returns empty sequence if there are no line mapping directives in the tree. + /// Otherwise, returns a sequence of pairs of spans: each describing a mapping of a span of the tree between two consecutive #line directives. + /// If the first directive is not on the first line the first pair describes mapping of the span preceding the first directive. + /// The last pair of the sequence describes mapping of the span following the last #line directive. + /// + /// + /// Empty sequence if the tree does not contain a line mapping directive. + /// Otherwise a non-empty sequence of . + /// + public abstract IEnumerable GetLineMappings(CancellationToken cancellationToken = default); + /// /// Returns the visibility for the line at the given position. /// diff --git a/src/Compilers/Core/Portable/Text/TextChangeRange.cs b/src/Compilers/Core/Portable/Text/TextChangeRange.cs index ce6166cfe689d..3e2bcaf8dc886 100644 --- a/src/Compilers/Core/Portable/Text/TextChangeRange.cs +++ b/src/Compilers/Core/Portable/Text/TextChangeRange.cs @@ -133,5 +133,10 @@ private string GetDebuggerDisplay() { return $"new TextChangeRange(new TextSpan({Span.Start}, {Span.Length}), {NewLength})"; } + + public override string ToString() + { + return $"TextChangeRange(Span={Span}, NewLength={NewLength})"; + } } } diff --git a/src/Compilers/Core/Portable/Text/TextUtilities.cs b/src/Compilers/Core/Portable/Text/TextUtilities.cs index 360236eefc797..75c04b8138fd0 100644 --- a/src/Compilers/Core/Portable/Text/TextUtilities.cs +++ b/src/Compilers/Core/Portable/Text/TextUtilities.cs @@ -83,7 +83,7 @@ public static void GetStartAndLengthOfLineBreakEndingAt(SourceText text, int ind /// internal static bool IsAnyLineBreakCharacter(char c) { - return c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029'; + return c is '\n' or '\r' or '\u0085' or '\u2028' or '\u2029'; } } } diff --git a/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs b/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs index f9d3ed8da9ee2..e4a078844f34d 100644 --- a/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs +++ b/src/Compilers/Core/Rebuild/CSharpCompilationFactory.cs @@ -109,7 +109,7 @@ private static (CSharpCompilationOptions, CSharpParseOptions) CreateCSharpCompil deterministic: true, xmlReferenceResolver: null, - sourceReferenceResolver: null, + sourceReferenceResolver: RebuildSourceReferenceResolver.Instance, metadataReferenceResolver: null, assemblyIdentityComparer: null, diff --git a/src/Compilers/Core/Rebuild/CompilationFactory.cs b/src/Compilers/Core/Rebuild/CompilationFactory.cs index ac78d7c378941..828aa0646560b 100644 --- a/src/Compilers/Core/Rebuild/CompilationFactory.cs +++ b/src/Compilers/Core/Rebuild/CompilationFactory.cs @@ -6,10 +6,12 @@ using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Rebuild @@ -40,10 +42,27 @@ public static CompilationFactory Create(string assemblyFileName, CompilationOpti public abstract SyntaxTree CreateSyntaxTree(string filePath, SourceText sourceText); + public Compilation CreateCompilation(IRebuildArtifactResolver resolver) + { + var tuple = OptionsReader.ResolveArtifacts(resolver, CreateSyntaxTree); + return CreateCompilation(tuple.SyntaxTrees, tuple.MetadataReferences); + } + public abstract Compilation CreateCompilation( ImmutableArray syntaxTrees, ImmutableArray metadataReferences); + public EmitResult Emit( + Stream rebuildPeStream, + Stream? rebuildPdbStream, + IRebuildArtifactResolver rebuildArtifactResolver, + CancellationToken cancellationToken) + => Emit( + rebuildPeStream, + rebuildPdbStream, + CreateCompilation(rebuildArtifactResolver), + cancellationToken); + public EmitResult Emit( Stream rebuildPeStream, Stream? rebuildPdbStream, @@ -98,7 +117,7 @@ public unsafe EmitResult Emit( { if (rebuildPdbStream is object) { - throw new ArgumentException("PDB stream must be null because the compilation has an embedded PDB", nameof(rebuildPdbStream)); + throw new ArgumentException(RebuildResources.PDB_stream_must_be_null_because_the_compilation_has_an_embedded_PDB, nameof(rebuildPdbStream)); } debugInformationFormat = DebugInformationFormat.Embedded; @@ -108,21 +127,23 @@ public unsafe EmitResult Emit( { if (rebuildPdbStream is null) { - throw new ArgumentException("A non-null PDB stream must be provided because the compilation does not have an embedded PDB", nameof(rebuildPdbStream)); + throw new ArgumentException(RebuildResources.A_non_null_PDB_stream_must_be_provided_because_the_compilation_does_not_have_an_embedded_PDB, nameof(rebuildPdbStream)); } debugInformationFormat = DebugInformationFormat.PortablePdb; var codeViewEntry = OptionsReader.PeReader.ReadDebugDirectory().Single(entry => entry.Type == DebugDirectoryEntryType.CodeView); var codeView = OptionsReader.PeReader.ReadCodeViewDebugDirectoryData(codeViewEntry); - pdbFilePath = codeView.Path ?? throw new InvalidOperationException("Could not get PDB file path"); + pdbFilePath = codeView.Path ?? throw new InvalidOperationException(RebuildResources.Could_not_get_PDB_file_path); } + var rebuildData = new RebuildData( + OptionsReader.GetMetadataCompilationOptionsBlobReader(), + getNonSourceFileDocumentNames(OptionsReader.PdbReader, OptionsReader.GetSourceFileCount())); var emitResult = rebuildCompilation.Emit( peStream: rebuildPeStream, pdbStream: rebuildPdbStream, xmlDocumentationStream: null, win32Resources: win32ResourceStream, - useRawWin32Resources: true, manifestResources: OptionsReader.GetManifestResources(), options: new EmitOptions( debugInformationFormat: debugInformationFormat, @@ -131,13 +152,26 @@ public unsafe EmitResult Emit( subsystemVersion: SubsystemVersion.Create(peHeader.MajorSubsystemVersion, peHeader.MinorSubsystemVersion)), debugEntryPoint: debugEntryPoint, metadataPEStream: null, - pdbOptionsBlobReader: OptionsReader.GetMetadataCompilationOptionsBlobReader(), + rebuildData: rebuildData, sourceLinkStream: sourceLink != null ? new MemoryStream(sourceLink) : null, embeddedTexts: embeddedTexts, cancellationToken: cancellationToken); return emitResult; + static ImmutableArray getNonSourceFileDocumentNames(MetadataReader pdbReader, int sourceFileCount) + { + var count = pdbReader.Documents.Count - sourceFileCount; + var builder = ArrayBuilder.GetInstance(count); + foreach (var documentHandle in pdbReader.Documents.Skip(sourceFileCount)) + { + var document = pdbReader.GetDocument(documentHandle); + var name = pdbReader.GetString(document.Name); + builder.Add(name); + } + return builder.ToImmutableAndFree(); + } + IMethodSymbol? getDebugEntryPoint() { if (OptionsReader.GetMainMethodInfo() is (string mainTypeName, string mainMethodName)) diff --git a/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs b/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs index 17cd66939f760..493188491583c 100644 --- a/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs +++ b/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs @@ -47,7 +47,6 @@ public class CompilationOptionsReader public bool HasMetadataCompilationOptions => TryGetMetadataCompilationOptions(out _); private MetadataCompilationOptions? _metadataCompilationOptions; - private ImmutableArray _metadataReferenceInfo; private byte[]? _sourceLinkUTF8; public CompilationOptionsReader(ILogger logger, MetadataReader pdbReader, PEReader peReader) @@ -66,7 +65,7 @@ public BlobReader GetMetadataCompilationOptionsBlobReader() { if (!TryGetMetadataCompilationOptionsBlobReader(out var reader)) { - throw new InvalidOperationException("Does not contain metadata compilation options"); + throw new InvalidOperationException(RebuildResources.Does_not_contain_metadata_compilation_options); } return reader; } @@ -101,7 +100,7 @@ public string GetLanguageName() var pdbCompilationOptions = GetMetadataCompilationOptions(); if (!pdbCompilationOptions.TryGetUniqueOption(CompilationOptionNames.Language, out var language)) { - throw new Exception("Invalid language name"); + throw new Exception(RebuildResources.Invalid_language_name); } return language; @@ -132,21 +131,6 @@ public Encoding GetEncoding() return _sourceLinkUTF8; } - public ImmutableArray GetMetadataReferences() - { - if (_metadataReferenceInfo.IsDefault) - { - if (!TryGetCustomDebugInformationBlobReader(MetadataReferenceInfoGuid, out var referencesBlob)) - { - throw new InvalidOperationException(); - } - - _metadataReferenceInfo = ParseMetadataReferenceInfo(referencesBlob).ToImmutableArray(); - } - - return _metadataReferenceInfo; - } - public string? GetMainTypeName() => GetMainMethodInfo()?.MainTypeName; public (string MainTypeName, string MainMethodName)? GetMainMethodInfo() @@ -181,16 +165,45 @@ public ImmutableArray GetMetadataReferences() return (typeName, methodName); } - private (SourceText? embeddedText, byte[]? compressedHash) ResolveEmbeddedSource(DocumentHandle document, SourceHashAlgorithm hashAlgorithm, Encoding encoding) + public int GetSourceFileCount() + => int.Parse(GetMetadataCompilationOptions().GetUniqueOption(CompilationOptionNames.SourceFileCount)); + + public IEnumerable GetEmbeddedSourceTextInfo() + => GetSourceTextInfoCore() + .Select(x => ResolveEmbeddedSource(x.DocumentHandle, x.SourceTextInfo)) + .WhereNotNull(); + + private IEnumerable<(DocumentHandle DocumentHandle, SourceTextInfo SourceTextInfo)> GetSourceTextInfoCore() + { + var encoding = GetEncoding(); + var sourceFileCount = GetSourceFileCount(); + foreach (var documentHandle in PdbReader.Documents.Take(sourceFileCount)) + { + var document = PdbReader.GetDocument(documentHandle); + var name = PdbReader.GetString(document.Name); + + var hashAlgorithmGuid = PdbReader.GetGuid(document.HashAlgorithm); + var hashAlgorithm = + hashAlgorithmGuid == HashAlgorithmSha1 ? SourceHashAlgorithm.Sha1 + : hashAlgorithmGuid == HashAlgorithmSha256 ? SourceHashAlgorithm.Sha256 + : SourceHashAlgorithm.None; + + var hash = PdbReader.GetBlobBytes(document.Hash); + var sourceTextInfo = new SourceTextInfo(name, hashAlgorithm, hash.ToImmutableArray(), encoding); + yield return (documentHandle, sourceTextInfo); + } + } + + private EmbeddedSourceTextInfo? ResolveEmbeddedSource(DocumentHandle document, SourceTextInfo sourceTextInfo) { byte[] bytes = (from handle in PdbReader.GetCustomDebugInformation(document) let cdi = PdbReader.GetCustomDebugInformation(handle) where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid select PdbReader.GetBlobBytes(cdi.Value)).SingleOrDefault(); - if (bytes == null) + if (bytes is null) { - return default; + return null; } int uncompressedSize = BitConverter.ToInt32(bytes, 0); @@ -199,7 +212,7 @@ where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid byte[]? compressedHash = null; if (uncompressedSize != 0) { - using var algorithm = CryptographicHashProvider.TryGetAlgorithm(hashAlgorithm) ?? throw new InvalidOperationException(); + using var algorithm = CryptographicHashProvider.TryGetAlgorithm(sourceTextInfo.HashAlgorithm) ?? throw new InvalidOperationException(); compressedHash = algorithm.ComputeHash(bytes); var decompressed = new MemoryStream(uncompressedSize); @@ -220,8 +233,8 @@ where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid using (stream) { // todo: IVT and EncodedStringText.Create? - var embeddedText = SourceText.From(stream, encoding: encoding, checksumAlgorithm: hashAlgorithm, canBeEmbedded: true); - return (embeddedText, compressedHash); + var embeddedText = SourceText.From(stream, encoding: sourceTextInfo.SourceTextEncoding, checksumAlgorithm: sourceTextInfo.HashAlgorithm, canBeEmbedded: true); + return new EmbeddedSourceTextInfo(sourceTextInfo, embeddedText, compressedHash?.ToImmutableArray() ?? ImmutableArray.Empty); } } @@ -271,35 +284,75 @@ where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid return result; } - public ImmutableArray GetSourceFileInfos(Encoding encoding) + public (ImmutableArray SyntaxTrees, ImmutableArray MetadataReferences) ResolveArtifacts( + IRebuildArtifactResolver resolver, + Func createSyntaxTreeFunc) { - var sourceFileCount = int.Parse( - GetMetadataCompilationOptions() - .GetUniqueOption(CompilationOptionNames.SourceFileCount)); + var syntaxTrees = ResolveSyntaxTrees(); + var metadataReferences = ResolveMetadataReferences(); + return (syntaxTrees, metadataReferences); - var builder = ImmutableArray.CreateBuilder(sourceFileCount); - foreach (var documentHandle in PdbReader.Documents.Take(sourceFileCount)) + ImmutableArray ResolveSyntaxTrees() { - var document = PdbReader.GetDocument(documentHandle); - var name = PdbReader.GetString(document.Name); - - var hashAlgorithmGuid = PdbReader.GetGuid(document.HashAlgorithm); - var hashAlgorithm = - hashAlgorithmGuid == HashAlgorithmSha1 ? SourceHashAlgorithm.Sha1 - : hashAlgorithmGuid == HashAlgorithmSha256 ? SourceHashAlgorithm.Sha256 - : SourceHashAlgorithm.None; + var sourceFileCount = GetSourceFileCount(); + var builder = ImmutableArray.CreateBuilder(sourceFileCount); + foreach (var (documentHandle, sourceTextInfo) in GetSourceTextInfoCore()) + { + SourceText sourceText; + if (ResolveEmbeddedSource(documentHandle, sourceTextInfo) is { } embeddedSourceTextInfo) + { + sourceText = embeddedSourceTextInfo.SourceText; + } + else + { + sourceText = resolver.ResolveSourceText(sourceTextInfo); + if (!sourceText.GetChecksum().SequenceEqual(sourceTextInfo.Hash)) + { + throw new InvalidOperationException(); + } + } - var hash = PdbReader.GetBlobBytes(document.Hash); - var embeddedContent = ResolveEmbeddedSource(documentHandle, hashAlgorithm, encoding); + var syntaxTree = createSyntaxTreeFunc(sourceTextInfo.OriginalSourceFilePath, sourceText); + builder.Add(syntaxTree); + } - builder.Add(new SourceFileInfo(name, hashAlgorithm, hash, embeddedContent.embeddedText, embeddedContent.compressedHash)); + return builder.MoveToImmutable(); } - return builder.MoveToImmutable(); + ImmutableArray ResolveMetadataReferences() + { + var builder = ImmutableArray.CreateBuilder(); + foreach (var metadataReferenceInfo in GetMetadataReferenceInfo()) + { + var metadataReference = resolver.ResolveMetadataReference(metadataReferenceInfo); + if (metadataReference.Properties.EmbedInteropTypes != metadataReferenceInfo.EmbedInteropTypes) + { + throw new InvalidOperationException(); + } + + if (!( + (metadataReferenceInfo.ExternAlias is null && metadataReference.Properties.Aliases.IsEmpty) || + (metadataReferenceInfo.ExternAlias == metadataReference.Properties.Aliases.SingleOrDefault()) + )) + { + throw new InvalidOperationException(); + } + + builder.Add(metadataReference); + } + + return builder.ToImmutable(); + } } - private static IEnumerable ParseMetadataReferenceInfo(BlobReader blobReader) + public IEnumerable GetMetadataReferenceInfo() { + if (!TryGetCustomDebugInformationBlobReader(MetadataReferenceInfoGuid, out var blobReader)) + { + throw new InvalidOperationException(); + } + + var builder = ImmutableArray.CreateBuilder(); while (blobReader.RemainingBytes > 0) { // Order of information @@ -328,7 +381,7 @@ private static IEnumerable ParseMetadataReferenceInfo(Blo // byte has data. if ((embedInteropTypesAndKind & 0b11111100) != 0) { - throw new InvalidDataException($"Unexpected value for EmbedInteropTypes/MetadataImageKind {embedInteropTypesAndKind}"); + throw new InvalidDataException(string.Format(RebuildResources.Unexpected_value_for_EmbedInteropTypes_MetadataImageKind_0, embedInteropTypesAndKind)); } var embedInteropTypes = (embedInteropTypesAndKind & 0b10) == 0b10; @@ -343,13 +396,13 @@ private static IEnumerable ParseMetadataReferenceInfo(Blo if (string.IsNullOrEmpty(externAliases)) { yield return new MetadataReferenceInfo( - timestamp, - imageSize, name, mvid, - externAlias: null, + ExternAlias: null, kind, - embedInteropTypes); + embedInteropTypes, + timestamp, + imageSize); } else { @@ -359,13 +412,13 @@ private static IEnumerable ParseMetadataReferenceInfo(Blo // The compiler itself just sees "global" as a reference without any aliases // and we need to mimic that here. yield return new MetadataReferenceInfo( - timestamp, - imageSize, name, mvid, - alias == "global" ? null : alias, + ExternAlias: alias == "global" ? null : alias, kind, - embedInteropTypes); + embedInteropTypes, + timestamp, + imageSize); } } } @@ -412,7 +465,7 @@ where PdbReader.GetGuid(cdi.Kind) == infoGuid { if (value is null or { Length: 0 }) { - throw new InvalidDataException("Encountered null or empty key for compilation options pairs"); + throw new InvalidDataException(RebuildResources.Encountered_null_or_empty_key_for_compilation_options_pairs); } key = value; diff --git a/src/Compilers/Core/Rebuild/Extensions.cs b/src/Compilers/Core/Rebuild/Extensions.cs index 918d2bf329058..ed745b61ba941 100644 --- a/src/Compilers/Core/Rebuild/Extensions.cs +++ b/src/Compilers/Core/Rebuild/Extensions.cs @@ -17,7 +17,7 @@ internal static void SkipNullTerminator(ref this BlobReader blobReader) var b = blobReader.ReadByte(); if (b != '\0') { - throw new InvalidDataException($"Encountered unexpected byte \"{b}\" when expecting a null terminator"); + throw new InvalidDataException(string.Format(RebuildResources.Encountered_unexpected_byte_0_when_expecting_a_null_terminator, b)); } } diff --git a/src/Compilers/Core/Rebuild/IRebuildArtifactResolver.cs b/src/Compilers/Core/Rebuild/IRebuildArtifactResolver.cs new file mode 100644 index 0000000000000..c191dff86589b --- /dev/null +++ b/src/Compilers/Core/Rebuild/IRebuildArtifactResolver.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Rebuild +{ + public interface IRebuildArtifactResolver + { + SourceText ResolveSourceText(SourceTextInfo sourceTextInfo); + + MetadataReference ResolveMetadataReference(MetadataReferenceInfo metadataReferenceInfo); + } +} diff --git a/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs b/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs index c45cf2f592881..b73ec8ca8753c 100644 --- a/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs +++ b/src/Compilers/Core/Rebuild/MetadataCompilationOptions.cs @@ -51,7 +51,7 @@ public string GetUniqueOption(string optionName) var optionValues = _options.Where(pair => pair.optionName == optionName).ToArray(); if (optionValues.Length != 1) { - throw new InvalidOperationException($"{optionName} exists {optionValues.Length} times in compilation options"); + throw new InvalidOperationException(string.Format(RebuildResources._0_exists_1_times_in_compilation_options, optionName, optionValues.Length)); } return optionValues[0].value; diff --git a/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs b/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs deleted file mode 100644 index 17bb5653e6848..0000000000000 --- a/src/Compilers/Core/Rebuild/MetadataReferenceInfo.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.IO; -using Microsoft.CodeAnalysis; - -namespace Microsoft.CodeAnalysis.Rebuild -{ - public readonly struct MetadataReferenceInfo - { - public readonly int Timestamp; - public readonly int ImageSize; - public readonly string Name; - public readonly FileInfo FileInfo; - public readonly Guid Mvid; - public readonly string? ExternAlias; - public readonly MetadataImageKind Kind; - public readonly bool EmbedInteropTypes; - - public MetadataReferenceInfo( - int timestamp, - int imageSize, - string name, - Guid mvid, - string? externAlias, - MetadataImageKind kind, - bool embedInteropTypes) - { - Timestamp = timestamp; - ImageSize = imageSize; - Name = name; - Mvid = mvid; - ExternAlias = externAlias; - Kind = kind; - EmbedInteropTypes = embedInteropTypes; - FileInfo = new FileInfo(name); - } - - public override string ToString() - { - return $"{Name}::{Mvid}::{Timestamp}"; - } - } -} diff --git a/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj b/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj index a2af82cde4c38..5c29391f49c6d 100644 --- a/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj +++ b/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/src/Compilers/Core/Rebuild/RebuildResources.resx b/src/Compilers/Core/Rebuild/RebuildResources.resx new file mode 100644 index 0000000000000..3971925d31cd4 --- /dev/null +++ b/src/Compilers/Core/Rebuild/RebuildResources.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{0}' exists '{1}' times in compilation options. + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + Cannot create compilation options: {0} + + + Could not get PDB file path. + + + Does not contain metadata compilation options. + + + Encountered null or empty key for compilation options pairs. + + + Encountered unexpected byte '{0}' when expecting a null terminator. + + + Invalid language name. + + + PDB stream must be null because the compilation has an embedded PDB. + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs b/src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs new file mode 100644 index 0000000000000..d4266d342b855 --- /dev/null +++ b/src/Compilers/Core/Rebuild/RebuildSourceReferenceResolver.cs @@ -0,0 +1,69 @@ +// 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 Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Rebuild +{ + /// + /// For most operations the rebuild scenario does not need to provide a + /// . However the usage of #line in the program + /// forces our hand. + /// + /// A #line pragma which has a document argument goes through the same path normalization + /// as other files. This is always done relative to the file which contains the #line + /// pragma (the containing file will be the base path). + /// + /// It is possible for multiple files, in different directories, to have a #line which + /// refers to the same file. For example lib1.cs and dir/lib2.cs can both have the following + /// entry: + /// + /// #line 42 "data.txt" + /// + /// Without a resolver entry to provide a normalized path, based on the directory, it is possible + /// these get normalized out to the same path. Particularly when pathmap is involved and we + /// are observing unix paths in a windows rebuild (or vice versa). This is because pathmap can + /// create paths which are illegal to the current operating system (by design). + /// + internal sealed class RebuildSourceReferenceResolver : SourceReferenceResolver + { + internal static RebuildSourceReferenceResolver Instance { get; } = new RebuildSourceReferenceResolver(); + + private RebuildSourceReferenceResolver() + { + } + + public override bool Equals(object? other) => object.ReferenceEquals(this, other); + + public override int GetHashCode() => 0; + + public override string? NormalizePath(string path, string? baseFilePath) + { + if (baseFilePath is null) + { + return path; + } + + // The only invariant we need to maintain here is that for a given external file identified + // via #line directive across many source files we always return the same name for that + // file. What name we return is irrelevant, it just needs to be the same. The actual name + // return here is eventually discarded and we end up writing the name from the PDB. + var index = baseFilePath.LastIndexOfAny(new[] { '/', '\\' }); + if (index > 0) + { + var root = baseFilePath.Substring(0, index); + return @$"{root}\{path}"; + } + + return null; + } + + public override Stream OpenRead(string resolvedPath) => throw ExceptionUtilities.Unreachable; + + public override string? ResolveReference(string path, string? baseFilePath) => throw ExceptionUtilities.Unreachable; + } + +} diff --git a/src/Compilers/Core/Rebuild/Records.cs b/src/Compilers/Core/Rebuild/Records.cs index 38fa2411ec403..404949816b7a2 100644 --- a/src/Compilers/Core/Rebuild/Records.cs +++ b/src/Compilers/Core/Rebuild/Records.cs @@ -3,10 +3,31 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; +using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Rebuild { + public sealed record EmbeddedSourceTextInfo( + SourceTextInfo SourceTextInfo, + SourceText SourceText, + ImmutableArray CompressedHash); + + public sealed record SourceTextInfo( + string OriginalSourceFilePath, + SourceHashAlgorithm HashAlgorithm, + ImmutableArray Hash, + Encoding SourceTextEncoding); + + public sealed record MetadataReferenceInfo( + string FileName, + Guid ModuleVersionId, + string? ExternAlias, + MetadataImageKind ImageKind, + bool EmbedInteropTypes, + int Timestamp, + int ImageSize); } diff --git a/src/Compilers/Core/Rebuild/SourceFileInfo.cs b/src/Compilers/Core/Rebuild/SourceFileInfo.cs deleted file mode 100644 index 180f0c7a9d5f5..0000000000000 --- a/src/Compilers/Core/Rebuild/SourceFileInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Rebuild -{ - public readonly struct SourceFileInfo - { - public string SourceFilePath { get; } - public SourceHashAlgorithm HashAlgorithm { get; } - public byte[] Hash { get; } - public SourceText? EmbeddedText { get; } - public byte[]? EmbeddedCompressedHash { get; } - - public SourceFileInfo( - string sourceFilePath, - SourceHashAlgorithm hashAlgorithm, - byte[] hash, - SourceText? embeddedText, - byte[]? embeddedCompressedHash) - { - SourceFilePath = sourceFilePath; - HashAlgorithm = hashAlgorithm; - Hash = hash; - EmbeddedText = embeddedText; - EmbeddedCompressedHash = embeddedCompressedHash; - } - } -} \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/SourceLink.cs b/src/Compilers/Core/Rebuild/SourceLink.cs deleted file mode 100644 index 39764edbe5a7d..0000000000000 --- a/src/Compilers/Core/Rebuild/SourceLink.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.CodeAnalysis.Rebuild -{ - /// An entry in the source-link.json dictionary. - public readonly struct SourceLink - { - public string Prefix { get; } - public string Replace { get; } - - public SourceLink(string prefix, string replace) - { - Prefix = prefix; - Replace = replace; - } - } -} diff --git a/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs b/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs index f127491cefe18..aa038058f594c 100644 --- a/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs +++ b/src/Compilers/Core/Rebuild/VisualBasicCompilationFactory.cs @@ -78,7 +78,7 @@ private static VisualBasicCompilationOptions CreateVisualBasicCompilationOptions var diagnostic = diagnostics?.FirstOrDefault(x => x.IsUnsuppressedError); if (diagnostic is object) { - throw new Exception($"Cannot create compilation options: {diagnostic}"); + throw new Exception(string.Format(RebuildResources.Cannot_create_compilation_options_0, diagnostic)); } } @@ -117,7 +117,7 @@ private static VisualBasicCompilationOptions CreateVisualBasicCompilationOptions concurrentBuild: true, deterministic: true, xmlReferenceResolver: null, - sourceReferenceResolver: null, + sourceReferenceResolver: RebuildSourceReferenceResolver.Instance, metadataReferenceResolver: null, assemblyIdentityComparer: null, strongNameProvider: null, diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.cs.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.cs.xlf new file mode 100644 index 0000000000000..e6a62881b2b96 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.cs.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.de.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.de.xlf new file mode 100644 index 0000000000000..716ed686e8a68 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.de.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.es.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.es.xlf new file mode 100644 index 0000000000000..eebeff9ca59ee --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.es.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.fr.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.fr.xlf new file mode 100644 index 0000000000000..dba8c52fa4fa4 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.fr.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.it.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.it.xlf new file mode 100644 index 0000000000000..71f7bca0fb6de --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.it.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.ja.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.ja.xlf new file mode 100644 index 0000000000000..5374a3e2ec0b8 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.ja.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.ko.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.ko.xlf new file mode 100644 index 0000000000000..cbd6ccd4b6799 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.ko.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.pl.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.pl.xlf new file mode 100644 index 0000000000000..d5f53a1b4be42 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.pl.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.pt-BR.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.pt-BR.xlf new file mode 100644 index 0000000000000..f33d5cda1d2fd --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.pt-BR.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.ru.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.ru.xlf new file mode 100644 index 0000000000000..8d1b62f73bb6e --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.ru.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.tr.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.tr.xlf new file mode 100644 index 0000000000000..827587fa80614 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.tr.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.zh-Hans.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.zh-Hans.xlf new file mode 100644 index 0000000000000..da2cf9de58560 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.zh-Hans.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Rebuild/xlf/RebuildResources.zh-Hant.xlf b/src/Compilers/Core/Rebuild/xlf/RebuildResources.zh-Hant.xlf new file mode 100644 index 0000000000000..438731677df53 --- /dev/null +++ b/src/Compilers/Core/Rebuild/xlf/RebuildResources.zh-Hant.xlf @@ -0,0 +1,57 @@ + + + + + + '{0}' exists '{1}' times in compilation options. + '{0}' exists '{1}' times in compilation options. + + + + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + A non-null PDB stream must be provided because the compilation does not have an embedded PDB. + + + + Cannot create compilation options: {0} + Cannot create compilation options: {0} + + + + Could not get PDB file path. + Could not get PDB file path. + + + + Does not contain metadata compilation options. + Does not contain metadata compilation options. + + + + Encountered null or empty key for compilation options pairs. + Encountered null or empty key for compilation options pairs. + + + + Encountered unexpected byte '{0}' when expecting a null terminator. + Encountered unexpected byte '{0}' when expecting a null terminator. + + + + Invalid language name. + Invalid language name. + + + + PDB stream must be null because the compilation has an embedded PDB. + PDB stream must be null because the compilation has an embedded PDB. + + + + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + Unexpected value for EmbedInteropTypes/MetadataImageKind '{0}'. + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/RebuildTest/CompilationRebuildArtifactResolver.cs b/src/Compilers/Core/RebuildTest/CompilationRebuildArtifactResolver.cs new file mode 100644 index 0000000000000..d26078412b194 --- /dev/null +++ b/src/Compilers/Core/RebuildTest/CompilationRebuildArtifactResolver.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Rebuild.UnitTests +{ + internal sealed class CompilationRebuildArtifactResolver : IRebuildArtifactResolver + { + internal Compilation Compilation { get; } + + public CompilationRebuildArtifactResolver(Compilation compilation) + { + Compilation = compilation; + } + + public MetadataReference ResolveMetadataReference(MetadataReferenceInfo metadataReferenceInfo) => + Compilation + .References + .Single(x => + x.GetModuleVersionId() == metadataReferenceInfo.ModuleVersionId && + x.Properties.Aliases.SingleOrDefault() == metadataReferenceInfo.ExternAlias); + + public SourceText ResolveSourceText(SourceTextInfo sourceTextInfo) => + Compilation + .SyntaxTrees + .Select(x => x.GetText()) + .Single(x => x.GetChecksum().SequenceEqual(sourceTextInfo.Hash)); + + } +} diff --git a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs index e9f1b4436b839..46846399b67b6 100644 --- a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs +++ b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs @@ -119,8 +119,7 @@ private void VerifyRoundTrip(CommonCompiler commonCompiler, string peFilePath, s peStream, pdbStream, Path.GetFileName(peFilePath), - compilation.SyntaxTrees.ToImmutableArray(), - compilation.References.ToImmutableArray()); + new CompilationRebuildArtifactResolver(compilation)); } private void AddCSharpSourceFiles() @@ -176,6 +175,45 @@ void Method() Console.WriteLine(a3); } } +"); + + AddSourceFile("lib4.cs", @" +using System; + +class Library4 +{ +#line 42 ""data.txt"" + void Method() + { + } +} +"); + + AddSourceFile("lib5.cs", @" +using System; + +class Library5 +{ +#line 42 ""data.txt"" + void Method() + { + } +} +"); + + AddSourceFile(Path.Combine("dir1", "lib1.cs"), @" +using System; + +namespace Nested +{ + #line 13 ""data.txt"" + class NestedLibrary + { + void Method() + { + } + } +} "); } @@ -187,11 +225,21 @@ void Method() new CommandInfo("hw.cs", "test.exe", null), PermutateOptimizations, PermutateExeKinds, PermutatePdbFormat); Permutate(new CommandInfo("lib1.cs", "test.dll", null), - PermutateOptimizations, PermutateDllKinds, PermutatePdbFormat); + PermutateOptimizations, PermutateDllKinds, PermutatePdbFormat, PermutatePathMap); Permutate(new CommandInfo("lib2.cs /target:library /r:SystemRuntime=System.Runtime.dll /debug:embedded", "test.dll", null), PermutateOptimizations); Permutate(new CommandInfo("lib3.cs /target:library", "test.dll", null), PermutateOptimizations, PermutateExternAlias, PermutatePdbFormat); + Permutate(new CommandInfo("lib4.cs /target:library", "test.dll", null), + PermutateOptimizations, PermutatePdbFormat, PermutatePathMap); + + // This uses a #line directive with the same file name but in different source directories. + // Need to make sure that we map the same file name but different base paths correctly + Permutate(new CommandInfo($"lib4.cs {Path.Combine("dir1", "lib1.cs")} /target:library", "test.dll", null), + PermutatePdbFormat, PermutatePathMap); + + Permutate(new CommandInfo("lib4.cs lib5.cs /target:library", "test.dll", null), + PermutateOptimizations, PermutatePdbFormat, PermutatePathMap); return list; @@ -206,6 +254,25 @@ void Permutate(CommandInfo commandInfo, params Func PermutatePathMap(CommandInfo commandInfo) + { + yield return commandInfo; + yield return commandInfo with + { + CommandLine = commandInfo.CommandLine + $" /pathmap:{RootDirectory}=/root/test", + }; + yield return commandInfo with + { + CommandLine = commandInfo.CommandLine + $@" /pathmap:{RootDirectory}=j:\other_root", + }; + + // Path map doesn't care about path legality, it's a simple substitute + yield return commandInfo with + { + CommandLine = commandInfo.CommandLine + $@" /pathmap:""{RootDirectory}=???""", + }; + } + // Permutate the alias before and after the standard references so that we make sure the // rebuild is resistent to the ordering of aliases. static IEnumerable PermutateExternAlias(CommandInfo commandInfo) @@ -319,6 +386,30 @@ Public Function Add(left As Integer, right As Integer) As Integer return left + right End Function End Module +"); + + AddSourceFile("lib2.vb", @" +Imports System +Public Module Lib2 +#ExternalSource(""data.txt"", 30) + Public Function Add(left As Integer, right As Integer) As Integer + return left + right + End Function +#End ExternalSource +End Module +"); + + AddSourceFile(Path.Combine("dir1", "lib1.vb"), @" +Imports System +Namespace Nested + Public Module Lib1 +#ExternalSource(""data.txt"", 30) + Public Function Add(left As Integer, right As Integer) As Integer + return left + right + End Function +#End ExternalSource + End Module +End Namespace "); } @@ -335,6 +426,15 @@ End Module Permutate( new CommandInfo(@"lib1.vb /debug:embedded /d:_MYTYPE=""Empty"" /vbruntime:Microsoft.VisualBasic.dll", "test.dll", null), PermutateOptimizations, PermutateDllKinds); + Permutate( + new CommandInfo("lib2.vb /target:library /debug:embedded", "test.dll", null), + PermutatePathMap, PermutateRuntime); + + // This uses a #ExternalSource directive with the same file name but in different source directories. + // Need to make sure that we map the same file name but different base paths correctly + Permutate( + new CommandInfo(@$"lib2.vb {Path.Combine("dir1", "lib1.vb")} /target:library /debug:embedded", "test.dll", null), + PermutatePathMap, PermutateRuntime); return list; @@ -349,6 +449,15 @@ void Permutate(CommandInfo commandInfo, params Func PermutatePathMap(CommandInfo commandInfo) + { + yield return commandInfo; + yield return commandInfo with + { + CommandLine = commandInfo.CommandLine + $" /pathmap:{RootDirectory}=/root/test", + }; + } + static IEnumerable PermutateOptimizations(CommandInfo commandInfo) { // No options at all for optimization diff --git a/src/Compilers/Core/RebuildTest/RoundTripUtil.cs b/src/Compilers/Core/RebuildTest/RoundTripUtil.cs index 4e16b1f5cc671..e2cef09b916f2 100644 --- a/src/Compilers/Core/RebuildTest/RoundTripUtil.cs +++ b/src/Compilers/Core/RebuildTest/RoundTripUtil.cs @@ -32,8 +32,7 @@ public static void VerifyRoundTrip( MemoryStream peStream, MemoryStream? pdbStream, string assemblyFileName, - ImmutableArray syntaxTrees, - ImmutableArray metadataReferences, + IRebuildArtifactResolver rebuildArtifactResolver, CancellationToken cancellationToken = default) { using var peReader = new PEReader(peStream); @@ -45,43 +44,22 @@ public static void VerifyRoundTrip( var factory = LoggerFactory.Create(configure => { }); var logger = factory.CreateLogger("RoundTripVerification"); var optionsReader = new CompilationOptionsReader(logger, pdbReader, peReader); - VerifyMetadataReferenceInfo(optionsReader, metadataReferences); - var compilationFactory = CompilationFactory.Create( assemblyFileName, optionsReader); using var rebuildPeStream = new MemoryStream(); using var rebuildPdbStream = optionsReader.HasEmbeddedPdb ? null : new MemoryStream(); - var emitResult = compilationFactory.Emit(rebuildPeStream, rebuildPdbStream, syntaxTrees, metadataReferences, cancellationToken); + var emitResult = compilationFactory.Emit( + rebuildPeStream, + rebuildPdbStream, + rebuildArtifactResolver, + cancellationToken); + Assert.True(emitResult.Success); Assert.Equal(peStream.ToArray(), rebuildPeStream.ToArray()); Assert.Equal(pdbStream?.ToArray(), rebuildPdbStream?.ToArray()); } - public static void VerifyMetadataReferenceInfo(CompilationOptionsReader optionsReader, ImmutableArray metadataReferences) - { - var count = 0; - foreach (var info in optionsReader.GetMetadataReferences()) - { - count++; - var metadataReference = metadataReferences.FirstOrDefault(x => - info.Mvid == x.GetModuleVersionId() && - info.ExternAlias == GetSingleAlias(x)); - AssertEx.NotNull(metadataReference); - - string? GetSingleAlias(MetadataReference metadataReference) - { - Assert.True(metadataReference.Properties.Aliases.Length is 0 or 1); - return metadataReference.Properties.Aliases.Length == 1 - ? metadataReference.Properties.Aliases[0] - : null; - } - } - - Assert.Equal(metadataReferences.Length, count); - - } - private record EmitInfo(ImmutableArray PEBytes, PEReader PEReader, ImmutableArray PdbBytes, MetadataReader PdbReader) : IDisposable { public void Dispose() => PEReader.Dispose(); diff --git a/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs b/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs index 8fee1d6614ec4..c362d53678f2f 100644 --- a/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs +++ b/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs @@ -32,7 +32,7 @@ internal static RunRequest GetRunRequest(BuildRequest req) break; } - return new RunRequest(language, currentDirectory, tempDirectory, libDirectory, arguments); + return new RunRequest(req.RequestId, language, currentDirectory, tempDirectory, libDirectory, arguments); } internal static string[] GetCommandLineArguments(BuildRequest req, out string? currentDirectory, out string? tempDirectory, out string? libDirectory) diff --git a/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs b/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs index 3ada2bf762f97..067155ff6d49d 100644 --- a/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs +++ b/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs @@ -8,7 +8,6 @@ using System.IO; using System.IO.Pipes; using System.Net.Sockets; -using System.Runtime.InteropServices.WindowsRuntime; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -51,20 +50,13 @@ async Task ProcessCore() { using var clientConnection = await clientConnectionTask.ConfigureAwait(false); var request = await clientConnection.ReadBuildRequestAsync(cancellationToken).ConfigureAwait(false); - - if (request.ProtocolVersion != BuildProtocolConstants.ProtocolVersion) - { - return await WriteBuildResponseAsync( - clientConnection, - new MismatchedVersionBuildResponse(), - CompletionData.RequestError, - cancellationToken).ConfigureAwait(false); - } + Logger.Log($"Received request {request.RequestId} of type {request.GetType()}"); if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new IncorrectHashBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false); @@ -74,6 +66,7 @@ async Task ProcessCore() { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new ShutdownBuildResponse(Process.GetCurrentProcess().Id), new CompletionData(CompletionReason.RequestCompleted, shutdownRequested: true), cancellationToken).ConfigureAwait(false); @@ -83,6 +76,7 @@ async Task ProcessCore() { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new RejectedBuildResponse("Compilation not allowed at this time"), CompletionData.RequestCompleted, cancellationToken).ConfigureAwait(false); @@ -92,6 +86,7 @@ async Task ProcessCore() { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new RejectedBuildResponse("Not enough resources to accept connection"), CompletionData.RequestError, cancellationToken).ConfigureAwait(false); @@ -101,12 +96,12 @@ async Task ProcessCore() } } - private async Task WriteBuildResponseAsync(IClientConnection clientConnection, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) + private async Task WriteBuildResponseAsync(IClientConnection clientConnection, Guid requestId, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) { var message = response switch { - RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {clientConnection.LoggingIdentifier}", - _ => $"Writing {response.Type} response for {clientConnection.LoggingIdentifier}" + RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {requestId}", + _ => $"Writing {response.Type} response for {requestId}" }; Logger.Log(message); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); @@ -143,13 +138,14 @@ private async Task ProcessCompilationRequestAsync(IClientConnect { // The compilation task should never throw. If it does we need to assume that the compiler is // in a bad state and need to issue a RequestError - Logger.LogException(ex, $"Exception running compilation for {clientConnection.LoggingIdentifier}"); + Logger.LogException(ex, $"Exception running compilation for {request.RequestId}"); response = new RejectedBuildResponse($"Exception during compilation: {ex.Message}"); completionData = CompletionData.RequestError; } return await WriteBuildResponseAsync( clientConnection, + request.RequestId, response, completionData, cancellationToken).ConfigureAwait(false); diff --git a/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs b/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs index 49d14abe47b1d..06dd245c9283f 100644 --- a/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs +++ b/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs @@ -21,14 +21,16 @@ namespace Microsoft.CodeAnalysis.CompilerServer { internal readonly struct RunRequest { + public Guid RequestId { get; } public string Language { get; } public string? WorkingDirectory { get; } public string? TempDirectory { get; } public string? LibDirectory { get; } public string[] Arguments { get; } - public RunRequest(string language, string? workingDirectory, string? tempDirectory, string? libDirectory, string[] arguments) + public RunRequest(Guid requestId, string language, string? workingDirectory, string? tempDirectory, string? libDirectory, string[] arguments) { + RequestId = requestId; Language = language; WorkingDirectory = workingDirectory; TempDirectory = tempDirectory; @@ -72,7 +74,7 @@ private bool CheckAnalyzers(string baseDirectory, ImmutableArray? errorMessages)) { + Logger.Log($"Rejected: {request.RequestId}: for analyer load issues {string.Join(";", errorMessages)}"); return new AnalyzerInconsistencyBuildResponse(new ReadOnlyCollection(errorMessages)); } - Logger.Log($"Begin {request.Language} compiler run"); - TextWriter output = new StringWriter(CultureInfo.InvariantCulture); - int returnCode = compiler.Run(output, cancellationToken); - var outputString = output.ToString(); - Logger.Log(@$" -End {request.Language} Compilation complete. + Logger.Log($"Begin {request.RequestId} {request.Language} compiler run"); + try + { + TextWriter output = new StringWriter(CultureInfo.InvariantCulture); + int returnCode = compiler.Run(output, cancellationToken); + var outputString = output.ToString(); + Logger.Log(@$"End {request.RequestId} {request.Language} compiler run Return code: {returnCode} Output: {outputString}"); - return new CompletedBuildResponse(returnCode, utf8output, outputString); + return new CompletedBuildResponse(returnCode, utf8output, outputString); + } + catch (Exception ex) + { + Logger.LogException(ex, $"Running compilation for {request.RequestId}"); + throw; + } } } } diff --git a/src/Compilers/Server/VBCSCompiler/IClientConnection.cs b/src/Compilers/Server/VBCSCompiler/IClientConnection.cs index 1df9ef1c26456..42e2129d211a3 100644 --- a/src/Compilers/Server/VBCSCompiler/IClientConnection.cs +++ b/src/Compilers/Server/VBCSCompiler/IClientConnection.cs @@ -14,12 +14,6 @@ namespace Microsoft.CodeAnalysis.CompilerServer /// internal interface IClientConnection : IDisposable { - /// - /// A value which can be used to identify this connection for logging purposes only. It has - /// no guarantee of uniqueness. - /// - string LoggingIdentifier { get; } - /// /// This task resolves if the client disconnects from the server. /// diff --git a/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs b/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs index da1f26b49c850..f8509a3452058 100644 --- a/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs +++ b/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs @@ -17,6 +17,6 @@ internal interface ICompilerServerHost { ICompilerServerLogger Logger { get; } - BuildResponse RunCompilation(RunRequest request, CancellationToken cancellationToken); + BuildResponse RunCompilation(in RunRequest request, CancellationToken cancellationToken); } } diff --git a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs index fdb3cf20e0670..5df1c170ab14c 100644 --- a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs +++ b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs @@ -22,15 +22,13 @@ internal sealed class NamedPipeClientConnection : IClientConnection public NamedPipeServerStream Stream { get; } public ICompilerServerLogger Logger { get; } - public string LoggingIdentifier { get; } public bool IsDisposed { get; private set; } public Task DisconnectTask => DisconnectTaskCompletionSource.Task; - internal NamedPipeClientConnection(NamedPipeServerStream stream, string loggingIdentifier, ICompilerServerLogger logger) + internal NamedPipeClientConnection(NamedPipeServerStream stream, ICompilerServerLogger logger) { Stream = stream; - LoggingIdentifier = loggingIdentifier; Logger = logger; } @@ -46,7 +44,7 @@ public void Dispose() } catch (Exception ex) { - Logger.LogException(ex, $"Error closing client connection {LoggingIdentifier}"); + Logger.LogException(ex, $"Error closing client connection"); } IsDisposed = true; @@ -73,11 +71,11 @@ async Task MonitorDisconnect() { try { - await BuildServerConnection.MonitorDisconnectAsync(Stream, LoggingIdentifier, Logger, DisconnectCancellationTokenSource.Token).ConfigureAwait(false); + await BuildServerConnection.MonitorDisconnectAsync(Stream, request.RequestId, Logger, DisconnectCancellationTokenSource.Token).ConfigureAwait(false); } catch (Exception ex) { - Logger.LogException(ex, $"Error monitoring disconnect {LoggingIdentifier}"); + Logger.LogException(ex, $"Error monitoring disconnect {request.RequestId}"); } finally { diff --git a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs index 2419128053043..1b860f8856f19 100644 --- a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs +++ b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs @@ -68,18 +68,11 @@ public void BeginListening() // large builds such as dotnet/roslyn or dotnet/runtime var listenCount = Math.Min(4, Environment.ProcessorCount); _listenTasks = new Task[listenCount]; - int clientLoggingIdentifier = 0; for (int i = 0; i < listenCount; i++) { - var task = Task.Run(() => ListenCoreAsync(PipeName, Logger, _queue, GetNextClientLoggingIdentifier, _cancellationTokenSource.Token)); + var task = Task.Run(() => ListenCoreAsync(PipeName, Logger, _queue, _cancellationTokenSource.Token)); _listenTasks[i] = task; } - - string GetNextClientLoggingIdentifier() - { - var count = Interlocked.Increment(ref clientLoggingIdentifier); - return $"Client{count}"; - } } public void EndListening() @@ -175,7 +168,6 @@ private static async Task ListenCoreAsync( string pipeName, ICompilerServerLogger logger, AsyncQueue queue, - Func getClientLoggingIdentifier, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) @@ -213,7 +205,7 @@ private static async Task ListenCoreAsync( await connectTask.ConfigureAwait(false); logger.Log("Pipe connection established."); - var connection = new NamedPipeClientConnection(pipeStream, getClientLoggingIdentifier(), logger); + var connection = new NamedPipeClientConnection(pipeStream, logger); queue.Enqueue(new ListenResult(connection: connection)); } catch (OperationCanceledException) diff --git a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs index 007fe40de92b6..a67d4e8d767eb 100644 --- a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs +++ b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs @@ -13,13 +13,13 @@ internal static class VBCSCompiler { public static int Main(string[] args) { - using var logger = new CompilerServerLogger(); + using var logger = new CompilerServerLogger("VBCSCompiler"); NameValueCollection appSettings; try { #if BOOTSTRAP - ExitingTraceListener.Install(); + ExitingTraceListener.Install(logger); #endif #if NET472 diff --git a/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs b/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs index 98e04464dff83..9228dfb6a34d9 100644 --- a/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs +++ b/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Immutable; using System.IO; using System.Threading; @@ -44,7 +45,6 @@ public async Task ReadWriteCompleted() public async Task ReadWriteRequest() { var request = new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.VisualBasicCompile, "HashValue", ImmutableArray.Create( @@ -55,7 +55,6 @@ public async Task ReadWriteRequest() Assert.True(memoryStream.Position > 0); memoryStream.Position = 0; var read = await BuildRequest.ReadAsync(memoryStream, default(CancellationToken)); - Assert.Equal(BuildProtocolConstants.ProtocolVersion, read.ProtocolVersion); Assert.Equal(RequestLanguage.VisualBasicCompile, read.Language); Assert.Equal("HashValue", read.CompilerHash); Assert.Equal(2, read.Arguments.Count); diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs index 15d22527d653e..0dbd1b2e14379 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs @@ -70,7 +70,6 @@ private async Task CreateBuildRequest(string sourceText, TimeSpan? builder.Add(new BuildRequest.Argument(BuildProtocolConstants.ArgumentId.CommandLineArgument, argumentIndex: 0, value: file.Path)); return new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, BuildProtocolConstants.GetCommitHash(), builder.ToImmutable()); @@ -182,19 +181,11 @@ public async Task RejectEmptyTempPath() Assert.Equal(ResponseType.Rejected, response.Type); } - [Fact] - public async Task IncorrectProtocolReturnsMismatchedVersionResponse() - { - using var serverData = await ServerUtil.CreateServer(Logger); - var buildResponse = await serverData.SendAsync(new BuildRequest(1, RequestLanguage.CSharpCompile, "abc", new List { })); - Assert.Equal(BuildResponse.ResponseType.MismatchedVersion, buildResponse.Type); - } - [Fact] public async Task IncorrectServerHashReturnsIncorrectHashResponse() { using var serverData = await ServerUtil.CreateServer(Logger); - var buildResponse = await serverData.SendAsync(new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, "abc", new List { })); + var buildResponse = await serverData.SendAsync(new BuildRequest(RequestLanguage.CSharpCompile, "abc", new List { })); Assert.Equal(BuildResponse.ResponseType.IncorrectHash, buildResponse.Type); } diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs index 5e056293beba8..5822aa4d989e6 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs @@ -1495,7 +1495,7 @@ public async Task MissingCompilerAssembly_CompilerServerHost() }); using var serverData = await ServerUtil.CreateServer(_logger, compilerServerHost: host); - var request = new BuildRequest(1, RequestLanguage.CSharpCompile, string.Empty, new BuildRequest.Argument[0]); + var request = new BuildRequest(RequestLanguage.CSharpCompile, string.Empty, new BuildRequest.Argument[0]); var compileTask = serverData.SendAsync(request); var response = await compileTask; Assert.Equal(BuildResponse.ResponseType.Completed, response.Type); diff --git a/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs b/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs index c2dccbddb76ac..3969e10dec9ff 100644 --- a/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs +++ b/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs @@ -24,13 +24,11 @@ namespace Microsoft.CodeAnalysis.CompilerServer.UnitTests internal static class ProtocolUtil { internal static readonly BuildRequest EmptyCSharpBuildRequest = new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, BuildProtocolConstants.GetCommitHash(), ImmutableArray.Empty); internal static readonly BuildRequest EmptyBasicBuildRequest = new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.VisualBasicCompile, BuildProtocolConstants.GetCommitHash(), ImmutableArray.Empty); @@ -158,8 +156,8 @@ internal static BuildClient CreateBuildClient( TextWriter textWriter = null, int? timeoutOverride = null) { - compileFunc = compileFunc ?? GetCompileFunc(language); - textWriter = textWriter ?? new StringWriter(); + compileFunc ??= GetCompileFunc(language); + textWriter ??= new StringWriter(); return new BuildClient(language, compileFunc, logger, timeoutOverride: timeoutOverride); } diff --git a/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs b/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs index 06f7dfd63cc85..c8297f1cbf0c4 100644 --- a/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs +++ b/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs @@ -21,7 +21,7 @@ internal TestableCompilerServerHost(Func arguments, RequestLanguage language, CompileFunc compileFunc, ICompilerServerLogger logger) + internal static int Run(IEnumerable arguments, RequestLanguage language, CompileFunc compileFunc, ICompilerServerLogger logger, Guid? requestId = null) { var sdkDir = GetSystemSdkDirectory(); if (RuntimeHostInfo.IsCoreClrRuntime) @@ -93,7 +93,7 @@ internal static int Run(IEnumerable arguments, RequestLanguage language, var tempDir = BuildServerConnection.GetTempPath(workingDir); var buildPaths = new BuildPaths(clientDir: clientDir, workingDir: workingDir, sdkDir: sdkDir, tempDir: tempDir); var originalArguments = GetCommandLineArgs(arguments); - return client.RunCompilation(originalArguments, buildPaths).ExitCode; + return client.RunCompilation(originalArguments, buildPaths, requestId: requestId).ExitCode; } @@ -102,7 +102,7 @@ internal static int Run(IEnumerable arguments, RequestLanguage language, /// to the console. If the compiler server fails, run the fallback /// compiler. /// - internal RunCompilationResult RunCompilation(IEnumerable originalArguments, BuildPaths buildPaths, TextWriter textWriter = null, string pipeName = null) + internal RunCompilationResult RunCompilation(IEnumerable originalArguments, BuildPaths buildPaths, TextWriter textWriter = null, string pipeName = null, Guid? requestId = null) { textWriter = textWriter ?? Console.Out; @@ -132,7 +132,7 @@ internal RunCompilationResult RunCompilation(IEnumerable originalArgumen { pipeName = pipeName ?? GetPipeName(buildPaths); var libDirectory = Environment.GetEnvironmentVariable("LIB"); - var serverResult = RunServerCompilation(textWriter, parsedArgs, buildPaths, libDirectory, pipeName, keepAliveOpt); + var serverResult = RunServerCompilation(textWriter, parsedArgs, buildPaths, libDirectory, pipeName, keepAliveOpt, requestId); if (serverResult.HasValue) { Debug.Assert(serverResult.Value.RanOnServer); @@ -209,7 +209,7 @@ private int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextW /// Runs the provided compilation on the server. If the compilation cannot be completed on the server then null /// will be returned. /// - private RunCompilationResult? RunServerCompilation(TextWriter textWriter, List arguments, BuildPaths buildPaths, string libDirectory, string sessionName, string keepAlive) + private RunCompilationResult? RunServerCompilation(TextWriter textWriter, List arguments, BuildPaths buildPaths, string libDirectory, string pipeName, string keepAlive, Guid? requestId) { BuildResponse buildResponse; @@ -220,13 +220,25 @@ private int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextW try { - var buildResponseTask = RunServerCompilationAsync( + var alt = new BuildPathsAlt( + buildPaths.ClientDirectory, + buildPaths.WorkingDirectory, + buildPaths.SdkDirectory, + buildPaths.TempDirectory); + + var buildResponseTask = BuildServerConnection.RunServerCompilationCoreAsync( + requestId ?? Guid.NewGuid(), + _language, arguments, - buildPaths, - sessionName, + alt, + pipeName, keepAlive, libDirectory, + _timeoutOverride, + _createServerFunc, + _logger, CancellationToken.None); + buildResponse = buildResponseTask.Result; Debug.Assert(buildResponse != null); @@ -266,48 +278,6 @@ private int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextW } } - private Task RunServerCompilationAsync( - List arguments, - BuildPaths buildPaths, - string sessionKey, - string keepAlive, - string libDirectory, - CancellationToken cancellationToken) - { - return RunServerCompilationCoreAsync(_language, arguments, buildPaths, sessionKey, keepAlive, libDirectory, _timeoutOverride, _createServerFunc, _logger, cancellationToken); - } - - private static Task RunServerCompilationCoreAsync( - RequestLanguage language, - List arguments, - BuildPaths buildPaths, - string pipeName, - string keepAlive, - string libEnvVariable, - int? timeoutOverride, - CreateServerFunc createServerFunc, - ICompilerServerLogger logger, - CancellationToken cancellationToken) - { - var alt = new BuildPathsAlt( - buildPaths.ClientDirectory, - buildPaths.WorkingDirectory, - buildPaths.SdkDirectory, - buildPaths.TempDirectory); - - return BuildServerConnection.RunServerCompilationCoreAsync( - language, - arguments, - alt, - pipeName, - keepAlive, - libEnvVariable, - timeoutOverride, - createServerFunc, - logger, - cancellationToken); - } - /// /// Given the full path to the directory containing the compiler exes, /// retrieves the name of the pipe for client/server communication on diff --git a/src/Compilers/Shared/BuildServerConnection.cs b/src/Compilers/Shared/BuildServerConnection.cs index 043ae5735a0cf..17b78a3ea99cf 100644 --- a/src/Compilers/Shared/BuildServerConnection.cs +++ b/src/Compilers/Shared/BuildServerConnection.cs @@ -78,6 +78,7 @@ internal sealed class BuildServerConnection internal static bool IsCompilerServerSupported => GetPipeNameForPath("") is object; public static Task RunServerCompilationAsync( + Guid requestId, RequestLanguage language, string? sharedCompilationId, List arguments, @@ -90,6 +91,7 @@ public static Task RunServerCompilationAsync( var pipeNameOpt = sharedCompilationId ?? GetPipeNameForPath(buildPaths.ClientDirectory); return RunServerCompilationCoreAsync( + requestId, language, arguments, buildPaths, @@ -103,12 +105,13 @@ public static Task RunServerCompilationAsync( } internal static async Task RunServerCompilationCoreAsync( + Guid requestId, RequestLanguage language, List arguments, BuildPathsAlt buildPaths, string? pipeName, string? keepAlive, - string? libEnvVariable, + string? libDirectory, int? timeoutOverride, CreateServerFunc createServerFunc, ICompilerServerLogger logger, @@ -149,8 +152,9 @@ internal static async Task RunServerCompilationCoreAsync( workingDirectory: buildPaths.WorkingDirectory, tempDirectory: buildPaths.TempDirectory, compilerHash: BuildProtocolConstants.GetCommitHash() ?? "", + requestId: requestId, keepAlive: keepAlive, - libDirectory: libEnvVariable); + libDirectory: libDirectory); return await TryCompileAsync(pipe, request, logger, cancellationToken).ConfigureAwait(false); } @@ -256,26 +260,26 @@ private static async Task TryCompileAsync( // Write the request try { - logger.Log("Begin writing request"); + logger.Log($"Begin writing request for {request.RequestId}"); await request.WriteAsync(pipeStream, cancellationToken).ConfigureAwait(false); - logger.Log("End writing request"); + logger.Log($"End writing request for {request.RequestId}"); } catch (Exception e) { - logger.LogException(e, "Error writing build request."); + logger.LogException(e, $"Error writing build request for {request.RequestId}"); return new RejectedBuildResponse($"Error writing build request: {e.Message}"); } // Wait for the compilation and a monitor to detect if the server disconnects var serverCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - logger.Log("Begin reading response"); + logger.Log($"Begin reading response for {request.RequestId}"); var responseTask = BuildResponse.ReadAsync(pipeStream, serverCts.Token); - var monitorTask = MonitorDisconnectAsync(pipeStream, "client", logger, serverCts.Token); + var monitorTask = MonitorDisconnectAsync(pipeStream, request.RequestId, logger, serverCts.Token); await Task.WhenAny(responseTask, monitorTask).ConfigureAwait(false); - logger.Log("End reading response"); + logger.Log($"End reading response for {request.RequestId}"); if (responseTask.IsCompleted) { @@ -286,13 +290,13 @@ private static async Task TryCompileAsync( } catch (Exception e) { - logger.LogException(e, "Error reading response"); + logger.LogException(e, $"Reading response for {request.RequestId}"); response = new RejectedBuildResponse($"Error reading response: {e.Message}"); } } else { - logger.LogError("Client disconnect"); + logger.LogError($"Client disconnect for {request.RequestId}"); response = new RejectedBuildResponse($"Client disconnected"); } @@ -310,7 +314,7 @@ private static async Task TryCompileAsync( /// internal static async Task MonitorDisconnectAsync( PipeStream pipeStream, - string identifier, + Guid requestId, ICompilerServerLogger logger, CancellationToken cancellationToken = default) { @@ -332,7 +336,7 @@ internal static async Task MonitorDisconnectAsync( { // It is okay for this call to fail. Errors will be reflected in the // IsConnected property which will be read on the next iteration of the - logger.LogException(e, $"Error poking pipe {identifier}."); + logger.LogException(e, $"Error poking pipe {requestId}."); } } } diff --git a/src/Compilers/Shared/ExitingTraceListener.cs b/src/Compilers/Shared/ExitingTraceListener.cs index 4c4acc178c93e..946f2baefbd0f 100644 --- a/src/Compilers/Shared/ExitingTraceListener.cs +++ b/src/Compilers/Shared/ExitingTraceListener.cs @@ -18,6 +18,13 @@ namespace Microsoft.CodeAnalysis.CommandLine /// internal sealed class ExitingTraceListener : TraceListener { + internal ICompilerServerLogger Logger { get; } + + internal ExitingTraceListener(ICompilerServerLogger logger) + { + Logger = logger; + } + public override void Write(string message) { Exit(message); @@ -28,13 +35,13 @@ public override void WriteLine(string message) Exit(message); } - internal static void Install() + internal static void Install(ICompilerServerLogger logger) { Trace.Listeners.Clear(); - Trace.Listeners.Add(new ExitingTraceListener()); + Trace.Listeners.Add(new ExitingTraceListener(logger)); } - private static void Exit(string originalMessage) + private void Exit(string originalMessage) { var builder = new StringBuilder(); builder.AppendLine($"Debug.Assert failed with message: {originalMessage}"); @@ -43,21 +50,12 @@ private static void Exit(string originalMessage) builder.AppendLine(stackTrace.ToString()); var message = builder.ToString(); - var logFullName = GetLogFileFullName(); - File.WriteAllText(logFullName, message); + Logger.Log(message); - Console.WriteLine(message); - Console.WriteLine($"Log at: {logFullName}"); - - Environment.Exit(1); - } - - private static string GetLogFileFullName() - { - var assembly = typeof(ExitingTraceListener).Assembly; - var name = $"{Path.GetFileName(assembly.Location)}.tracelog"; - var path = Path.GetDirectoryName(assembly.Location); - return Path.Combine(path, name); + // Use FailFast so that the process fails rudely and goes through + // windows error reporting (on Windows at least). This will allow our + // CI environment to capture crash dumps for future investigation + Environment.FailFast(message); } } } diff --git a/src/Compilers/Test/Core/Assert/ClrOnlyFactAttribute.cs b/src/Compilers/Test/Core/Assert/ClrOnlyFactAttribute.cs index 8badc0a26e636..45b38c08910d1 100644 --- a/src/Compilers/Test/Core/Assert/ClrOnlyFactAttribute.cs +++ b/src/Compilers/Test/Core/Assert/ClrOnlyFactAttribute.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 Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; @@ -79,15 +77,4 @@ private static string GetSkipReason(ClrOnlyReason reason) } } } - - public sealed class MonoOnlyFactAttribute : FactAttribute - { - public MonoOnlyFactAttribute(string reason) - { - if (!MonoHelpers.IsRunningOnMono()) - { - Skip = reason; - } - } - } } diff --git a/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs b/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs index e77af8860caf8..283c3bd4cefe8 100644 --- a/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs +++ b/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs @@ -222,9 +222,21 @@ public override bool ShouldSkip public class IsEnglishLocal : ExecutionCondition { - public override bool ShouldSkip => - !CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase) || - !CultureInfo.CurrentCulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase); + public override bool ShouldSkip + { + get + { + // WSL environments can have this value as empty string + if (string.IsNullOrEmpty(CultureInfo.CurrentCulture.Name)) + { + return false; + } + + return + !CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase) || + !CultureInfo.CurrentCulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase); + } + } public override string SkipReason => "Current culture is not en"; } diff --git a/src/Compilers/Test/Core/Compilation/CompilationDifference.cs b/src/Compilers/Test/Core/Compilation/CompilationDifference.cs index 9afdbbf5c5442..cf8e61bad5444 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationDifference.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationDifference.cs @@ -28,22 +28,19 @@ public sealed class CompilationDifference public readonly ImmutableArray PdbDelta; internal readonly CompilationTestData TestData; public readonly EmitDifferenceResult EmitResult; - public readonly ImmutableArray UpdatedMethods; internal CompilationDifference( ImmutableArray metadata, ImmutableArray il, ImmutableArray pdb, CompilationTestData testData, - EmitDifferenceResult result, - ImmutableArray methodHandles) + EmitDifferenceResult result) { MetadataDelta = metadata; ILDelta = il; PdbDelta = pdb; TestData = testData; EmitResult = result; - UpdatedMethods = methodHandles; } public EmitBaseline NextGeneration @@ -150,7 +147,14 @@ public void VerifyUpdatedMethods(params string[] expectedMethodTokens) { AssertEx.Equal( expectedMethodTokens, - UpdatedMethods.Select(methodHandle => $"0x{MetadataTokens.GetToken(methodHandle):X8}")); + EmitResult.UpdatedMethods.Select(methodHandle => $"0x{MetadataTokens.GetToken(methodHandle):X8}")); + } + + public void VerifyUpdatedTypes(params string[] expectedTypeTokens) + { + AssertEx.Equal( + expectedTypeTokens, + EmitResult.UpdatedTypes.Select(typeHandle => $"0x{MetadataTokens.GetToken(typeHandle):X8}")); } } } diff --git a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs index 1f1ddae95c017..b32953150b1a8 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs @@ -72,13 +72,12 @@ internal static ImmutableArray EmitToArray( pdbStream: pdbStream, xmlDocumentationStream: null, win32Resources: null, - useRawWin32Resources: false, manifestResources: manifestResources, options: options, debugEntryPoint: debugEntryPoint, sourceLinkStream: sourceLinkStream, embeddedTexts: embeddedTexts, - pdbOptionsBlobReader: null, + rebuildData: null, testData: testData, cancellationToken: default(CancellationToken)); @@ -146,8 +145,6 @@ internal static CompilationDifference EmitDifference( using var ilStream = new MemoryStream(); using var pdbStream = new MemoryStream(); - var updatedMethods = new List(); - var result = compilation.EmitDifference( baseline, edits, @@ -155,7 +152,6 @@ internal static CompilationDifference EmitDifference( mdStream, ilStream, pdbStream, - updatedMethods, testData, CancellationToken.None); @@ -164,8 +160,7 @@ internal static CompilationDifference EmitDifference( ilStream.ToImmutable(), pdbStream.ToImmutable(), testData, - result, - updatedMethods.ToImmutableArray()); + result); } internal static void VerifyAssemblyVersionsAndAliases(this Compilation compilation, params string[] expectedAssembliesAndAliases) diff --git a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs index 71d1f4be45a2d..efa06ebed815c 100644 --- a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs @@ -1060,6 +1060,13 @@ bool isLongLivedCaptureReferenceSyntax(SyntaxNode captureReferenceSyntax) break; } + if (syntax.Parent is CSharp.Syntax.WithExpressionSyntax withExpr + && withExpr.Initializer.Expressions.Any() + && withExpr.Expression == (object)syntax) + { + return true; + } + syntax = applyParenthesizedOrNullSuppressionIfAnyCS(syntax); if (syntax.Parent?.Parent is CSharp.Syntax.UsingStatementSyntax usingStmt && diff --git a/src/Compilers/Test/Core/Compilation/IRuntimeEnvironment.cs b/src/Compilers/Test/Core/Compilation/IRuntimeEnvironment.cs index 3ce98739edf6d..c17c634d67adb 100644 --- a/src/Compilers/Test/Core/Compilation/IRuntimeEnvironment.cs +++ b/src/Compilers/Test/Core/Compilation/IRuntimeEnvironment.cs @@ -257,13 +257,12 @@ EmitOptions emitOptions pdbStream: pdbStream, xmlDocumentationStream: null, win32Resources: null, - useRawWin32Resources: false, manifestResources: manifestResources, options: emitOptions, debugEntryPoint: null, sourceLinkStream: null, embeddedTexts, - pdbOptionsBlobReader: null, + rebuildData: null, testData: testData, cancellationToken: default); } diff --git a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs index 3be5f5a769a20..b23f93dbd36ce 100644 --- a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs @@ -1941,7 +1941,7 @@ public override void VisitDiscardPattern(IDiscardPatternOperation operation) public override void VisitSwitchExpression(ISwitchExpressionOperation operation) { - LogString($"{nameof(ISwitchExpressionOperation)} ({operation.Arms.Length} arms)"); + LogString($"{nameof(ISwitchExpressionOperation)} ({operation.Arms.Length} arms, IsExhaustive: {operation.IsExhaustive})"); LogCommonPropertiesAndNewLine(operation); Visit(operation.Value, nameof(operation.Value)); VisitArray(operation.Arms, nameof(operation.Arms), logElementCount: true); diff --git a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs index 6cf90a9e745c0..108e47582030b 100644 --- a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs +++ b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs @@ -1284,6 +1284,8 @@ public override void VisitPropertySubpattern(IPropertySubpatternOperation operat public override void VisitSwitchExpression(ISwitchExpressionOperation operation) { + //force the existence of IsExhaustive + _ = operation.IsExhaustive; Assert.NotNull(operation.Type); Assert.False(operation.ConstantValue.HasValue); Assert.Equal(OperationKind.SwitchExpression, operation.Kind); diff --git a/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs b/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs index 4d7a95af6b0d4..61aa60dcb0238 100644 --- a/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs +++ b/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs @@ -181,6 +181,11 @@ public DiagnosticDescription(Diagnostic d, bool errorCodeOnly, bool includeDefau _startPosition = _location.GetMappedLineSpan().StartLinePosition; } + public DiagnosticDescription WithSquiggledText(string squiggledText) + { + return new DiagnosticDescription(_code, _isWarningAsError, squiggledText, _arguments, _startPosition, _syntaxPredicate, false, _errorCodeType, _defaultSeverityOpt, _effectiveSeverityOpt, _isSuppressed); + } + public DiagnosticDescription WithArguments(params object[] arguments) { return new DiagnosticDescription(_code, _isWarningAsError, _squiggledText, arguments, _startPosition, _syntaxPredicate, false, _errorCodeType, _defaultSeverityOpt, _effectiveSeverityOpt, _isSuppressed); @@ -224,7 +229,10 @@ public DiagnosticDescription WhereSyntax(Func syntaxPredicate) } public object Code => _code; + public string SquiggledText => _squiggledText; public bool HasLocation => _startPosition != null; + public int LocationLine => _startPosition.Value.Line + 1; + public int LocationCharacter => _startPosition.Value.Character + 1; public bool IsWarningAsError => _isWarningAsError; public bool IsSuppressed => _isSuppressed; public DiagnosticSeverity? DefaultSeverity => _defaultSeverityOpt; diff --git a/src/Workspaces/CoreTestUtilities/DotNetCoreSdk.cs b/src/Compilers/Test/Core/DotNetCoreSdk.cs similarity index 95% rename from src/Workspaces/CoreTestUtilities/DotNetCoreSdk.cs rename to src/Compilers/Test/Core/DotNetCoreSdk.cs index b1129005a7fc6..f181f3060b757 100644 --- a/src/Workspaces/CoreTestUtilities/DotNetCoreSdk.cs +++ b/src/Compilers/Test/Core/DotNetCoreSdk.cs @@ -5,9 +5,8 @@ using System; using System.IO; using System.Linq; -using Roslyn.Test.Utilities; -namespace Microsoft.CodeAnalysis.UnitTests +namespace Roslyn.Test.Utilities { public static class DotNetCoreSdk { diff --git a/src/Compilers/Test/Core/FX/ProcessUtilities.cs b/src/Compilers/Test/Core/FX/ProcessUtilities.cs index 6cb97cd3bf1df..f4f14ef13287e 100644 --- a/src/Compilers/Test/Core/FX/ProcessUtilities.cs +++ b/src/Compilers/Test/Core/FX/ProcessUtilities.cs @@ -128,6 +128,7 @@ public static string RunAndGetOutput(string exeFileName, string arguments = null startInfo.CreateNoWindow = true; startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; if (startFolder != null) @@ -141,8 +142,9 @@ public static string RunAndGetOutput(string exeFileName, string arguments = null // redirected stream. Read the output stream first and then wait. Doing otherwise // might cause a deadlock. result = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); process.WaitForExit(); - Assert.True(expectedRetCode == process.ExitCode, $"Unexpected exit code: {process.ExitCode} (expecting {expectedRetCode}). Process output: {result}"); + Assert.True(expectedRetCode == process.ExitCode, $"Unexpected exit code: {process.ExitCode} (expecting {expectedRetCode}). Process output: {result}. Process error: {error}"); } return result; diff --git a/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs b/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs index 298889f6004fa..51f5c91a89bad 100644 --- a/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs +++ b/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs @@ -7,18 +7,17 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; +using System.IO.Compression; using System.Linq; -using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; -using Microsoft.CodeAnalysis; -using Roslyn.Utilities; using Microsoft.CodeAnalysis.Debugging; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using System.IO; -using System.IO.Compression; +using Roslyn.Utilities; namespace Roslyn.Test.Utilities { @@ -110,6 +109,11 @@ public static Guid GetModuleVersionId(this MetadataReader reader) return reader.GetGuid(reader.GetModuleDefinition().Mvid); } + public static StringHandle[] GetUpdatedTypeDefNames(this MetadataReader reader, EmitDifferenceResult emitResult) + { + return emitResult.UpdatedTypes.Select(handle => reader.GetTypeDefinition(handle).Name).ToArray(); + } + public static StringHandle[] GetAssemblyRefNames(this MetadataReader reader) { return reader.AssemblyReferences.Select(handle => reader.GetAssemblyReference(handle).Name).ToArray(); diff --git a/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj b/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj index f2cc6908b1599..9bd2d660f996b 100644 --- a/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj +++ b/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj @@ -54,8 +54,10 @@ + + diff --git a/src/Compilers/Test/Core/ObjectReference.cs b/src/Compilers/Test/Core/ObjectReference.cs index a765991b3abfc..a93ad8393b518 100644 --- a/src/Compilers/Test/Core/ObjectReference.cs +++ b/src/Compilers/Test/Core/ObjectReference.cs @@ -161,12 +161,24 @@ public U UseReference(Func function) /// caller must not "leak" the object out of the given action for any lifetime assertions to be safe. /// [MethodImpl(MethodImplOptions.NoInlining)] - public ObjectReference GetObjectReference(Func function) where U : class + public ObjectReference GetObjectReference(Func function) where TResult : class { var newValue = function(GetReferenceWithChecks()); return ObjectReference.Create(newValue); } + /// + /// Provides the underlying strong reference to the given function, lets a function extract some value, and then returns a new ObjectReference. + /// This method is marked not be inlined, to ensure that no temporaries are left on the stack that might still root the strong reference. The + /// caller must not "leak" the object out of the given action for any lifetime assertions to be safe. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public ObjectReference GetObjectReference(Func function, TArg argument) where TResult : class + { + var newValue = function(GetReferenceWithChecks(), argument); + return ObjectReference.Create(newValue); + } + /// /// Fetches the object strongly being held from this. Because the value returned might be cached in a local temporary from /// the caller of this function, no further calls to or may be called diff --git a/src/Compilers/Test/Core/PDB/DeterministicBuildCompilationTestHelpers.cs b/src/Compilers/Test/Core/PDB/DeterministicBuildCompilationTestHelpers.cs index 26d9857e0dc4f..aaaa72c5d36f8 100644 --- a/src/Compilers/Test/Core/PDB/DeterministicBuildCompilationTestHelpers.cs +++ b/src/Compilers/Test/Core/PDB/DeterministicBuildCompilationTestHelpers.cs @@ -46,7 +46,7 @@ public static IEnumerable GetEmitOptions() internal static void AssertCommonOptions(EmitOptions emitOptions, CompilationOptions compilationOptions, Compilation compilation, ImmutableDictionary pdbOptions) { - pdbOptions.VerifyPdbOption("version", MetadataWriter.CompilationOptionsSchemaVersion); + pdbOptions.VerifyPdbOption("version", 2); pdbOptions.VerifyPdbOption("fallback-encoding", emitOptions.FallbackSourceFileEncoding, toString: v => v.WebName); pdbOptions.VerifyPdbOption("default-encoding", emitOptions.DefaultSourceFileEncoding, toString: v => v.WebName); diff --git a/src/Compilers/Test/Core/Traits/CompilerFeature.cs b/src/Compilers/Test/Core/Traits/CompilerFeature.cs index be07b3f523b2c..7a58820fda4fb 100644 --- a/src/Compilers/Test/Core/Traits/CompilerFeature.cs +++ b/src/Compilers/Test/Core/Traits/CompilerFeature.cs @@ -42,5 +42,6 @@ public enum CompilerFeature AnonymousFunctions, ModuleInitializers, FunctionPointers, + RecordStructs, } } diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index b21f3558c22ba..18553a1efba64 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -110,6 +110,7 @@ public static class Features public const string CodeActionsInsertBraces = "CodeActions.InsertBraces"; public const string CodeActionsInsertMissingTokens = "CodeActions.InsertMissingTokens"; public const string CodeActionsIntroduceLocalForExpression = "CodeActions.IntroduceLocalForExpression"; + public const string CodeActionsIntroduceParameter = "CodeActions.IntroduceParameter"; public const string CodeActionsIntroduceUsingStatement = "CodeActions.IntroduceUsingStatement"; public const string CodeActionsIntroduceVariable = "CodeActions.IntroduceVariable"; public const string CodeActionsInvertConditional = "CodeActions.InvertConditional"; @@ -245,6 +246,7 @@ public static class Features public const string GoToBase = nameof(GoToBase); public const string GoToDefinition = nameof(GoToDefinition); public const string GoToImplementation = nameof(GoToImplementation); + public const string InheritanceMargin = nameof(InheritanceMargin); public const string InlineHints = nameof(InlineHints); public const string Interactive = nameof(Interactive); public const string InteractiveHost = nameof(InteractiveHost); diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index a3e11534b54c8..1c115f3ff7428 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -570,6 +570,18 @@ public NativeIntegerAttribute(bool[] flags) } }"; + protected const string UnmanagedCallersOnlyAttributeDefinition = +@"namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public sealed class UnmanagedCallersOnlyAttribute : Attribute + { + public UnmanagedCallersOnlyAttribute() { } + public Type[] CallConvs; + public string EntryPoint; + } +}"; + protected static CSharpCompilationOptions WithNullableEnable(CSharpCompilationOptions options = null) { return WithNullable(options, NullableContextOptions.Enable); @@ -1869,6 +1881,7 @@ protected static void VerifyFlowGraphForTest(CSharpCompilation comp { var tree = compilation.SyntaxTrees[0]; SyntaxNode syntaxNode = GetSyntaxNodeOfTypeForBinding(GetSyntaxNodeList(tree)); + Debug.Assert(syntaxNode is not null, "Did you forget to place /**/ comments in your source?"); VerifyFlowGraph(compilation, syntaxNode, expectedFlowGraph); } diff --git a/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/VisualBasicDataFlowAnalysis.vb b/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/VisualBasicDataFlowAnalysis.vb index eb58278384b91..8306c3a26ba30 100644 --- a/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/VisualBasicDataFlowAnalysis.vb +++ b/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/VisualBasicDataFlowAnalysis.vb @@ -207,7 +207,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic readOutside = readInside writtenOutside = readInside captured = readInside - + capturedInside = readInside + capturedOutside = readInside Else ReadWriteWalker.Analyze( _context.AnalysisInfo, _context.RegionInfo, diff --git a/src/Compilers/VisualBasic/Portable/Binding/ConstantFieldsInProgress.vb b/src/Compilers/VisualBasic/Portable/Binding/ConstantFieldsInProgress.vb index e265718c5a2c8..a0fcaf9b0d120 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/ConstantFieldsInProgress.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/ConstantFieldsInProgress.vb @@ -29,6 +29,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Public Function AnyDependencies() As Boolean + Return _dependencies.Any() + End Function + Friend Sub AddDependency(field As SourceFieldSymbol) _dependencies.Add(field) End Sub @@ -53,6 +57,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic _builder.Add(field) End Sub + Friend Function Any() As Boolean + Return _builder.Count <> 0 + End Function + Friend Sub Freeze() #If DEBUG Then diff --git a/src/Compilers/VisualBasic/Portable/BoundTree/BoundFieldAccess.vb b/src/Compilers/VisualBasic/Portable/BoundTree/BoundFieldAccess.vb index 0d89dc80529cd..cb1bdd345f783 100644 --- a/src/Compilers/VisualBasic/Portable/BoundTree/BoundFieldAccess.vb +++ b/src/Compilers/VisualBasic/Portable/BoundTree/BoundFieldAccess.vb @@ -51,7 +51,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End If #If DEBUG Then - ValidateConstantValue(Me.Type, result) + If constantsInProgress Is Nothing OrElse + constantsInProgress.IsEmpty OrElse + Not constantsInProgress.AnyDependencies() Then + ValidateConstantValue(Me.Type, result) + End If #End If Return result End Get diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index 041a960da5fcb..d445d7b3c6cf7 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -1675,8 +1675,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - Friend Overrides Sub ReportUnusedImports(filterTree As SyntaxTree, diagnostics As DiagnosticBag, cancellationToken As CancellationToken) - ReportUnusedImports(filterTree, New BindingDiagnosticBag(diagnostics), cancellationToken) + Friend Overrides Sub ReportUnusedImports(diagnostics As DiagnosticBag, cancellationToken As CancellationToken) + ReportUnusedImports(filterTree:=Nothing, New BindingDiagnosticBag(diagnostics), cancellationToken) End Sub Private Overloads Sub ReportUnusedImports(filterTree As SyntaxTree, diagnostics As BindingDiagnosticBag, cancellationToken As CancellationToken) @@ -2373,7 +2373,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic cancellationToken As CancellationToken) As CommonPEModuleBuilder Debug.Assert(Not IsSubmission OrElse HasCodeToEmit() OrElse - (emitOptions = emitOptions.Default AndAlso debugEntryPoint Is Nothing AndAlso sourceLinkStream Is Nothing AndAlso + (emitOptions = EmitOptions.Default AndAlso debugEntryPoint Is Nothing AndAlso sourceLinkStream Is Nothing AndAlso embeddedTexts Is Nothing AndAlso manifestResources Is Nothing AndAlso testData Is Nothing)) ' Get the runtime metadata version from the cor library. If this fails we have no reasonable value to give. @@ -2554,7 +2554,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic metadataStream As Stream, ilStream As Stream, pdbStream As Stream, - updatedMethods As ICollection(Of MethodDefinitionHandle), testData As CompilationTestData, cancellationToken As CancellationToken) As EmitDifferenceResult @@ -2566,7 +2565,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic metadataStream, ilStream, pdbStream, - updatedMethods, testData, cancellationToken) End Function diff --git a/src/Compilers/VisualBasic/Portable/Emit/ArrayTypeSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/ArrayTypeSymbolAdapter.vb index 19ad7863276fa..827d844c941b6 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/ArrayTypeSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/ArrayTypeSymbolAdapter.vb @@ -19,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private Function IArrayTypeReferenceGetElementType(context As EmitContext) As Cci.ITypeReference Implements Cci.IArrayTypeReference.GetElementType Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Dim customModifiers As ImmutableArray(Of CustomModifier) = AdaptedArrayTypeSymbol.CustomModifiers - Dim type = moduleBeingBuilt.Translate(AdaptedArrayTypeSymbol.ElementType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Dim type = moduleBeingBuilt.Translate(AdaptedArrayTypeSymbol.ElementType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) If customModifiers.Length = 0 Then Return type diff --git a/src/Compilers/VisualBasic/Portable/Emit/AttributeDataAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/AttributeDataAdapter.vb index 96766df0bcc57..72e54c1ec2552 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/AttributeDataAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/AttributeDataAdapter.vb @@ -24,7 +24,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' Details: https://github.com/dotnet/roslyn/issues/19394 If reportDiagnostics Then - context.Diagnostics.Add(ERRID.ERR_AttributeMustBeClassNotStruct1, If(context.SyntaxNodeOpt?.GetLocation(), NoLocation.Singleton), Me.AttributeClass) + context.Diagnostics.Add(ERRID.ERR_AttributeMustBeClassNotStruct1, If(context.SyntaxNode?.GetLocation(), NoLocation.Singleton), Me.AttributeClass) End If Return Nothing @@ -32,7 +32,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Return moduleBeingBuilt.Translate(AttributeConstructor, needDeclaration:=False, - syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private Function GetNamedArguments1(context As EmitContext) As ImmutableArray(Of Cci.IMetadataNamedArgument) Implements Cci.ICustomAttribute.GetNamedArguments @@ -53,7 +53,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private Function GetType1(context As EmitContext) As Cci.ITypeReference Implements Cci.ICustomAttribute.GetType Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) - Return moduleBeingBuilt.Translate(AttributeClass, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(AttributeClass, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private ReadOnly Property AllowMultiple1 As Boolean Implements Cci.ICustomAttribute.AllowMultiple @@ -105,7 +105,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Debug.Assert(argument.ValueInternal IsNot Nothing) Dim moduleBeingBuilt = DirectCast(context.Module, PEModuleBuilder) - Dim syntaxNodeOpt = DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode) + Dim syntaxNodeOpt = DirectCast(context.SyntaxNode, VisualBasicSyntaxNode) Dim diagnostics = context.Diagnostics Return New MetadataTypeOf(moduleBeingBuilt.Translate(DirectCast(argument.ValueInternal, TypeSymbol), syntaxNodeOpt, diagnostics), moduleBeingBuilt.Translate(DirectCast(argument.TypeInternal, TypeSymbol), syntaxNodeOpt, diagnostics)) @@ -113,7 +113,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private Function CreateMetadataConstant(type As ITypeSymbolInternal, value As Object, context As EmitContext) As MetadataConstant Dim moduleBeingBuilt = DirectCast(context.Module, PEModuleBuilder) - Return moduleBeingBuilt.CreateConstant(DirectCast(type, TypeSymbol), value, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.CreateConstant(DirectCast(type, TypeSymbol), value, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function @@ -129,7 +129,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End If Dim moduleBeingBuilt = DirectCast(context.Module, PEModuleBuilder) - Return New MetadataNamedArgument(sym, moduleBeingBuilt.Translate(type, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics), value) + Return New MetadataNamedArgument(sym, moduleBeingBuilt.Translate(type, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics), value) End Function Private Function LookupName(name As String) As Symbol diff --git a/src/Compilers/VisualBasic/Portable/Emit/CustomModifierAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/CustomModifierAdapter.vb index b10252bd3fc5f..fa7f2e2dd9a1b 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/CustomModifierAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/CustomModifierAdapter.vb @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Property Private Function CciGetModifier(context As EmitContext) As Cci.ITypeReference Implements Cci.ICustomModifier.GetModifier - Return DirectCast(context.Module, PEModuleBuilder).Translate(Me.ModifierSymbol, DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), context.Diagnostics) + Return DirectCast(context.Module, PEModuleBuilder).Translate(Me.ModifierSymbol, DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics) End Function End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb index f15985b0d51a2..659acb689b99e 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb @@ -9,6 +9,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeGen Imports Microsoft.CodeAnalysis.Emit +Imports Microsoft.CodeAnalysis.PooledObjects Namespace Microsoft.CodeAnalysis.VisualBasic.Emit @@ -22,7 +23,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit metadataStream As Stream, ilStream As Stream, pdbStream As Stream, - updatedMethods As ICollection(Of MethodDefinitionHandle), testData As CompilationTestData, cancellationToken As CancellationToken) As EmitDifferenceResult @@ -34,6 +34,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim serializationProperties = compilation.ConstructModuleSerializationProperties(emitOpts, runtimeMDVersion, baseline.ModuleVersionId) Dim manifestResources = SpecializedCollections.EmptyEnumerable(Of ResourceDescription)() + Dim updatedMethods = ArrayBuilder(Of MethodDefinitionHandle).GetInstance() + Dim updatedTypes = ArrayBuilder(Of TypeDefinitionHandle).GetInstance() + Dim moduleBeingBuilt As PEDeltaAssemblyBuilder Try moduleBeingBuilt = New PEDeltaAssemblyBuilder( @@ -48,7 +51,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Catch e As NotSupportedException ' TODO: https://github.com/dotnet/roslyn/issues/9004 diagnostics.Add(ERRID.ERR_ModuleEmitFailure, NoLocation.Singleton, compilation.AssemblyName, e.Message) - Return New EmitDifferenceResult(success:=False, diagnostics:=diagnostics.ToReadOnlyAndFree(), baseline:=Nothing) + Return New EmitDifferenceResult(success:=False, diagnostics:=diagnostics.ToReadOnlyAndFree(), baseline:=Nothing, updatedMethods:=updatedMethods.ToImmutableAndFree(), updatedTypes:=updatedTypes.ToImmutableAndFree()) End Try If testData IsNot Nothing Then @@ -81,6 +84,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit ilStream, pdbStream, updatedMethods, + updatedTypes, diagnostics, testData?.SymWriterFactory, emitOpts.PdbFilePath, @@ -90,7 +94,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Return New EmitDifferenceResult( success:=newBaseline IsNot Nothing, diagnostics:=diagnostics.ToReadOnlyAndFree(), - baseline:=newBaseline) + baseline:=newBaseline, + updatedMethods:=updatedMethods.ToImmutableAndFree(), + updatedTypes:=updatedTypes.ToImmutableAndFree()) End Function Friend Function MapToCompilation( diff --git a/src/Compilers/VisualBasic/Portable/Emit/EventSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/EventSymbolAdapter.vb index 7c3f87d6e63bf..b4c79f0ada65f 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EventSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EventSymbolAdapter.vb @@ -81,7 +81,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Property Private Overloads Function IEventDefinitionGetType(context As EmitContext) As Cci.ITypeReference Implements Cci.IEventDefinition.GetType - Return (DirectCast(context.Module, PEModuleBuilder)).Translate(AdaptedEventSymbol.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return (DirectCast(context.Module, PEModuleBuilder)).Translate(AdaptedEventSymbol.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private ReadOnly Property IEventDefinitionContainingTypeDefinition As Cci.ITypeDefinition Implements Cci.IEventDefinition.ContainingTypeDefinition diff --git a/src/Compilers/VisualBasic/Portable/Emit/FieldSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/FieldSymbolAdapter.vb index 98bee9971dba5..5f4b6c1163269 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/FieldSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/FieldSymbolAdapter.vb @@ -24,7 +24,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private Function IFieldReferenceGetType(context As EmitContext) As ITypeReference Implements IFieldReference.GetType Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Dim customModifiers = AdaptedFieldSymbol.CustomModifiers - Dim type = moduleBeingBuilt.Translate(AdaptedFieldSymbol.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Dim type = moduleBeingBuilt.Translate(AdaptedFieldSymbol.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) If customModifiers.Length = 0 Then Return type Else @@ -62,7 +62,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Debug.Assert(Me.IsDefinitionOrDistinct()) - Return moduleBeingBuilt.Translate(AdaptedFieldSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=AdaptedFieldSymbol.IsDefinition) + Return moduleBeingBuilt.Translate(AdaptedFieldSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=AdaptedFieldSymbol.IsDefinition) End Function Friend NotOverridable Overrides Sub IReferenceDispatch(visitor As MetadataVisitor) ' Implements IReference.Dispatch @@ -106,7 +106,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' do not return a compile time value for const fields of types DateTime or Decimal because they ' are only const from a VB point of view If AdaptedFieldSymbol.IsMetadataConstant Then - Return DirectCast(context.Module, PEModuleBuilder).CreateConstant(AdaptedFieldSymbol.Type, AdaptedFieldSymbol.ConstantValue, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return DirectCast(context.Module, PEModuleBuilder).CreateConstant(AdaptedFieldSymbol.Type, AdaptedFieldSymbol.ConstantValue, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End If Return Nothing diff --git a/src/Compilers/VisualBasic/Portable/Emit/GenericMethodInstanceReference.vb b/src/Compilers/VisualBasic/Portable/Emit/GenericMethodInstanceReference.vb index dfb46eeaa2a1d..9b53b0ab0d9d2 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/GenericMethodInstanceReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/GenericMethodInstanceReference.vb @@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Return From arg In m_UnderlyingMethod.TypeArguments - Select moduleBeingBuilt.Translate(arg, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Select moduleBeingBuilt.Translate(arg, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private Function IGenericMethodInstanceReferenceGetGenericMethod(context As EmitContext) As Cci.IMethodReference Implements Cci.IGenericMethodInstanceReference.GetGenericMethod @@ -35,7 +35,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit ' NoPia method might come through here. Return DirectCast(context.Module, PEModuleBuilder).Translate( m_UnderlyingMethod.OriginalDefinition, - DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics, needDeclaration:=True) End Function diff --git a/src/Compilers/VisualBasic/Portable/Emit/GenericNestedTypeInstanceReference.vb b/src/Compilers/VisualBasic/Portable/Emit/GenericNestedTypeInstanceReference.vb index dda36a6a00b1e..ebbb7008ec4ad 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/GenericNestedTypeInstanceReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/GenericNestedTypeInstanceReference.vb @@ -20,7 +20,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Sub Private Function ITypeMemberReferenceGetContainingType(context As EmitContext) As Cci.ITypeReference Implements Cci.ITypeMemberReference.GetContainingType - Return (DirectCast(context.Module, PEModuleBuilder)).Translate(m_UnderlyingNamedType.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return (DirectCast(context.Module, PEModuleBuilder)).Translate(m_UnderlyingNamedType.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Public Overrides ReadOnly Property AsGenericTypeInstanceReference As Cci.IGenericTypeInstanceReference diff --git a/src/Compilers/VisualBasic/Portable/Emit/GenericTypeInstanceReference.vb b/src/Compilers/VisualBasic/Portable/Emit/GenericTypeInstanceReference.vb index 5f00a736ab6a4..49f44e9a1f4d0 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/GenericTypeInstanceReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/GenericTypeInstanceReference.vb @@ -41,7 +41,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim builder = ArrayBuilder(Of ITypeReference).GetInstance() For Each t In m_UnderlyingNamedType.TypeArgumentsNoUseSiteDiagnostics - builder.Add(moduleBeingBuilt.Translate(t, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics)) + builder.Add(moduleBeingBuilt.Translate(t, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics)) Next Return builder.ToImmutableAndFree @@ -50,7 +50,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function IGenericTypeInstanceReferenceGetGenericType(context As EmitContext) As Cci.INamedTypeReference Implements Cci.IGenericTypeInstanceReference.GetGenericType Debug.Assert(m_UnderlyingNamedType.OriginalDefinition Is m_UnderlyingNamedType.OriginalDefinition.OriginalDefinition) Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) - Return moduleBeingBuilt.Translate(m_UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + Return moduleBeingBuilt.Translate(m_UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=True) End Function End Class diff --git a/src/Compilers/VisualBasic/Portable/Emit/MethodReference.vb b/src/Compilers/VisualBasic/Portable/Emit/MethodReference.vb index 58fc4ecbdcdc4..6dfa4a8ea7d78 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/MethodReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/MethodReference.vb @@ -91,7 +91,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Dim returnType As TypeSymbol = m_UnderlyingMethod.ReturnType - Return moduleBeingBuilt.Translate(returnType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(returnType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Public Overridable ReadOnly Property AsGenericMethodInstanceReference As Cci.IGenericMethodInstanceReference Implements Cci.IMethodReference.AsGenericMethodInstanceReference diff --git a/src/Compilers/VisualBasic/Portable/Emit/MethodSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/MethodSymbolAdapter.vb index 860fb6cc0b9f4..638c169c7f3cd 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/MethodSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/MethodSymbolAdapter.vb @@ -57,14 +57,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Debug.Assert(Me.IsDefinitionOrDistinct()) If Not AdaptedMethodSymbol.IsDefinition Then - Return moduleBeingBuilt.Translate(AdaptedMethodSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(AdaptedMethodSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) ElseIf TypeOf AdaptedMethodSymbol Is SynthesizedGlobalMethodBase Then - Dim privateImplClass = moduleBeingBuilt.GetPrivateImplClass(syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Dim privateImplClass = moduleBeingBuilt.GetPrivateImplClass(syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) Debug.Assert(privateImplClass IsNot Nothing) Return privateImplClass End If - Return moduleBeingBuilt.Translate(AdaptedMethodSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=True) + Return moduleBeingBuilt.Translate(AdaptedMethodSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=True) End Function Friend NotOverridable Overrides Sub IReferenceDispatch(visitor As Cci.MetadataVisitor) ' Implements IReference.Dispatch @@ -197,7 +197,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Dim returnType As TypeSymbol = AdaptedMethodSymbol.ReturnType - Return moduleBeingBuilt.Translate(returnType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(returnType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private Function IGenericMethodInstanceReferenceGetGenericArguments(context As EmitContext) As IEnumerable(Of Cci.ITypeReference) Implements Cci.IGenericMethodInstanceReference.GetGenericArguments @@ -206,7 +206,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Debug.Assert((DirectCast(Me, Cci.IMethodReference)).AsGenericMethodInstanceReference IsNot Nothing) Return From arg In AdaptedMethodSymbol.TypeArguments - Select moduleBeingBuilt.Translate(arg, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Select moduleBeingBuilt.Translate(arg, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private Function IGenericMethodInstanceReferenceGetGenericMethod(context As EmitContext) As Cci.IMethodReference Implements Cci.IGenericMethodInstanceReference.GetGenericMethod @@ -218,7 +218,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' NoPia method might come through here. Return DirectCast(context.Module, PEModuleBuilder).Translate( AdaptedMethodSymbol.OriginalDefinition, - DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics, needDeclaration:=True) End If diff --git a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb index 06d9c87af5f8e..7e5181c4d4a07 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb @@ -234,7 +234,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End If If baseType IsNot Nothing Then - Return moduleBeingBuilt.Translate(baseType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(baseType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End If Return Nothing @@ -305,14 +305,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' The stub will implement the implemented method in metadata. If MethodSignatureComparer.CustomModifiersAndParametersAndReturnTypeSignatureComparer.Equals(implementingMethod, implemented) Then explicitImplements.Add(New Cci.MethodImplementation(implementingMethod.GetCciAdapter(), - moduleBeingBuilt.TranslateOverriddenMethodReference(implemented, DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), context.Diagnostics))) + moduleBeingBuilt.TranslateOverriddenMethodReference(implemented, DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics))) End If Next ' Explicit overrides needed in some overriding situations. If OverrideHidingHelper.RequiresExplicitOverride(implementingMethod) Then explicitImplements.Add(New Cci.MethodImplementation(implementingMethod.GetCciAdapter(), - moduleBeingBuilt.TranslateOverriddenMethodReference(implementingMethod.OverriddenMethod, DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), context.Diagnostics))) + moduleBeingBuilt.TranslateOverriddenMethodReference(implementingMethod.OverriddenMethod, DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics))) End If If sourceNamedType IsNot Nothing Then @@ -320,7 +320,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols If comMethod IsNot Nothing Then explicitImplements.Add(New Cci.MethodImplementation(implementingMethod.GetCciAdapter(), - moduleBeingBuilt.TranslateOverriddenMethodReference(comMethod, DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), context.Diagnostics))) + moduleBeingBuilt.TranslateOverriddenMethodReference(comMethod, DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics))) End If End If End Sub @@ -415,7 +415,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) For Each [interface] In AdaptedNamedTypeSymbol.GetInterfacesToEmit() Dim typeRef = moduleBeingBuilt.Translate([interface], - syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, fromImplements:=True) @@ -792,7 +792,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Debug.Assert((DirectCast(Me, ITypeReference)).AsNestedTypeReference IsNot Nothing) Debug.Assert(Me.IsDefinitionOrDistinct()) - Return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=AdaptedNamedTypeSymbol.IsDefinition) + Return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=AdaptedNamedTypeSymbol.IsDefinition) End Function Private ReadOnly Property ITypeDefinitionMemberContainingTypeDefinition As ITypeDefinition Implements ITypeDefinitionMember.ContainingTypeDefinition @@ -825,7 +825,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Dim builder = ArrayBuilder(Of ITypeReference).GetInstance() Dim arguments = AdaptedNamedTypeSymbol.TypeArgumentsNoUseSiteDiagnostics For i As Integer = 0 To arguments.Length - 1 - Dim arg = moduleBeingBuilt.Translate(arguments(i), syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Dim arg = moduleBeingBuilt.Translate(arguments(i), syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) If hasModifiers Then Dim modifiers = AdaptedNamedTypeSymbol.GetTypeArgumentCustomModifiers(i) @@ -848,7 +848,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private ReadOnly Property GenericTypeImpl(context As EmitContext) As INamedTypeReference Get Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) - Return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + Return moduleBeingBuilt.Translate(AdaptedNamedTypeSymbol.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=True) End Get End Property diff --git a/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb b/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb index 04ebf07d1568b..a4cb610621fc7 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb @@ -109,7 +109,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit.NoPia For Each [interface] In UnderlyingNamedType.AdaptedNamedTypeSymbol.GetInterfacesToEmit() Dim typeRef = moduleBeingBuilt.Translate([interface], - DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics) Yield [interface].GetTypeRefWithAttributes(UnderlyingNamedType.AdaptedNamedTypeSymbol.DeclaringCompilation, typeRef) diff --git a/src/Compilers/VisualBasic/Portable/Emit/ParameterSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/ParameterSymbolAdapter.vb index 497b10e2595dc..e786748a7029d 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/ParameterSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/ParameterSymbolAdapter.vb @@ -39,7 +39,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private Function IParameterTypeInformationGetType(context As EmitContext) As ITypeReference Implements IParameterTypeInformation.GetType Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Dim paramType As TypeSymbol = AdaptedParameterSymbol.Type - Return moduleBeingBuilt.Translate(paramType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(paramType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private ReadOnly Property IParameterListEntryIndex As UShort Implements IParameterListEntry.Index @@ -55,7 +55,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Friend Function GetMetadataConstantValue(context As EmitContext) As MetadataConstant If AdaptedParameterSymbol.HasMetadataConstantValue Then - Return DirectCast(context.Module, PEModuleBuilder).CreateConstant(AdaptedParameterSymbol.Type, AdaptedParameterSymbol.ExplicitDefaultConstantValue.Value, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return DirectCast(context.Module, PEModuleBuilder).CreateConstant(AdaptedParameterSymbol.Type, AdaptedParameterSymbol.ExplicitDefaultConstantValue.Value, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) Else Return Nothing End If diff --git a/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb b/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb index 566754a2fe678..4f3ea0aed9844 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb @@ -39,7 +39,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function IParameterTypeInformationGetType(context As EmitContext) As Cci.ITypeReference Implements Cci.IParameterTypeInformation.GetType Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Dim paramType As TypeSymbol = _underlyingParameter.Type - Return moduleBeingBuilt.Translate(paramType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(paramType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private ReadOnly Property IParameterListEntryIndex As UShort Implements Cci.IParameterListEntry.Index diff --git a/src/Compilers/VisualBasic/Portable/Emit/PropertySymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/PropertySymbolAdapter.vb index b5ef4ff0bf49b..9903e462d77fd 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PropertySymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PropertySymbolAdapter.vb @@ -140,7 +140,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Private Function ISignatureGetType(context As EmitContext) As ITypeReference Implements ISignature.GetType CheckDefinitionInvariantAllowEmbedded() - Return (DirectCast(context.Module, PEModuleBuilder)).Translate(AdaptedPropertySymbol.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return (DirectCast(context.Module, PEModuleBuilder)).Translate(AdaptedPropertySymbol.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private ReadOnly Property ITypeDefinitionMemberContainingTypeDefinition As ITypeDefinition Implements ITypeDefinitionMember.ContainingTypeDefinition diff --git a/src/Compilers/VisualBasic/Portable/Emit/SpecializedFieldReference.vb b/src/Compilers/VisualBasic/Portable/Emit/SpecializedFieldReference.vb index 38b7518e43257..d5cf8c6821167 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/SpecializedFieldReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/SpecializedFieldReference.vb @@ -56,7 +56,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function IFieldReferenceGetType(context As EmitContext) As Cci.ITypeReference Implements Cci.IFieldReference.GetType Dim customModifiers = _underlyingField.CustomModifiers - Dim type = DirectCast(context.Module, PEModuleBuilder).Translate(_underlyingField.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Dim type = DirectCast(context.Module, PEModuleBuilder).Translate(_underlyingField.Type, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) If customModifiers.Length = 0 Then Return type diff --git a/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericMethodInstanceReference.vb b/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericMethodInstanceReference.vb index 0a2f9bd7a1add..4b3d466ccfcbb 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericMethodInstanceReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericMethodInstanceReference.vb @@ -34,7 +34,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) Return From arg In m_UnderlyingMethod.TypeArguments - Select moduleBeingBuilt.Translate(arg, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Select moduleBeingBuilt.Translate(arg, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Public Overrides ReadOnly Property AsGenericMethodInstanceReference As Cci.IGenericMethodInstanceReference diff --git a/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericNestedTypeInstanceReference.vb b/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericNestedTypeInstanceReference.vb index f3ed9c94ef4f1..004aa84cf807b 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericNestedTypeInstanceReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/SpecializedGenericNestedTypeInstanceReference.vb @@ -36,7 +36,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim builder = ArrayBuilder(Of Cci.ITypeReference).GetInstance() For Each t In m_UnderlyingNamedType.TypeArgumentsNoUseSiteDiagnostics - builder.Add(moduleBeingBuilt.Translate(t, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics)) + builder.Add(moduleBeingBuilt.Translate(t, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics)) Next Return builder.ToImmutableAndFree() @@ -45,7 +45,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function IGenericTypeInstanceReferenceGetGenericType(context As EmitContext) As Cci.INamedTypeReference Implements Cci.IGenericTypeInstanceReference.GetGenericType Debug.Assert(m_UnderlyingNamedType.OriginalDefinition Is m_UnderlyingNamedType.OriginalDefinition.OriginalDefinition) Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) - Return moduleBeingBuilt.Translate(m_UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + Return moduleBeingBuilt.Translate(m_UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=True) End Function diff --git a/src/Compilers/VisualBasic/Portable/Emit/SpecializedNestedTypeReference.vb b/src/Compilers/VisualBasic/Portable/Emit/SpecializedNestedTypeReference.vb index 452d924461020..b0685aaee1edd 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/SpecializedNestedTypeReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/SpecializedNestedTypeReference.vb @@ -23,7 +23,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function ISpecializedNestedTypeReferenceGetUnspecializedVersion(context As EmitContext) As Cci.INestedTypeReference Implements Cci.ISpecializedNestedTypeReference.GetUnspecializedVersion Debug.Assert(m_UnderlyingNamedType.OriginalDefinition Is m_UnderlyingNamedType.OriginalDefinition.OriginalDefinition) - Dim result = (DirectCast(context.Module, PEModuleBuilder)).Translate(m_UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + Dim result = (DirectCast(context.Module, PEModuleBuilder)).Translate(m_UnderlyingNamedType.OriginalDefinition, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics, needDeclaration:=True).AsNestedTypeReference Debug.Assert(result IsNot Nothing) Return result @@ -34,7 +34,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Sub Private Function ITypeMemberReferenceGetContainingType(context As EmitContext) As Cci.ITypeReference Implements Cci.ITypeMemberReference.GetContainingType - Return (DirectCast(context.Module, PEModuleBuilder)).Translate(m_UnderlyingNamedType.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return (DirectCast(context.Module, PEModuleBuilder)).Translate(m_UnderlyingNamedType.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Public Overrides ReadOnly Property AsGenericTypeInstanceReference As Cci.IGenericTypeInstanceReference diff --git a/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb b/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb index a536a84b50e09..e53016af2ca34 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb @@ -13,7 +13,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public Overridable Function GetContainingType(context As EmitContext) As Cci.ITypeReference Implements Cci.ITypeMemberReference.GetContainingType Dim moduleBeingBuilt As PEModuleBuilder = DirectCast(context.Module, PEModuleBuilder) - Return moduleBeingBuilt.Translate(UnderlyingSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) + Return moduleBeingBuilt.Translate(UnderlyingSymbol.ContainingType, syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) End Function Private ReadOnly Property INamedEntityName As String Implements Cci.INamedEntity.Name diff --git a/src/Compilers/VisualBasic/Portable/Emit/TypeParameterSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/TypeParameterSymbolAdapter.vb index 2e8cf5c8f5db2..2b94b3f77ac31 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/TypeParameterSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/TypeParameterSymbolAdapter.vb @@ -201,7 +201,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End If Dim typeRef As ITypeReference = _module.Translate(t, - syntaxNodeOpt:=DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), + syntaxNodeOpt:=DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), diagnostics:=context.Diagnostics) Yield t.GetTypeRefWithAttributes(AdaptedTypeParameterSymbol.DeclaringCompilation, typeRef) @@ -209,7 +209,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols If AdaptedTypeParameterSymbol.HasValueTypeConstraint AndAlso Not seenValueType Then ' Add System.ValueType constraint to comply with Dev11 C# output Dim typeRef As INamedTypeReference = _module.GetSpecialType(CodeAnalysis.SpecialType.System_ValueType, - DirectCast(context.SyntaxNodeOpt, VisualBasicSyntaxNode), context.Diagnostics) + DirectCast(context.SyntaxNode, VisualBasicSyntaxNode), context.Diagnostics) Yield New Cci.TypeReferenceWithAttributes(typeRef) End If diff --git a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt index 38c7a8bf816fd..264737b55b170 100644 --- a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16_9 = 1609 -> Microsoft.CodeAnalysis.VisualBasic.LanguageVersion Microsoft.CodeAnalysis.VisualBasic.VisualBasicGeneratorDriver +Overrides Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.GetLineMappings(cancellationToken As System.Threading.CancellationToken = Nothing) -> System.Collections.Generic.IEnumerable(Of Microsoft.CodeAnalysis.LineMapping) Shared Microsoft.CodeAnalysis.VisualBasic.VisualBasicGeneratorDriver.Create(generators As System.Collections.Immutable.ImmutableArray(Of Microsoft.CodeAnalysis.ISourceGenerator), additionalTexts As System.Collections.Immutable.ImmutableArray(Of Microsoft.CodeAnalysis.AdditionalText) = Nothing, parseOptions As Microsoft.CodeAnalysis.VisualBasic.VisualBasicParseOptions = Nothing, analyzerConfigOptionsProvider As Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider = Nothing) -> Microsoft.CodeAnalysis.VisualBasic.VisualBasicGeneratorDriver Overrides Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation.GetUsedAssemblyReferences(cancellationToken As System.Threading.CancellationToken = Nothing) -> System.Collections.Immutable.ImmutableArray(Of Microsoft.CodeAnalysis.MetadataReference) \ No newline at end of file diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MissingMetadataTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MissingMetadataTypeSymbol.vb index 7ede361f825e8..ef87f87d7c3b0 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MissingMetadataTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MissingMetadataTypeSymbol.vb @@ -51,17 +51,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Get Dim containingAssembly As AssemblySymbol = Me.ContainingAssembly - If containingAssembly.IsMissing Then + If containingAssembly?.IsMissing Then Dim arg = If(Me.SpecialType <> SpecialType.None, DirectCast(CustomSymbolDisplayFormatter.DefaultErrorFormat(Me), Object), Me) Return ErrorFactory.ErrorInfo(ERRID.ERR_UnreferencedAssembly3, containingAssembly.Identity, arg) Else Dim containingModule As ModuleSymbol = Me.ContainingModule - If containingModule.IsMissing Then - Return ErrorFactory.ErrorInfo(ERRID.ERR_UnreferencedModule3, containingModule.Name, Me) + If containingModule IsNot Nothing Then + If containingModule.IsMissing Then + Return ErrorFactory.ErrorInfo(ERRID.ERR_UnreferencedModule3, containingModule.Name, Me) + End If + + Return ErrorFactory.ErrorInfo(ERRID.ERR_TypeRefResolutionError3, Me, containingModule.Name) End If - Return ErrorFactory.ErrorInfo(ERRID.ERR_TypeRefResolutionError3, Me, containingModule.Name) + Return If(TryCast(ContainingType, ErrorTypeSymbol)?.ErrorInfo, + ErrorFactory.ErrorInfo(ERRID.ERR_UnsupportedType1, String.Empty)) ' This is the best we can do at this point End If End Get End Property diff --git a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicLineDirectiveMap.vb b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicLineDirectiveMap.vb index cd62fab0ded18..127eb38c85c84 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicLineDirectiveMap.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicLineDirectiveMap.vb @@ -151,6 +151,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax Return GetLineVisibility(index) End Function + Protected Overrides Function GetUnknownStateVisibility(index As Integer) As LineVisibility + Return GetLineVisibility(index) + End Function + Private Overloads Function GetLineVisibility(index As Integer) As LineVisibility ' #ExternalSource is used primarily for ASP.NET (formerly XSP) or Venus. The requirement is that only spans marked ' with the directive should add sequence points. This is because the XSP guys don't want the user debugging into diff --git a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb index 9329c4eff92a7..0370605d0b81e 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb @@ -387,6 +387,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return InDocumentationComment(CType(trivia.Token, SyntaxToken)) End Function + Private Function GetDirectiveMap() As VisualBasicLineDirectiveMap + If _lineDirectiveMap Is Nothing Then + ' Create the line directive map on demand. + Interlocked.CompareExchange(_lineDirectiveMap, New VisualBasicLineDirectiveMap(Me), Nothing) + End If + + Return _lineDirectiveMap + End Function + ''' ''' Gets the location in terms of path, line and column for a given . ''' @@ -413,39 +422,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' If the location path is not mapped the resulting path is . ''' Public Overrides Function GetMappedLineSpan(span As TextSpan, Optional cancellationToken As CancellationToken = Nothing) As FileLinePositionSpan - If _lineDirectiveMap Is Nothing Then - ' Create the line directive map on demand. - Interlocked.CompareExchange(_lineDirectiveMap, New VisualBasicLineDirectiveMap(Me), Nothing) - End If - - Return _lineDirectiveMap.TranslateSpan(Me.GetText(cancellationToken), Me.FilePath, span) + Return GetDirectiveMap().TranslateSpan(GetText(cancellationToken), FilePath, span) End Function + ''' Public Overrides Function GetLineVisibility(position As Integer, Optional cancellationToken As CancellationToken = Nothing) As LineVisibility - If _lineDirectiveMap Is Nothing Then - ' Create the line directive map on demand. - Interlocked.CompareExchange(_lineDirectiveMap, New VisualBasicLineDirectiveMap(Me), Nothing) - End If - - Return _lineDirectiveMap.GetLineVisibility(Me.GetText(cancellationToken), position) + Return GetDirectiveMap().GetLineVisibility(Me.GetText(cancellationToken), position) End Function Friend Overrides Function GetMappedLineSpanAndVisibility(span As TextSpan, ByRef isHiddenPosition As Boolean) As FileLinePositionSpan - If _lineDirectiveMap Is Nothing Then - ' Create the line directive map on demand. - Interlocked.CompareExchange(_lineDirectiveMap, New VisualBasicLineDirectiveMap(Me), Nothing) - End If + Return GetDirectiveMap().TranslateSpanAndVisibility(Me.GetText(), Me.FilePath, span, isHiddenPosition) + End Function - Return _lineDirectiveMap.TranslateSpanAndVisibility(Me.GetText(), Me.FilePath, span, isHiddenPosition) + ''' + Public Overrides Function GetLineMappings(Optional cancellationToken As CancellationToken = Nothing) As IEnumerable(Of LineMapping) + Dim map = GetDirectiveMap() + Debug.Assert(map.Entries.Length >= 1) + Return If(map.Entries.Length = 1, Array.Empty(Of LineMapping)(), map.GetLineMappings(GetText(cancellationToken).Lines)) End Function Public Overrides Function HasHiddenRegions() As Boolean - If _lineDirectiveMap Is Nothing Then - ' Create the line directive map on demand. - Interlocked.CompareExchange(_lineDirectiveMap, New VisualBasicLineDirectiveMap(Me), Nothing) - End If - - Return _lineDirectiveMap.HasAnyHiddenRegions() + Return GetDirectiveMap().HasAnyHiddenRegions() End Function ' Gets the reporting state for a warning (diagnostic) at a given source location based on warning directives. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf index 548d8e49ee0a6..9f83a032adb3a 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + Atribut UnmanagedCallersOnly se nepodporuje. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf index 539c9d52877d7..f51f52e925693 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + Das Attribut "UnmanagedCallersOnly" wird nicht unterstützt. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf index b31f5e72839c4..8b6dc9d3f257f 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -29,12 +29,12 @@ '{0}' cannot override init-only '{1}'. - "{0}" no puede sobrescribir la propiedad "{1}" de solo inicio. + "{0}" no puede sobrescribir la propiedad init-only "{1}". Init-only '{0}' cannot be implemented. - No se puede implementar la propiedad "{0}" de solo inicio. + La propiedad Init-only "{0}" no se puede implementar. @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + No se admite el atributo "UnmanagedCallersOnly". @@ -69,7 +69,7 @@ assigning to or passing 'ByRef' properties with init-only setters - se asigna a propiedades "ByRef" o se pasa a estas con establecedores de solo inicio + se asigna a propiedades "ByRef" o se pasa a estas con establecedores init-only diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf index 9eba2e80fabab..a93959fe4783e 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + L'attribut 'UnmanagedCallersOnly' n'est pas pris en charge. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf index a007c1baaf5f4..4f1d8849ce630 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + L'attributo 'UnmanagedCallersOnly' non è supportato. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf index 5eb9bb04bc193..681b5b63cc41f 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + 'UnmanagedCallersOnly' 属性はサポートされていません。 diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf index 3e61a0713ff23..5681d90608c47 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + 'UnmanagedCallersOnly' 특성은 지원되지 않습니다. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf index 75b49306091a3..4aa6463cce289 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + Atrybut „UnmanagedCallersOnly” jest nieobsługiwany. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf index 86cd9d0b0ee1f..a82071ee08b67 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + Não há suporte para o atributo 'UnmanagedCallersOnly'. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf index b1293366142ad..2aeb50903813b 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + Атрибут "UnmanagedCallersOnly" не поддерживается. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf index 0c4df566719a8..9068dad971eb6 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + 'UnmanagedCallersOnly' özniteliği desteklenmez. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf index de28e8e4fe70a..25e84d8a1afc5 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + 不支持 "UnmanagedCallersOnly" 属性。 @@ -5539,7 +5539,7 @@ 'New' cannot be used with tuple type. Use a tuple literal expression instead. - '"New" 不能用于元组类型。请改用元组文本表达式。 + "New" 不能与元组类型共同使用。请改用元组字面量表达式。 diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf index 9f17362d77b5a..f8fc335555d01 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@ 'UnmanagedCallersOnly' attribute is not supported. - 'UnmanagedCallersOnly' attribute is not supported. + 不支援 'UnmanagedCallersOnly' 屬性。 diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb index eb04c61bb9507..83c1e44fac1f9 100644 --- a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb @@ -4611,9 +4611,9 @@ End Class" Dim Compilation = CreateEmptyCompilation("") Dim result = Compilation.Emit(outStream, options:=New EmitOptions(pdbFilePath:="test\\?.pdb", debugInformationFormat:=DebugInformationFormat.Embedded)) - Assert.False(result.Success) - ' // error BC2032: File name 'test\?.pdb' is empty, contains invalid characters, has a drive specification without an absolute path, or is too long - result.Diagnostics.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments("test\\?.pdb").WithLocation(1, 1)) + ' This is fine because EmitOptions just controls what is written into the PE file and it's + ' valid for this to be an illegal file name (path map can easily create these). + Assert.True(result.Success) End Using End Sub diff --git a/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb b/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb index ffb06b5ba9a6c..17b3d68434888 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb @@ -3,6 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities.TestGenerators @@ -179,6 +181,80 @@ End Namespace Assert.Same(rootFromGetRoot, rootFromTryGetRoot) End Sub + + Public Sub Diagnostics_Respect_Suppression() + + Dim compilation As Compilation = GetCompilation(TestOptions.Regular) + + Dim gen As CallbackGenerator = New CallbackGenerator(Sub(c) + End Sub, + Sub(c) + c.ReportDiagnostic(VBDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 2)) + c.ReportDiagnostic(VBDiagnostic.Create("GEN002", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 3)) + End Sub) + + VerifyDiagnosticsWithOptions(gen, compilation, + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1)) + + + Dim warnings As IDictionary(Of String, ReportDiagnostic) = New Dictionary(Of String, ReportDiagnostic)() + warnings.Add("GEN001", ReportDiagnostic.Suppress) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN002").WithLocation(1, 1)) + + warnings.Clear() + warnings.Add("GEN002", ReportDiagnostic.Suppress) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN001").WithLocation(1, 1)) + + warnings.Clear() + warnings.Add("GEN001", ReportDiagnostic.Error) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN001").WithLocation(1, 1).WithWarningAsError(True), + Diagnostic("GEN002").WithLocation(1, 1)) + + warnings.Clear() + warnings.Add("GEN002", ReportDiagnostic.Error) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1).WithWarningAsError(True)) + End Sub + + + Public Sub Diagnostics_Respect_Pragma_Suppression() + + Dim gen001 = VBDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 2) + + VerifyDiagnosticsWithSource("'comment", + gen001, TextSpan.FromBounds(1, 4), + Diagnostic("GEN001", "com").WithLocation(1, 2)) + + VerifyDiagnosticsWithSource("#disable warning +'comment", + gen001, TextSpan.FromBounds(19, 22), + Diagnostic("GEN001", "com", isSuppressed:=True).WithLocation(2, 2)) + + VerifyDiagnosticsWithSource("#disable warning +'comment", + gen001, New TextSpan(0, 0), + Diagnostic("GEN001").WithLocation(1, 1)) + + VerifyDiagnosticsWithSource("#disable warning GEN001 +'comment", + gen001, TextSpan.FromBounds(26, 29), + Diagnostic("GEN001", "com", isSuppressed:=True).WithLocation(2, 2)) + + VerifyDiagnosticsWithSource("#disable warning GEN001 +'comment +#enable warning GEN001 +'another", + gen001, TextSpan.FromBounds(60, 63), + Diagnostic("GEN001", "ano").WithLocation(4, 2)) + + End Sub + + Shared Function GetCompilation(parseOptions As VisualBasicParseOptions, Optional source As String = "") As Compilation If (String.IsNullOrWhiteSpace(source)) Then source = " @@ -194,6 +270,47 @@ End Class Return compilation End Function + Shared Sub VerifyDiagnosticsWithOptions(generator As ISourceGenerator, compilation As Compilation, ParamArray expected As DiagnosticDescription()) + + compilation.VerifyDiagnostics() + Assert.Single(compilation.SyntaxTrees) + + Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(generator), parseOptions:=TestOptions.Regular) + + Dim outputCompilation As Compilation = Nothing + Dim diagnostics As ImmutableArray(Of Diagnostic) = Nothing + driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, diagnostics) + outputCompilation.VerifyDiagnostics() + + diagnostics.Verify(expected) + End Sub + + Shared Sub VerifyDiagnosticsWithSource(source As String, diag As Diagnostic, location As TextSpan, ParamArray expected As DiagnosticDescription()) + Dim parseOptions = TestOptions.Regular + source = source.Replace(Environment.NewLine, vbCrLf) + Dim compilation As Compilation = CreateCompilation(source) + compilation.VerifyDiagnostics() + Assert.Single(compilation.SyntaxTrees) + + Dim gen As ISourceGenerator = New CallbackGenerator(Sub(c) + End Sub, + Sub(c) + If location.IsEmpty Then + c.ReportDiagnostic(diag) + Else + c.ReportDiagnostic(diag.WithLocation(CodeAnalysis.Location.Create(c.Compilation.SyntaxTrees.First(), location))) + End If + End Sub) + + Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions:=TestOptions.Regular) + + Dim outputCompilation As Compilation = Nothing + Dim diagnostics As ImmutableArray(Of Diagnostic) = Nothing + driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, diagnostics) + outputCompilation.VerifyDiagnostics() + + diagnostics.Verify(expected) + End Sub End Class diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/GenericConstraintTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/GenericConstraintTests.vb index 1ff304195a784..d80d560fc0eef 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/GenericConstraintTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/GenericConstraintTests.vb @@ -5839,6 +5839,43 @@ BC32044: Type argument 'String' does not inherit from or implement the constrain ) End Sub + + Public Sub RecursiveConstraintsFromUnifiedAssemblies() + Dim metadataComp = CreateCompilationWithMscorlib40( + + + +, assemblyName:="assembly1") + + metadataComp.AssertTheseDiagnostics() + + Dim finalComp = CreateCompilationWithMscorlib45( + + + + +, {metadataComp.EmitToImageReference()}) + finalComp.AssertTheseDiagnostics() + + Assert.Null(finalComp.GetTypeByMetadataName("C").GetUseSiteErrorInfo()) + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Source/EnumTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Source/EnumTests.vb index b692a5329deec..a911e8734f554 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Source/EnumTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Source/EnumTests.vb @@ -1530,6 +1530,37 @@ End Enum Dim item2000 = comp.GetMember(Of FieldSymbol)("Test.Item2000") Assert.Equal(2001, item2000.ConstantValue) End Sub + + + + Public Sub Issue52624() + Dim source1 = +" +Public Enum SyntaxKind As UShort + None = 0 + List = GreenNode.ListKind +End Enum +" + Dim source2 = +" +Friend Class GreenNode + Public Const ListKind = 1 +End Class +" + + For i As Integer = 1 To 1000 + Dim comp = CreateCompilation({source1, source2}, options:=TestOptions.DebugDll) + comp.VerifyDiagnostics() + + Dim listKind = comp.GlobalNamespace.GetMember(Of FieldSymbol)("GreenNode.ListKind") + Assert.Equal(1, listKind.ConstantValue) + Assert.Equal("System.Int32", listKind.Type.ToTestDisplayString()) + + Dim list = comp.GlobalNamespace.GetMember(Of FieldSymbol)("SyntaxKind.List") + Assert.Equal(1US, list.ConstantValue) + Assert.Equal("SyntaxKind", list.Type.ToTestDisplayString()) + Next + End Sub End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/SymbolErrorTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/SymbolErrorTests.vb index 89495e8cecab3..c915ee3308918 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/SymbolErrorTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/SymbolErrorTests.vb @@ -24623,5 +24623,17 @@ BC37208: Module 'C.dll' in assembly 'C, Version=0.0.0.0, Culture=neutral, Public ]]>) End Sub + + + Public Sub ErrorInfo_01() + Dim [error] = New MissingMetadataTypeSymbol.Nested(New UnsupportedMetadataTypeSymbol(), "Test", 0, False) + Dim info = [error].ErrorInfo + + Assert.Equal(ERRID.ERR_UnsupportedType1, CType(info.Code, ERRID)) + Assert.Null([error].ContainingModule) + Assert.Null([error].ContainingAssembly) + Assert.NotNull([error].ContainingSymbol) + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Syntax/LocationTests.vb b/src/Compilers/VisualBasic/Test/Syntax/LocationTests.vb index 60e5510bcba74..e21c44becf83d 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/LocationTests.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/LocationTests.vb @@ -59,6 +59,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Return New TextSpan(index, textToFind.Length) End Function + Private Shared Function InspectLineMapping(tree As SyntaxTree) As IEnumerable(Of String) + Dim text = tree.GetText() + Return tree.GetLineMappings().Select(Function(mapping) $"[|{text.GetSubText(text.Lines.GetTextSpan(mapping.Span))}|] -> {If(mapping.IsHidden, "", mapping.MappedSpan.ToString())}") + End Function + Public Sub TestGetSourceLocationInFile() Dim sampleProgram = "Class X" + vbCrLf + "Public x As Integer" + vbCrLf + "End Class" + vbCrLf @@ -86,7 +91,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Public Sub TestLineMapping() - Dim sampleProgram As String = + Dim sampleProgram = "Imports System Class X #ExternalSource(""banana.vb"", 20) @@ -106,7 +111,7 @@ public a as integer #End ExternalSource #End If End Class -" +".NormalizeLineEndings() Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") AssertMappedSpanEqual(tree, "ports Sy", "c:\goo.vb", 0, 2, 0, 10, hasMappedPath:=False) @@ -116,31 +121,147 @@ End Class AssertMappedSpanEqual(tree, "w as", "c:\goo.vb", 9, 7, 9, 11, hasMappedPath:=False) AssertMappedSpanEqual(tree, "q as", "c:\goo.vb", 10, 7, 10, 11, hasMappedPath:=False) AssertMappedSpanEqual(tree, "a as", "c:\goo.vb", 14, 7, 14, 11, hasMappedPath:=False) + + AssertEx.Equal( + { + "[|Imports System" & vbCrLf & "Class X" & vbCrLf & "|] -> ", + "[|public x as integer" & vbCrLf & "public y as integer" & vbCrLf & "|] -> banana.vb: (19,0)-(20,21)", + "[|public z as integer" & vbCrLf & "|] -> banana.vb: (43,0)-(43,21)", + "[|public w as integer" & vbCrLf & + "public q as integer" & vbCrLf & + "#If False Then" & vbCrLf & + "#ExternalSource(""apple.vb"", 101)" & vbCrLf & + "#End If" & vbCrLf & + "public a as integer" & vbCrLf & + "#If False Then" & vbCrLf & + "#End ExternalSource" & vbCrLf & + "#End If" & vbCrLf & + "End Class" & vbCrLf & "|] -> " + }, InspectLineMapping(tree)) End Sub - + + Public Sub TestLineMapping_Invalid_MissingStartDirective1() + Dim sampleProgram = +"Class X +#End ExternalSource +End Class +".NormalizeLineEndings() + Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") + + AssertMappedSpanEqual(tree, "Class X", "c:\goo.vb", 0, 0, 0, 7, hasMappedPath:=False) + AssertMappedSpanEqual(tree, "End Class", "c:\goo.vb", 2, 0, 2, 9, hasMappedPath:=False) + + AssertEx.Equal( + { + "[|Class X" & vbCrLf & "|] -> : (0,0)-(0,9)", + "[|End Class" & vbCrLf & "|] -> : (2,0)-(3,0)" + }, InspectLineMapping(tree)) + End Sub + + + Public Sub TestLineMapping_Invalid_MissingStartDirective3() + Dim sampleProgram = +"Class X +#End ExternalSource +Class Y +#End ExternalSource +Class Z +End Class +End Class +End Class +".NormalizeLineEndings() + Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") + + AssertMappedSpanEqual(tree, "Class X", "c:\goo.vb", 0, 0, 0, 7, hasMappedPath:=False) + AssertMappedSpanEqual(tree, "Class Y", "c:\goo.vb", 2, 0, 2, 7, hasMappedPath:=False) + AssertMappedSpanEqual(tree, "Class Z", "c:\goo.vb", 4, 0, 4, 7, hasMappedPath:=False) + + AssertEx.Equal( + { + "[|Class X" & vbCrLf & "|] -> : (0,0)-(0,9)", + "[|Class Y" & vbCrLf & "|] -> : (2,0)-(2,9)", + "[|Class Z" & vbCrLf & "End Class" & vbCrLf & "End Class" & vbCrLf & "End Class" & vbCrLf & "|] -> : (4,0)-(8,0)" + }, InspectLineMapping(tree)) + End Sub + + + Public Sub TestLineMapping_Invalid_MissingEndDirective1() + Dim sampleProgram = +"Class X +#ExternalSource(""a.vb"", 20) +End Class +".NormalizeLineEndings() + Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") + + AssertMappedSpanEqual(tree, "Class X", "c:\goo.vb", 0, 0, 0, 7, hasMappedPath:=False) + AssertMappedSpanEqual(tree, "End Class", "a.vb", 19, 0, 19, 9, hasMappedPath:=True) + + AssertEx.Equal( + { + "[|Class X" & vbCrLf & "|] -> : (0,0)-(0,9)", + "[|End Class" & vbCrLf & "|] -> a.vb: (19,0)-(20,0)" + }, InspectLineMapping(tree)) + End Sub + + + Public Sub TestLineMapping_Invalid_MissingEndDirective2() + Dim sampleProgram = +"Class X +#ExternalSource(""a.vb"", 20) +Class Y +End Class +#ExternalSource(""b.vb"", 20) +Class Z +End Class +End Class +".NormalizeLineEndings() + Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") + + AssertMappedSpanEqual(tree, "Class X", "c:\goo.vb", 0, 0, 0, 7, hasMappedPath:=False) + AssertMappedSpanEqual(tree, "Class Y", "a.vb", 19, 0, 19, 7, hasMappedPath:=True) + AssertMappedSpanEqual(tree, "Class Z", "b.vb", 19, 0, 19, 7, hasMappedPath:=True) + + AssertEx.Equal( + { + "[|Class X" & vbCrLf & "|] -> : (0,0)-(0,9)", + "[|Class Y" & vbCrLf & "End Class" & vbCrLf & "|] -> a.vb: (19,0)-(20,11)", + "[|Class Z" & vbCrLf & "End Class" & vbCrLf & "End Class" & vbCrLf & "|] -> b.vb: (19,0)-(22,0)" + }, InspectLineMapping(tree)) + End Sub + + + Public Sub TestLineMapping_Invalid_MissingEndDirective_LastLine() + Dim sampleProgram = "#End ExternalSource" + Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") + + Assert.Empty(InspectLineMapping(tree)) + End Sub + + Public Sub TestLineMappingNoDirectives() - Dim sampleProgram As String = -Imports System + Dim sampleProgram = +"Imports System Class X public x as integer public y as integer End Class -.Value +".NormalizeLineEndings() Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram, path:="c:\goo.vb") AssertMappedSpanEqual(tree, "ports Sy", "c:\goo.vb", 0, 2, 0, 10, hasMappedPath:=False) AssertMappedSpanEqual(tree, "x as", "c:\goo.vb", 2, 7, 2, 11, hasMappedPath:=False) + Dim text = tree.GetText() + Assert.Empty(tree.GetLineMappings()) End Sub Public Sub TestLineMapping_NoSyntaxTreePath() - Dim sampleProgram As String = " + Dim sampleProgram = " Class X End Class -" - Dim resolver = New TestSourceResolver() +".NormalizeLineEndings() AssertMappedSpanEqual(SyntaxFactory.ParseSyntaxTree(sampleProgram, path:=""), "Class X", "", 1, 0, 1, 7, hasMappedPath:=False) AssertMappedSpanEqual(SyntaxFactory.ParseSyntaxTree(sampleProgram, path:=" "), "Class X", " ", 1, 0, 1, 7, hasMappedPath:=False) End Sub @@ -148,8 +269,8 @@ End Class Public Sub TestEqualSourceLocations() - Dim sampleProgram As String = class -end class .Value + Dim sampleProgram = "class +end class".NormalizeLineEndings() Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram) Dim tree2 = VisualBasicSyntaxTree.ParseText(sampleProgram) Dim loc1 As SourceLocation = New SourceLocation(tree, New TextSpan(3, 4)) @@ -164,13 +285,13 @@ end class .Value Public Sub TestSourceLocationToString() - Dim sampleProgram As String = Imports System + Dim sampleProgram = "Imports System Class Test -#ExternalSource("d:\banana.vb", 20) +#ExternalSource(""d:\banana.vb"", 20) public x As integer #End ExternalSource public y As String -End Class.Value +End Class".NormalizeLineEndings() Dim tree = VisualBasicSyntaxTree.ParseText(sampleProgram) Dim span1 As New TextSpan(sampleProgram.IndexOf("x As", StringComparison.Ordinal), 1) @@ -180,9 +301,9 @@ End Class.Value Dim loc2 = New SourceLocation(tree, span2) 'Assert.Equal("SourceLocation(@4:12)""x""", loc1.DebugView) - Assert.Equal("SourceFile([73..74))", loc1.ToString) 'use the override in Location + Assert.Equal("SourceFile([76..77))", loc1.ToString) 'use the override in Location 'Assert.Equal("SourceLocation(@6:12)""y""", loc2.DebugView) - Assert.Equal("SourceFile([117..118))", loc2.ToString) + Assert.Equal("SourceFile([122..123))", loc2.ToString) End Sub End Class diff --git a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SerializationTests.vb b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SerializationTests.vb index a8fea6102a772..e800fafb1a42c 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SerializationTests.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SerializationTests.vb @@ -218,7 +218,7 @@ End Class Assert.Equal(annotation, dannotation) ' but are equivalent End Sub - + Public Sub RoundtripSerializeDeepExpression() Dim text = GetVisualBasicImportStrings( if (importString.Length >= 2 && importString[0] == '@') { var ch1 = importString[1]; - if ('0' <= ch1 && ch1 <= '9') + if (ch1 is >= '0' and <= '9') { if (int.TryParse(importString.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture, out var tempMethodToken)) { diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf index 214c787cf71dd..7d0b8e01f8355 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Řazení není možné, protože metoda IComparer.Compare() vrací nekonzistentní výsledky. Buď není výsledkem porovnání hodnoty samé se sebou rovnost, nebo opakované porovnání jedné hodnoty s jinou hodnotou vrací různé výsledky. IComparer: {0} diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf index fbc3e82fe68ee..f0aaa5b0bc2aa 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Sortieren nicht möglich, da die IComparer.Compare()-Methode inkonsistente Ergebnisse zurückgibt. Entweder ist ein Wert nicht mit sich identisch, oder ein wiederholt mit einem anderen Wert verglichener Wert gibt verschiedene Ergebnisse aus. IComparer: '{0}'. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf index a697e104c4fff..bdab33501cd9e 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + No se puede ordenar porque el método IComparer.Compare() devuelve resultados incoherentes. No se compara un valor igual a sí mismo, o bien un valor comparado repetidamente con otro proporciona resultados diferentes. IComparer: '{0}'. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf index cfe198ce7e055..93244138e5407 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Impossible d'effectuer le tri, car la méthode IComparer.Compare() retourne des résultats incohérents. Une valeur comparée n'est pas égale à elle-même ou une valeur comparée de manière répétée à une autre valeur donne des résultats différents. IComparer : '{0}'. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf index 52ea44995a068..26c981f809be3 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Impossibile eseguire l'ordinamento. Il metodo IComparer.Compare() restituisce risultati incoerenti. Un valore non viene confrontato uguale a se stesso oppure un valore confrontato ripetutamente con un altro valore restituisce risultati diversi. IComparer: '{0}'. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf index 04b4318e230d8..0d0d5f5c02109 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + IComparer.Compare() メソッドから矛盾する結果が返されたため、並べ替えできません。値をそれ自体と比較したときに等しい結果にならないか、またはある値を別の値と繰り返し比較したときに異なる結果が生じます。IComparer: '{0}'。 diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf index e05281d429c72..b5a43ffc1a689 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + IComparer.Compare() 메서드가 일관성 없는 결과를 반환하므로 정렬할 수 없습니다. 값이 자신과 같은지 비교하지 않거나 한 값이 다른 값과 반복해서 비교되어 다른 결과를 생성합니다. IComparer: '{0}'. @@ -24,12 +24,12 @@ Destination array was not long enough. Check the destination index, length, and the array's lower bounds. - 대상 배열의 길이가 짧습니다. 대상 인덱스, 길이 및 배열의 하한을 확인하십시오. + 대상 배열의 길이가 짧습니다. 대상 인덱스, 길이, 배열의 하한을 확인하세요. Source array was not long enough. Check the source index, length, and the array's lower bounds. - 소스 배열의 길이가 짧습니다. 소스 인덱스, 길이 및 배열의 하한을 확인하십시오. + 소스 배열의 길이가 짧습니다. 소스 인덱스, 길이, 배열의 하한을 확인하세요. @@ -104,7 +104,7 @@ Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct. - 비 동시 컬렉션을 변경하는 작업에는 단독 액세스 권한이 있어야 합니다. 이 컬렉션에 대해 동시 업데이트가 수행되어 해당 상태가 손상되었습니다. 컬렉션의 상태가 더 이상 올바르지 않습니다. + 비동시 컬렉션을 변경하는 작업에는 단독 액세스 권한이 있어야 합니다. 이 컬렉션에 대해 동시 업데이트가 수행되어 해당 상태가 손상되었습니다. 컬렉션의 상태가 더 이상 올바르지 않습니다. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf index 8708a9fd9315a..2977da2df4ab5 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Nie można wykonać sortowania, ponieważ metoda IComparer.Compare() zwraca niespójne wyniki. Albo porównanie wartości z samą sobą nie daje w wyniku równości, albo jedna wartość porównywana wielokrotnie z inną wartością daje różne wyniki. IComparer: „{0}”. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf index c0dc61bc94c8e..a9e91a7ffa6cb 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Não é possível classificar porque o método IComparer.Compare() retorna resultados inconsistentes. Um valor não se compara igual a ele mesmo ou um valor comparado repetidas vezes a outro valor apresenta resultados diferentes. IComparer: '{0}'. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf index 9bd613221a006..5f736ff045e6d 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + Не удалось выполнить сортировку, поскольку метод IComparer.Compare() вернул несовместимые результаты. Значение не равно самому себе при сравнении, либо многократное сравнение одного значения с другим значением дает разные результаты. IComparer: "{0}". diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf index 6cf80a6de4f5c..ec9e67f37a0f5 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + IComparer.Compare() metodu tutarsız sonuçlar döndürdüğünden sıralanamıyor. Bir değer kendisiyle karşılaştırıldığında eşit olmuyor veya bir değer başka bir değerle art arda karşılaştırıldığında farklı sonuçlar oluşuyor. IComparer: '{0}'. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf index 65afe42bb5778..355f70efcbf8a 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + 无法排序,原因是 IComparer.Compare() 方法返回不一致的结果。一个值与本身比较不相等,或者一个值与另外一个值重复比较生成不同的结果。IComparer:“{0}”。 diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf index 343c51a6b1045..5827860c8e6b0 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. - Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'. + 無法排序,因為 IComparer.Compare() 方法傳回不一致的結果。可能是某個值與自己比較卻不相等,或是一個值反覆與另一個值比較但產生不同的結果。IComparer: '{0}'。 diff --git a/src/Dependencies/Collections/SegmentedDictionary`2.cs b/src/Dependencies/Collections/SegmentedDictionary`2.cs index f8dbf0b85ebe2..123f0e73cc5a8 100644 --- a/src/Dependencies/Collections/SegmentedDictionary`2.cs +++ b/src/Dependencies/Collections/SegmentedDictionary`2.cs @@ -32,6 +32,13 @@ namespace Microsoft.CodeAnalysis.Collections internal sealed class SegmentedDictionary : IDictionary, IDictionary, IReadOnlyDictionary where TKey : notnull { + private const bool SupportsComparerDevirtualization +#if NETCOREAPP + = true; +#else + = false; +#endif + private SegmentedArray _buckets; private SegmentedArray _entries; private ulong _fastModMultiplier; @@ -39,7 +46,15 @@ internal sealed class SegmentedDictionary : IDictionary? _comparer; +#else + /// + /// doesn't devirtualize on .NET Framework, so we always ensure + /// is initialized to a non- value. + /// + private readonly IEqualityComparer _comparer; +#endif private KeyCollection? _keys; private ValueCollection? _values; private const int StartOfFreeList = -3; @@ -75,6 +90,11 @@ public SegmentedDictionary(int capacity, IEqualityComparer? comparer) { _comparer = comparer; } + +#if !NETCOREAPP + // .NET Framework doesn't support devirtualization, so we always initialize comparer to a non-null value + _comparer ??= EqualityComparer.Default; +#endif } public SegmentedDictionary(IDictionary dictionary) @@ -241,7 +261,7 @@ public bool ContainsValue(TValue value) } } } - else if (typeof(TValue).IsValueType) + else if (SupportsComparerDevirtualization && typeof(TValue).IsValueType) { // ValueType: Devirtualize with EqualityComparer.Default intrinsic for (var i = 0; i < _count; i++) @@ -316,14 +336,9 @@ private ref TValue FindValue(TKey key) { Debug.Assert(_entries.Length > 0, "expected entries to be non-empty"); var comparer = _comparer; - if (comparer == null) + if (SupportsComparerDevirtualization && comparer == null) { -#if NETCOREAPP var hashCode = (uint)key.GetHashCode(); -#else - // Avoid boxing enum types on .NET Framework - var hashCode = (uint)EqualityComparer.Default.GetHashCode(key); -#endif var i = GetBucket(hashCode); var entries = _entries; uint collisionCount = 0; @@ -427,7 +442,7 @@ private ref TValue FindValue(TKey key) ConcurrentOperation: ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); ReturnFound: - ref var value = ref entry._value; + ref TValue value = ref entry._value; Return: return ref value; ReturnNotFound: @@ -467,18 +482,13 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) Debug.Assert(entries.Length > 0, "expected entries to be non-empty"); var comparer = _comparer; -#if NETCOREAPP - var hashCode = (uint)((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); -#else - // Avoid boxing enum types on .NET Framework - var hashCode = (uint)((comparer == null) ? EqualityComparer.Default.GetHashCode(key) : comparer.GetHashCode(key)); -#endif + var hashCode = (uint)((SupportsComparerDevirtualization && comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); uint collisionCount = 0; ref var bucket = ref GetBucket(hashCode); var i = bucket - 1; // Value in _buckets is 1-based - if (comparer == null) + if (SupportsComparerDevirtualization && comparer == null) { if (typeof(TKey).IsValueType) { @@ -676,12 +686,7 @@ public bool Remove(TKey key) { Debug.Assert(_entries.Length > 0, "entries should be non-empty"); uint collisionCount = 0; -#if NETCOREAPP var hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); -#else - // Avoid boxing enum types on .NET Framework - var hashCode = (uint)(_comparer?.GetHashCode(key) ?? EqualityComparer.Default.GetHashCode(key)); -#endif ref var bucket = ref GetBucket(hashCode); var entries = _entries; var last = -1; @@ -753,12 +758,7 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) { Debug.Assert(_entries.Length > 0, "entries should be non-empty"); uint collisionCount = 0; -#if NETCOREAPP var hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); -#else - // Avoid boxing enum types on .NET Framework - var hashCode = (uint)(_comparer?.GetHashCode(key) ?? EqualityComparer.Default.GetHashCode(key)); -#endif ref var bucket = ref GetBucket(hashCode); var entries = _entries; var last = -1; diff --git a/src/Dependencies/PooledObjects/ArrayBuilder.cs b/src/Dependencies/PooledObjects/ArrayBuilder.cs index aee0071024c16..5dc07827daab8 100644 --- a/src/Dependencies/PooledObjects/ArrayBuilder.cs +++ b/src/Dependencies/PooledObjects/ArrayBuilder.cs @@ -201,6 +201,26 @@ public int FindIndex(int startIndex, int count, Predicate match) return -1; } + public int FindIndex(Func match, TArg arg) + => FindIndex(0, Count, match, arg); + + public int FindIndex(int startIndex, Func match, TArg arg) + => FindIndex(startIndex, Count - startIndex, match, arg); + + public int FindIndex(int startIndex, int count, Func match, TArg arg) + { + var endIndex = startIndex + count; + for (var i = startIndex; i < endIndex; i++) + { + if (match(_builder[i], arg)) + { + return i; + } + } + + return -1; + } + public bool Remove(T element) { return _builder.Remove(element); diff --git a/src/Deployment/Properties/launchSettings.json b/src/Deployment/Properties/launchSettings.json index 01d32bd540287..570c6c4af71fb 100644 --- a/src/Deployment/Properties/launchSettings.json +++ b/src/Deployment/Properties/launchSettings.json @@ -5,6 +5,14 @@ "executablePath": "$(DevEnvDir)devenv.exe", "commandLineArgs": "/rootsuffix $(VSSDKTargetPlatformRegRootSuffix) /log" }, + "Visual Studio Extension (with ServiceHub Debugging)": { + "commandName": "Executable", + "executablePath": "$(DevEnvDir)devenv.exe", + "commandLineArgs": "/rootsuffix $(VSSDKTargetPlatformRegRootSuffix) /log", + "environmentVariables": { + "SERVICEHUBDEBUGHOSTONSTARTUP": "All" + } + }, "Visual Studio Extension (with Native Debugging)": { "commandName": "Executable", "executablePath": "$(DevEnvDir)devenv.exe", diff --git a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs index 66eb2cd13d206..5d42e7a200b3e 100644 --- a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs @@ -147,8 +147,14 @@ private static bool TryGetStartingNode(SyntaxNode root, SnapshotPoint caret, // `obj.ToString$()` where `token` references `(` but the caret isn't actually inside the argument list. // `obj.ToString()$` or `obj.method()$ .method()` where `token` references `)` but the caret isn't inside the argument list. // `defa$$ult(object)` where `token` references `default` but the caret isn't inside the parentheses. - var (openingDelimeter, closingDelimiter) = GetDelimiters(startingNode); - if (!openingDelimeter.IsKind(SyntaxKind.None) && openingDelimeter.Span.Start >= caretPosition + var delimiters = startingNode.GetParentheses(); + if (delimiters == default) + { + delimiters = startingNode.GetBrackets(); + } + + var (openingDelimiter, closingDelimiter) = delimiters; + if (!openingDelimiter.IsKind(SyntaxKind.None) && openingDelimiter.Span.Start >= caretPosition || !closingDelimiter.IsKind(SyntaxKind.None) && closingDelimiter.Span.End <= caretPosition) { startingNode = startingNode.Parent; @@ -327,6 +333,7 @@ private static bool TryGetCaretPositionToMove(SyntaxNode statementNode, Snapshot case SyntaxKind.ArrowExpressionClause: case SyntaxKind.MethodDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: // These statement types end in a semicolon. // if the original caret was inside any delimiters, `caret` will be after the outermost delimiter targetPosition = caret; @@ -387,87 +394,6 @@ private static bool IsInAStringOrCharacter(SyntaxNode currentNode, SnapshotPoint && caret.Position < currentNode.Span.End && caret.Position > currentNode.SpanStart; - private static bool SemicolonIsMissing(SyntaxNode currentNode) - { - switch (currentNode.Kind()) - { - case SyntaxKind.LocalDeclarationStatement: - return ((LocalDeclarationStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ReturnStatement: - return ((ReturnStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.VariableDeclaration: - return SemicolonIsMissing(currentNode.Parent); - case SyntaxKind.ThrowStatement: - return ((ThrowStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.DoStatement: - return ((DoStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - return ((AccessorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.FieldDeclaration: - return ((FieldDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ForStatement: - return ((ForStatementSyntax)currentNode).FirstSemicolonToken.IsMissing; - case SyntaxKind.ExpressionStatement: - return ((ExpressionStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.EmptyStatement: - return ((EmptyStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.GotoStatement: - return ((GotoStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.BreakStatement: - return ((BreakStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ContinueStatement: - return ((ContinueStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.YieldReturnStatement: - case SyntaxKind.YieldBreakStatement: - return ((YieldStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.LocalFunctionStatement: - return ((LocalFunctionStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.NamespaceDeclaration: - return ((NamespaceDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.UsingDirective: - return ((UsingDirectiveSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ExternAliasDirective: - return ((ExternAliasDirectiveSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ClassDeclaration: - return ((ClassDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.StructDeclaration: - return ((StructDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.InterfaceDeclaration: - return ((InterfaceDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.EnumDeclaration: - return ((EnumDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.EventFieldDeclaration: - return ((EventFieldDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ConversionOperatorDeclaration: - return ((ConversionOperatorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.BaseConstructorInitializer: - case SyntaxKind.ThisConstructorInitializer: - return ((ConstructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.DestructorDeclaration: - return ((DestructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.PropertyDeclaration: - return ((PropertyDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.AddAccessorDeclaration: - return ((AccessorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - default: - // At this point, the node should be empty or its children should not end with a semicolon. - Debug.Assert(!currentNode.ChildNodesAndTokens().Any() - || !currentNode.ChildNodesAndTokens().Last().IsKind(SyntaxKind.SemicolonToken)); - return false; - } - } - /// /// Determines if a statement ends with a closing delimiter, and that closing delimiter exists. /// @@ -518,59 +444,8 @@ private static bool StatementClosingDelimiterIsMissing(SyntaxNode currentNode) /// private static bool RequiredDelimiterIsMissing(SyntaxNode currentNode) { - return GetDelimiters(currentNode).closingDelimiter.IsMissing; - } - - private static (SyntaxToken openingDelimeter, SyntaxToken closingDelimiter) GetDelimiters(SyntaxNode currentNode) - { - switch (currentNode.Kind()) - { - case SyntaxKind.ArgumentList: - var argumentList = (ArgumentListSyntax)currentNode; - return (argumentList.OpenParenToken, argumentList.CloseParenToken); - - case SyntaxKind.ParenthesizedExpression: - var parenthesizedExpression = (ParenthesizedExpressionSyntax)currentNode; - return (parenthesizedExpression.OpenParenToken, parenthesizedExpression.CloseParenToken); - - case SyntaxKind.BracketedArgumentList: - var bracketedArgumentList = (BracketedArgumentListSyntax)currentNode; - return (bracketedArgumentList.OpenBracketToken, bracketedArgumentList.CloseBracketToken); - - case SyntaxKind.ObjectInitializerExpression: - case SyntaxKind.WithInitializerExpression: - var initializerExpressionSyntax = (InitializerExpressionSyntax)currentNode; - return (initializerExpressionSyntax.OpenBraceToken, initializerExpressionSyntax.CloseBraceToken); - - case SyntaxKind.ArrayRankSpecifier: - var arrayRankSpecifierSyntax = (ArrayRankSpecifierSyntax)currentNode; - return (arrayRankSpecifierSyntax.OpenBracketToken, arrayRankSpecifierSyntax.CloseBracketToken); - - case SyntaxKind.ParameterList: - var parameterList = (ParameterListSyntax)currentNode; - return (parameterList.OpenParenToken, parameterList.CloseParenToken); - - case SyntaxKind.DefaultExpression: - var defaultExpressionSyntax = (DefaultExpressionSyntax)currentNode; - return (defaultExpressionSyntax.OpenParenToken, defaultExpressionSyntax.CloseParenToken); - - case SyntaxKind.CheckedExpression: - case SyntaxKind.UncheckedExpression: - var checkedExpressionSyntax = (CheckedExpressionSyntax)currentNode; - return (checkedExpressionSyntax.OpenParenToken, checkedExpressionSyntax.CloseParenToken); - - case SyntaxKind.TypeOfExpression: - var typeOfExpressionSyntax = (TypeOfExpressionSyntax)currentNode; - return (typeOfExpressionSyntax.OpenParenToken, typeOfExpressionSyntax.CloseParenToken); - - case SyntaxKind.TupleExpression: - var tupleExpressionSyntax = (TupleExpressionSyntax)currentNode; - return (tupleExpressionSyntax.OpenParenToken, tupleExpressionSyntax.CloseParenToken); - - default: - // Type of node does not have delimiters used by this feature - return default; - } + return currentNode.GetBrackets().closeBracket.IsMissing || + currentNode.GetParentheses().closeParen.IsMissing; } } } diff --git a/src/EditorFeatures/CSharp/ContentType/ContentTypeDefinitions.cs b/src/EditorFeatures/CSharp/ContentType/ContentTypeDefinitions.cs index 2e1d06414bf47..a48534ec0c5e0 100644 --- a/src/EditorFeatures/CSharp/ContentType/ContentTypeDefinitions.cs +++ b/src/EditorFeatures/CSharp/ContentType/ContentTypeDefinitions.cs @@ -17,6 +17,10 @@ internal static class ContentTypeDefinitions [Export] [Name(ContentTypeNames.CSharpContentType)] [BaseDefinition(ContentTypeNames.RoslynContentType)] + // Adds the LSP base content type to ensure the LSP client activates on C# files. + // From Microsoft.VisualStudio.LanguageServer.Client.CodeRemoteContentDefinition.CodeRemoteBaseTypeName + // We cannot directly reference the LSP client package in EditorFeatures as it is a VS dependency. + [BaseDefinition("code-languageserver-base")] public static readonly ContentTypeDefinition CSharpContentTypeDefinition; [Export] diff --git a/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs b/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs index 839a690ee1583..ecd33f848fd00 100644 --- a/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs +++ b/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs @@ -21,15 +21,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.DocumentationComments [Order(After = PredefinedCommandHandlerNames.Rename)] [Order(After = PredefinedCompletionNames.CompletionCommandHandler)] internal class DocumentationCommentCommandHandler - : AbstractDocumentationCommentCommandHandler + : AbstractDocumentationCommentCommandHandler { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DocumentationCommentCommandHandler( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService) - : base(waitIndicator, undoHistoryRegistry, editorOperationsFactoryService) + : base(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService) { } diff --git a/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs b/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs index 378575811c2c3..1fedd08ae3d7c 100644 --- a/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs +++ b/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs @@ -5,7 +5,6 @@ using System; using System.Composition; using Microsoft.CodeAnalysis.Editor.FindUsages; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Editor.CSharp.FindUsages diff --git a/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs b/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs new file mode 100644 index 0000000000000..02518184e9a8c --- /dev/null +++ b/src/EditorFeatures/CSharp/InheritanceMargin/CSharpInheritanceMarginService.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.InheritanceMargin +{ + [ExportLanguageService(typeof(IInheritanceMarginService), LanguageNames.CSharp), Shared] + internal class CSharpInheritanceMarginService : AbstractInheritanceMarginService + { + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + [ImportingConstructor] + public CSharpInheritanceMarginService() + { + } + + protected override ImmutableArray GetMembers(IEnumerable nodesToSearch) + { + var typeDeclarationNodes = nodesToSearch.OfType(); + + using var _ = PooledObjects.ArrayBuilder.GetInstance(out var builder); + foreach (var typeDeclarationNode in typeDeclarationNodes) + { + // 1. Add the type declaration node.(e.g. class, struct etc..) + // Use its identifier's position as the line number, since we want the margin to be placed with the identifier + builder.Add(typeDeclarationNode); + + // 2. Add type members inside this type declaration. + foreach (var member in typeDeclarationNode.Members) + { + if (member.IsKind( + SyntaxKind.MethodDeclaration, + SyntaxKind.PropertyDeclaration, + SyntaxKind.EventDeclaration, + SyntaxKind.IndexerDeclaration)) + { + builder.Add(member); + } + + // For multiple events that declared in the same EventFieldDeclaration, + // add all VariableDeclarators + if (member is EventFieldDeclarationSyntax eventFieldDeclarationNode) + { + builder.AddRange(eventFieldDeclarationNode.Declaration.Variables); + } + } + } + + return builder.ToImmutableArray(); + } + + protected override SyntaxToken GetDeclarationToken(SyntaxNode declarationNode) + => declarationNode switch + { + MethodDeclarationSyntax methodDeclarationNode => methodDeclarationNode.Identifier, + PropertyDeclarationSyntax propertyDeclarationNode => propertyDeclarationNode.Identifier, + EventDeclarationSyntax eventDeclarationNode => eventDeclarationNode.Identifier, + VariableDeclaratorSyntax variableDeclaratorNode => variableDeclaratorNode.Identifier, + TypeDeclarationSyntax baseTypeDeclarationNode => baseTypeDeclarationNode.Identifier, + IndexerDeclarationSyntax indexerDeclarationNode => indexerDeclarationNode.ThisKeyword, + // Shouldn't reach here since the input declaration nodes are coming from GetMembers() method above + _ => throw ExceptionUtilities.UnexpectedValue(declarationNode), + }; + } +} diff --git a/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs b/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs index d0ce747248e8a..08025f9b33cdc 100644 --- a/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs @@ -4,15 +4,14 @@ using System; using System.Composition; -using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.NavigationBar; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.NavigationBar { @@ -21,26 +20,15 @@ internal class CSharpEditorNavigationBarItemService : AbstractEditorNavigationBa { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpEditorNavigationBarItemService() + public CSharpEditorNavigationBarItemService(IThreadingContext threadingContext) + : base(threadingContext) { } - protected override VirtualTreePoint? GetSymbolNavigationPoint( - Document document, ISymbol symbol, CancellationToken cancellationToken) + protected override async Task TryNavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, ITextSnapshot textSnapshot, CancellationToken cancellationToken) { - var syntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken); - var location = symbol.Locations.FirstOrDefault(l => l.SourceTree!.Equals(syntaxTree)); - - if (location == null) - location = symbol.Locations.FirstOrDefault(); - - if (location == null) - return null; - - return new VirtualTreePoint(location.SourceTree!, location.SourceTree!.GetText(cancellationToken), location.SourceSpan.Start); + await NavigateToSymbolItemAsync(document, item, (RoslynNavigationBarItem.SymbolItem)item.UnderlyingItem, textSnapshot, cancellationToken).ConfigureAwait(false); + return true; } - - protected override void NavigateToItem(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken) - => NavigateToSymbolItem(document, (RoslynNavigationBarItem.SymbolItem)item.UnderlyingItem, cancellationToken); } } diff --git a/src/EditorFeatures/CSharp/TextStructureNavigation/TextStructureNavigatorProvider.cs b/src/EditorFeatures/CSharp/TextStructureNavigation/TextStructureNavigatorProvider.cs index 3afc147c5f36f..e55bbc6760482 100644 --- a/src/EditorFeatures/CSharp/TextStructureNavigation/TextStructureNavigatorProvider.cs +++ b/src/EditorFeatures/CSharp/TextStructureNavigation/TextStructureNavigatorProvider.cs @@ -26,8 +26,8 @@ internal class TextStructureNavigatorProvider : AbstractTextStructureNavigatorPr public TextStructureNavigatorProvider( ITextStructureNavigatorSelectorService selectorService, IContentTypeRegistryService contentTypeService, - IWaitIndicator waitIndicator) - : base(selectorService, contentTypeService, waitIndicator) + IUIThreadOperationExecutor uIThreadOperationExecutor) + : base(selectorService, contentTypeService, uIThreadOperationExecutor) { } diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf index a07969485d458..08408a67dd409 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Vyhněte se výrazům, které implicitně ignorují hodnotu. Avoid unused value assignments - Avoid unused value assignments + Vyhněte se přiřazení nepoužitých hodnot. @@ -44,12 +44,12 @@ Discard - Discard + Zahodit Elsewhere - Elsewhere + Kdekoli jinde @@ -59,7 +59,7 @@ For built-in types - For built-in types + Pro předdefinované typy @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Ignorovat mezery ve výpisech deklarací Indent block contents - Indent block contents + Odsadit obsah bloku Indent case contents - Indent case contents + Odsadit obsah příkazu case Indent case contents (when block) - Indent case contents (when block) + Odsazovat obsah příkazu case (když je v bloku) Indent case labels - Indent case labels + Odsadit návěští case Indent open and close braces - Indent open and close braces + Odsadit levé a pravé složené závorky Insert space after cast - Insert space after cast + Vložit mezeru po přetypování Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Vložit mezeru po dvojtečce pro základní typ nebo rozhraní v deklaraci typu Insert space after comma - Insert space after comma + Vložit mezeru za čárku Insert space after dot - Insert space after dot + Vložit mezeru za tečku Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Vkládat mezeru po klíčových slovech v příkazech toku řízení Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Vložit mezeru za středníkem v příkazu for Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Vložit mezeru před dvojtečku pro základní typ nebo rozhraní v deklaraci typu Insert space before comma - Insert space before comma + Vložit mezeru před čárku Insert space before dot - Insert space before dot + Vložit mezeru před tečku Insert space before open square bracket - Insert space before open square bracket + Vložit mezeru před levou hranatou závorku Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Vložit mezeru před středník v příkazu for Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Vložit mezeru mezi název metody a její levou kulatou závorkou Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Vložit mezeru mezi název metody a její levou kulatou závorkou Insert space within argument list parentheses - Insert space within argument list parentheses + Vložit mezeru mezi kulaté závorky seznamu argumentů Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Vložit mezeru mezi kulaté závorky prázdného seznamu argumentů Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Vložit mezeru mezi kulaté závorky prázdného seznamu parametrů Insert space within empty square brackets - Insert space within empty square brackets + Vložit mezeru mezi prázdné hranaté závorky Insert space within parameter list parentheses - Insert space within parameter list parentheses + Vložit mezeru mezi kulaté závorky seznamu parametrů Insert space within parentheses of expressions - Insert space within parentheses of expressions + Vložit mezeru mezi kulaté závorky výrazů Insert space within parentheses of type casts - Insert space within parentheses of type casts + Vložit mezeru mezi kulaté závorky přetypování Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Vložit mezery mezi kulaté závorky příkazů řízení toku Insert spaces within square brackets - Insert spaces within square brackets + Vložit mezery mezi hranaté závorky Inside namespace - Inside namespace + Uvnitř namespace Label Indentation - Label Indentation + Odsazení návěští Leave block on single line - Leave block on single line + Nechat blok na jednom řádku Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Nechat příkazy a deklarace členů na stejném řádku @@ -259,177 +259,177 @@ Never - Never + Nikdy Outside namespace - Outside namespace + Vně namespace Place "catch" on new line - Place "catch" on new line + Umístit klíčové slovo catch na nový řádek Place "else" on new line - Place "else" on new line + Umístit klíčové slovo else na nový řádek Place "finally" on new line - Place "finally" on new line + Umístit klíčové slovo finally na nový řádek Place members in anonymous types on new line - Place members in anonymous types on new line + Umístit členy anonymních typů na nový řádek Place members in object initializers on new line - Place members in object initializers on new line + Umístit členy v inicializátorech objektů na nový řádek Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Pro anonymní metody umístit levou složenou závorku na nový řádek Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Pro anonymní typy umístit levou složenou závorku na nový řádek Place open brace on new line for control blocks - Place open brace on new line for control blocks + Pro řídicí bloky umístit levou složenou závorku na nový řádek Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Pro lambda výraz umístit levou složenou závorku na nový řádek Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Pro metody a lokální funkce umístit levou složenou závorku na nový řádek Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Pro objekty, kolekce, pole a inicializátory with umístit levou složenou závorku na nový řádek Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Vloží levou složenou závorku na nový řádek pro vlastnosti, indexery a události. Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Vloží levou složenou závorku na nový řádek pro přistupující objekty vlastností, indexerů a událostí. Place open brace on new line for types - Place open brace on new line for types + Pro typy umístit levou složenou závorku na nový řádek Place query expression clauses on new line - Place query expression clauses on new line + Umístit klauzule dotazového výrazu na nový řádek Prefer conditional delegate call - Prefer conditional delegate call + Preferovat podmíněné delegované volání Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Upřednostňovat dekonstruovanou deklaraci proměnných Prefer explicit type - Prefer explicit type + Preferovat explicitní typ Prefer index operator - Prefer index operator + Preferovat operátor indexu Prefer inlined variable declaration - Prefer inlined variable declaration + Upřednostňovat vloženou deklaraci proměnných Prefer local function over anonymous function - Prefer local function over anonymous function + Preferovat lokální funkci před anonymní funkcí Prefer pattern matching - Prefer pattern matching + Upřednostňovat porovnávání vzorů Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Preferovat porovnávání vzorů přes prvek as s kontrolou hodnot null Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Preferovat porovnávání vzorů přes prvek is s kontrolou hodnot cast Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Upřednostňovat porovnávání vzorů nad kontrolou kombinovaných typů Prefer range operator - Prefer range operator + Preferovat operátor rozsahu Prefer simple 'default' expression - Prefer simple 'default' expression + Upřednostňovat jednoduchý výraz default Prefer simple 'using' statement - Prefer simple 'using' statement + Preferovat jednoduchý příkaz using Prefer static local functions - Prefer static local functions + Preferovat statické místní funkce Prefer switch expression - Prefer switch expression + Preferovat výraz switch Prefer throw-expression - Prefer throw-expression + Preferovat výraz throw Prefer 'var' - Prefer 'var' + Preferovat „var“ Preferred 'using' directive placement - Preferred 'using' directive placement + Preferované umístění direktivy using @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Nastavit mezery pro operátory @@ -464,47 +464,47 @@ Unused local - Unused local + Nepoužitá místní Use expression body for accessors - Use expression body for accessors + Pro přístupové objekty používat text výrazu Use expression body for constructors - Use expression body for constructors + Pro konstruktory používat text výrazu Use expression body for indexers - Use expression body for indexers + Pro indexery používat text výrazu Use expression body for lambdas - Use expression body for lambdas + Pro výrazy lambda používat text výrazu Use expression body for local functions - Use expression body for local functions + Pro místní funkce používat text výrazu Use expression body for methods - Use expression body for methods + Pro metody používat text výrazu Use expression body for operators - Use expression body for operators + Pro operátory používat text výrazu Use expression body for properties - Use expression body for properties + Pro vlastnosti používat text výrazu @@ -514,17 +514,17 @@ When on single line - When on single line + Pokud je na jednom řádku When possible - When possible + Pokud je to možné When variable type is apparent - When variable type is apparent + Když je typ proměnné zjevný diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf index f83defa306236..f602885cf3ed3 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Ausdrucksanweisungen vermeiden, die implizit Werte ignorieren Avoid unused value assignments - Avoid unused value assignments + Zuweisungen nicht verwendeter Werte vermeiden @@ -44,12 +44,12 @@ Discard - Discard + Verwerfen Elsewhere - Elsewhere + Anderswo @@ -59,7 +59,7 @@ For built-in types - For built-in types + Für integrierte Typen @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Leerzeichen um Deklarationsanweisungen ignorieren Indent block contents - Indent block contents + Blockinhalte einziehen Indent case contents - Indent case contents + case-Inhalte einziehen Indent case contents (when block) - Indent case contents (when block) + Case-Inhalte einziehen (bei Block) Indent case labels - Indent case labels + case-Bezeichnungen einziehen Indent open and close braces - Indent open and close braces + Öffnende und schließende geschweifte Klammern einziehen Insert space after cast - Insert space after cast + Leerzeichen nach Umwandlung einfügen Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + In Typdeklarationen Leerzeichen nach Doppelpunkt für Basis oder Schnittstelle einfügen Insert space after comma - Insert space after comma + Leerzeichen nach Komma einfügen Insert space after dot - Insert space after dot + Leerzeichen nach Punkt einfügen Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Leerzeichen nach Schlüsselwörtern in Anweisungen für die Ablaufsteuerung einfügen Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + In for-Anweisung Leerzeichen nach Semikolon einfügen Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + In Typdeklarationen Leerzeichen vor Doppelpunkt für Basis oder Schnittstelle einfügen Insert space before comma - Insert space before comma + Leerzeichen vor Komma einfügen Insert space before dot - Insert space before dot + Leerzeichen vor Punkt einfügen Insert space before open square bracket - Insert space before open square bracket + Leerzeichen vor öffnender eckiger Klammer einfügen Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + In for-Anweisung Leerzeichen vor Semikolon einfügen Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Leerzeichen zwischen Methodenname und öffnender runder Klammer einfügen Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Leerzeichen zwischen Methodenname und öffnender runder Klammer einfügen Insert space within argument list parentheses - Insert space within argument list parentheses + Leerzeichen zwischen runden Klammern um Argumentliste einfügen Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Leerzeichen zwischen runden Klammern um leere Argumentliste einfügen Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Leerzeichen zwischen runden Klammern in leere Parameterliste einfügen Insert space within empty square brackets - Insert space within empty square brackets + Leerzeichen zwischen leeren eckigen Klammern einfügen Insert space within parameter list parentheses - Insert space within parameter list parentheses + Leerzeichen zwischen runden Klammern in Parameterliste einfügen Insert space within parentheses of expressions - Insert space within parentheses of expressions + Leerzeichen zwischen runden Klammern von Ausdrücken einfügen Insert space within parentheses of type casts - Insert space within parentheses of type casts + Leerzeichen zwischen runde Klammern für Typumwandlungen einfügen Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Leerzeichen zwischen den Klammern von Ablaufsteuerungsanweisungen einfügen Insert spaces within square brackets - Insert spaces within square brackets + Leerzeichen zwischen eckigen Klammern einfügen Inside namespace - Inside namespace + Innerhalb des Namespaces Label Indentation - Label Indentation + Bezeichnungseinzug Leave block on single line - Leave block on single line + Block auf einzelner Zeile belassen Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Anweisungen und Memberdeklarationen auf der gleichen Zeile belassen @@ -259,177 +259,177 @@ Never - Never + Nie Outside namespace - Outside namespace + Außerhalb des Namespaces Place "catch" on new line - Place "catch" on new line + catch-Anweisung in neue Zeile setzen Place "else" on new line - Place "else" on new line + else-Anweisung in neue Zeile setzen Place "finally" on new line - Place "finally" on new line + finally-Anweisung in neue Zeile setzen Place members in anonymous types on new line - Place members in anonymous types on new line + Member in anonymen Typen in neue Zeile setzen Place members in object initializers on new line - Place members in object initializers on new line + Elemente in Objektinitialisierern in neue Zeile setzen Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Öffnende geschweifte Klammer für anonyme Methoden in neuer Zeile platzieren Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Öffnende geschweifte Klammer für anonyme Typen in neuer Zeile platzieren Place open brace on new line for control blocks - Place open brace on new line for control blocks + Öffnende geschweifte Klammer für Kontrollblöcke in neuer Zeile platzieren Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Öffnende geschweifte Klammer für Lambdaausdruck in neue Zeile setzen Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Öffnende geschweifte Klammer in neuer Zeile für Methoden und lokale Funktionen einfügen Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Öffnende geschweifte Klammer für Objekt-, Sammlungs-, Array- und with-Initialisierer in neue Zeile einfügen Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Fügt eine öffnende geschweifte Klammer für Eigenschaften, Indexer und Ereignisse in einer neuen Zeile ein. Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Fügt eine öffnende geschweifte Klammer für eine Eigenschaft, den Indexer und Ereignisaccessor in einer neuen Zeile ein. Place open brace on new line for types - Place open brace on new line for types + Öffnende geschweifte Klammer für Typen in neuer Zeile platzieren Place query expression clauses on new line - Place query expression clauses on new line + Klauseln von Abfrageausdrücken in neuer Zeile platzieren Prefer conditional delegate call - Prefer conditional delegate call + Bedingten Delegataufruf bevorzugen Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Dekonstruierte Variablendeklaration vorziehen Prefer explicit type - Prefer explicit type + Expliziten Typ bevorzugen Prefer index operator - Prefer index operator + Indexoperator bevorzugen Prefer inlined variable declaration - Prefer inlined variable declaration + Inlinevariablendeklaration vorziehen Prefer local function over anonymous function - Prefer local function over anonymous function + Lokale Funktion gegenüber anonymer Funktion bevorzugen Prefer pattern matching - Prefer pattern matching + Musterabgleich bevorzugen Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Bei der NULL-Überprüfung Musterabgleich gegenüber "as" bevorzugen Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Bei der cast-Überprüfung Musterabgleich gegenüber "is" bevorzugen Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Musterabgleich gegenüber der Überprüfung gemischter Typen bevorzugen Prefer range operator - Prefer range operator + Bereichsoperator bevorzugen Prefer simple 'default' expression - Prefer simple 'default' expression + Einfachen "default"-Ausdruck bevorzugen Prefer simple 'using' statement - Prefer simple 'using' statement + Einfache using-Anweisung bevorzugen Prefer static local functions - Prefer static local functions + Statische lokale Funktionen bevorzugen Prefer switch expression - Prefer switch expression + Switch-Ausdruck bevorzugen Prefer throw-expression - Prefer throw-expression + throw-Ausdruck bevorzugen Prefer 'var' - Prefer 'var' + "var" bevorzugen Preferred 'using' directive placement - Preferred 'using' directive placement + Bevorzugte Platzierung der using-Anweisung @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Abstände für Operatoren festlegen @@ -464,47 +464,47 @@ Unused local - Unused local + Nicht verwendete lokale Variable Use expression body for accessors - Use expression body for accessors + Ausdruckskörper für Accessoren verwenden Use expression body for constructors - Use expression body for constructors + Ausdruckskörper für Konstruktoren verwenden Use expression body for indexers - Use expression body for indexers + Ausdruckskörper für Indexer verwenden Use expression body for lambdas - Use expression body for lambdas + Ausdruckskörper für Lambdaausdrücke verwenden Use expression body for local functions - Use expression body for local functions + Ausdruckstext für lokale Funktionen verwenden Use expression body for methods - Use expression body for methods + Ausdruckskörper für Methoden verwenden Use expression body for operators - Use expression body for operators + Ausdruckskörper für Operatoren verwenden Use expression body for properties - Use expression body for properties + Ausdruckskörper für Eigenschaften verwenden @@ -514,17 +514,17 @@ When on single line - When on single line + In einzelner Zeile When possible - When possible + Wenn möglich When variable type is apparent - When variable type is apparent + Wenn der Variablentyp offensichtlich ist diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf index ca9a0cd38dd0d..cf02c7fb87f3d 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Evitar instrucciones de expresión que omiten implícitamente el valor Avoid unused value assignments - Avoid unused value assignments + Evitar asignaciones de valores sin usar @@ -44,12 +44,12 @@ Discard - Discard + Descartar Elsewhere - Elsewhere + En otro lugar @@ -59,7 +59,7 @@ For built-in types - For built-in types + Para tipos integrados @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Omitir espacios en instrucciones de declaración Indent block contents - Indent block contents + Aplicar sangría al contenido del bloque Indent case contents - Indent case contents + Aplicar sangría al contenido de case Indent case contents (when block) - Indent case contents (when block) + Aplicar sangría al contenido de case (cuando esté bloqueado) Indent case labels - Indent case labels + Aplicar sangría a etiquetas case Indent open and close braces - Indent open and close braces + Aplicar sangría a llaves de apertura y cierre Insert space after cast - Insert space after cast + Insertar espacio tras conversión Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Insertar espacio tras dos puntos para base o interfaz en una declaración de tipo Insert space after comma - Insert space after comma + Insertar espacio tras coma Insert space after dot - Insert space after dot + Insertar espacio tras punto Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Insertar espacio después de las palabras clave en instrucciones de flujo de control Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Insertar espacio tras punto y coma en instrucción "for" Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Insertar espacio delante de dos puntos para base o interfaz en una declaración de tipo Insert space before comma - Insert space before comma + Insertar espacio delante de coma Insert space before dot - Insert space before dot + Insertar espacio delante de punto Insert space before open square bracket - Insert space before open square bracket + Insertar espacio delante de corchete de apertura Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Insertar espacio delante de punto y coma en instrucciones "for" Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Insertar espacio entre el nombre del método y el paréntesis de apertura Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Insertar espacio entre el nombre del método y el paréntesis de apertura Insert space within argument list parentheses - Insert space within argument list parentheses + Insertar espacio entre paréntesis de la lista de argumentos Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Insertar espacio entre paréntesis vacíos de la lista de argumentos Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Insertar espacio entre paréntesis vacíos de la lista de parámetros Insert space within empty square brackets - Insert space within empty square brackets + Insertar espacio entre corchetes vacíos Insert space within parameter list parentheses - Insert space within parameter list parentheses + Insertar espacio entre paréntesis de la lista de parámetros Insert space within parentheses of expressions - Insert space within parentheses of expressions + Insertar espacio entre paréntesis de expresiones Insert space within parentheses of type casts - Insert space within parentheses of type casts + Insertar espacio entre paréntesis de conversiones de tipo Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Insertar espacios entre paréntesis de instrucciones de flujo de control Insert spaces within square brackets - Insert spaces within square brackets + Insertar espacios entre corchetes Inside namespace - Inside namespace + namespace interior Label Indentation - Label Indentation + Sangría de etiquetas Leave block on single line - Leave block on single line + Mantener bloque en una sola línea Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Mantener instrucciones y declaraciones de miembros en la misma línea @@ -259,177 +259,177 @@ Never - Never + Nunca Outside namespace - Outside namespace + namespace exterior Place "catch" on new line - Place "catch" on new line + Colocar "catch" en nueva línea Place "else" on new line - Place "else" on new line + Colocar "else" en nueva línea Place "finally" on new line - Place "finally" on new line + Colocar "finally" en nueva línea Place members in anonymous types on new line - Place members in anonymous types on new line + Colocar miembros en tipos anónimos en nueva línea Place members in object initializers on new line - Place members in object initializers on new line + Colocar miembros de inicializadores de objetos en nueva línea Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Colocar llave de apertura para métodos anónimos en nueva línea Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Colocar llave de apertura para tipos anónimos en nueva línea Place open brace on new line for control blocks - Place open brace on new line for control blocks + Colocar llave de apertura para bloques de control en nueva línea Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Colocar llave de apertura para expresión lambda en nueva línea Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Poner la llave de apertura en una línea nueva para los métodos y las funciones locales Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Colocar llave de apertura en la nueva línea para los inicializadores de objeto, colección, matriz y with Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Colocar llave de apertura para propiedades, indizadores y eventos en nueva línea Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Colocar llave de apertura para descriptores de acceso de eventos, indizadores y propiedades en nueva línea Place open brace on new line for types - Place open brace on new line for types + Colocar llave de apertura para tipos en nueva línea Place query expression clauses on new line - Place query expression clauses on new line + Colocar cláusulas de expresiones de consulta en nueva línea Prefer conditional delegate call - Prefer conditional delegate call + Preferir llamada de delegado condicional Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Preferir declaración de variable desconstruida Prefer explicit type - Prefer explicit type + Preferir tipo explícito Prefer index operator - Prefer index operator + Preferir operador de índice Prefer inlined variable declaration - Prefer inlined variable declaration + Preferir declaración de variable insertada Prefer local function over anonymous function - Prefer local function over anonymous function + Preferir una función local frente a una función anónima Prefer pattern matching - Prefer pattern matching + Preferir coincidencia de patrones Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Preferir coincidencia de patrones en lugar de "as" con comprobación de "null" Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Preferir coincidencia de patrones en lugar de "is" con comprobación de "cast" Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Anteponer coincidencia de patrón a la comprobación de tipo mixto Prefer range operator - Prefer range operator + Preferir operador de intervalo Prefer simple 'default' expression - Prefer simple 'default' expression + Preferir la expresión simple "predeterminada" Prefer simple 'using' statement - Prefer simple 'using' statement + Preferir la instrucción "using" sencilla Prefer static local functions - Prefer static local functions + Preferir funciones locales estáticas Prefer switch expression - Prefer switch expression + Preferir expresión switch Prefer throw-expression - Prefer throw-expression + Preferir expresión throw Prefer 'var' - Prefer 'var' + Preferir 'var' Preferred 'using' directive placement - Preferred 'using' directive placement + Selección de ubicación de directiva "using" preferida @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Establecer espaciado para operadores @@ -464,47 +464,47 @@ Unused local - Unused local + Local sin uso Use expression body for accessors - Use expression body for accessors + Usar cuerpo de expresiones para los descriptores de acceso Use expression body for constructors - Use expression body for constructors + Usar cuerpo de expresiones para los constructores Use expression body for indexers - Use expression body for indexers + Usar cuerpo de expresiones para los indizadores Use expression body for lambdas - Use expression body for lambdas + Usar cuerpo de expresión para lambdas Use expression body for local functions - Use expression body for local functions + Usar el cuerpo de la expresión para las funciones locales Use expression body for methods - Use expression body for methods + Usar cuerpo de expresiones para los métodos Use expression body for operators - Use expression body for operators + Usar cuerpo de expresiones para los operadores Use expression body for properties - Use expression body for properties + Usar cuerpo de expresiones para las propiedades @@ -514,17 +514,17 @@ When on single line - When on single line + Cuando esté en una sola línea When possible - When possible + Cuando sea posible When variable type is apparent - When variable type is apparent + Cuando el tipo de variable es aparente diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf index 71feeb87ccb24..e407bf2c439e1 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Éviter les instructions d'expression qui ignorent implicitement la valeur Avoid unused value assignments - Avoid unused value assignments + Éviter les assignations de valeurs inutilisées @@ -44,12 +44,12 @@ Discard - Discard + Discard Elsewhere - Elsewhere + Ailleurs @@ -59,7 +59,7 @@ For built-in types - For built-in types + Pour les types intégrés @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Ignorer les espaces dans les instructions de déclaration Indent block contents - Indent block contents + Mettre en retrait le contenu d'un bloc Indent case contents - Indent case contents + Mettre en retrait le contenu de case Indent case contents (when block) - Indent case contents (when block) + Mettre en retrait le contenu de case (dans un bloc) Indent case labels - Indent case labels + Mettre en retrait des étiquettes case Indent open and close braces - Indent open and close braces + Mettre en retrait les accolades ouvrantes et fermantes Insert space after cast - Insert space after cast + Insérer un espace après le cast Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Insérer un espace après le signe deux-points pour base ou interface dans une déclaration de type Insert space after comma - Insert space after comma + Insérer un espace après la virgule Insert space after dot - Insert space after dot + Insérer un espace après le point Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Insérer un espace après les mots clés dans les instructions de flux de contrôle Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Insérer un espace après le point-virgule dans une instruction "for" Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Insérer un espace avant le signe deux-points pour base ou interface dans une déclaration de type Insert space before comma - Insert space before comma + Insérer un espace avant la virgule Insert space before dot - Insert space before dot + Insérer un espace avant le point Insert space before open square bracket - Insert space before open square bracket + Insérer un espace avant un crochet ouvrant Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Insérer un espace avant le point-virgule dans une instruction "for" Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Insérer un espace entre le nom de la méthode et sa parenthèse ouvrante Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Insérer un espace entre le nom de la méthode et sa parenthèse ouvrante Insert space within argument list parentheses - Insert space within argument list parentheses + Insérer un espace dans les parenthèses de la liste d'arguments Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Insérer un espace dans les parenthèses de la liste d'arguments vide Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Insérer un espace dans les parenthèses vides de la liste de paramètres Insert space within empty square brackets - Insert space within empty square brackets + Insérer un espace dans des crochets vides Insert space within parameter list parentheses - Insert space within parameter list parentheses + Insérer un espace dans les parenthèses de la liste de paramètres Insert space within parentheses of expressions - Insert space within parentheses of expressions + Insérer un espace dans les parenthèses d'expressions Insert space within parentheses of type casts - Insert space within parentheses of type casts + Insérer un espace dans les parenthèses de casts de type Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Insérer des espaces à l'intérieur des parenthèses des instructions de flux de contrôle Insert spaces within square brackets - Insert spaces within square brackets + Insérer des espaces dans des crochets Inside namespace - Inside namespace + Dans le namespace Label Indentation - Label Indentation + Mise en retrait d'étiquette Leave block on single line - Leave block on single line + Laisser un bloc sur une seule ligne Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Laisser les instructions et les déclarations de membre sur la même ligne @@ -259,177 +259,177 @@ Never - Never + Jamais Outside namespace - Outside namespace + Hors du namespace Place "catch" on new line - Place "catch" on new line + Placer "catch" sur une nouvelle ligne Place "else" on new line - Place "else" on new line + Placer "else" sur une nouvelle ligne Place "finally" on new line - Place "finally" on new line + Placer "finally" sur une nouvelle ligne Place members in anonymous types on new line - Place members in anonymous types on new line + Placer les membres dans les types anonymes sur une nouvelle ligne Place members in object initializers on new line - Place members in object initializers on new line + Placer les membres dans les initialiseurs d'objets sur une nouvelle ligne Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Placer une accolade ouvrante sur une nouvelle ligne pour les méthodes anonymes Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Placer une accolade ouvrante sur une nouvelle ligne pour des types anonymes Place open brace on new line for control blocks - Place open brace on new line for control blocks + Placer l'accolade ouvrante sur une nouvelle ligne pour les blocs de contrôle Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Placer une accolade ouvrante sur une nouvelle ligne pour une expression lambda Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Placer une accolade ouvrante sur une nouvelle ligne pour les méthodes et les fonctions locales Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Placer une accolade ouvrante sur une nouvelle ligne pour les initialiseurs d'objets, de collections, de tableaux et with Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Placer une accolade ouvrante sur une nouvelle ligne pour les propriétés, indexeurs et événements Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Placer une accolade ouvrante sur une nouvelle ligne pour les accesseurs de propriété, d'indexeur et d'événement Place open brace on new line for types - Place open brace on new line for types + Placer une accolade ouvrante sur une nouvelle ligne pour les types Place query expression clauses on new line - Place query expression clauses on new line + Placer les clauses d'expression de requête sur une nouvelle ligne Prefer conditional delegate call - Prefer conditional delegate call + Préférer l'appel de délégué conditionnel Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Préférer la déclaration de variable déconstruite Prefer explicit type - Prefer explicit type + Préférer un type explicite Prefer index operator - Prefer index operator + Préférer l'opérateur d'index Prefer inlined variable declaration - Prefer inlined variable declaration + Préférer la déclaration de variable inlined Prefer local function over anonymous function - Prefer local function over anonymous function + Préférer une fonction locale à une fonction anonyme Prefer pattern matching - Prefer pattern matching + Préférer les critères spéciaux Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Préférer les critères spéciaux à 'as' avec le contrôle de 'null' Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Préférer les critères spéciaux à 'is' avec le contrôle de 'cast' Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Préférer les critères spéciaux à la vérification de type mixte Prefer range operator - Prefer range operator + Préférer l'opérateur de plage Prefer simple 'default' expression - Prefer simple 'default' expression + Préférer l'expression 'default' simple Prefer simple 'using' statement - Prefer simple 'using' statement + Préférer une instruction 'using' simple Prefer static local functions - Prefer static local functions + Préférer les fonctions locales statiques Prefer switch expression - Prefer switch expression + Préférer l'expression switch Prefer throw-expression - Prefer throw-expression + Préférer l'expression throw Prefer 'var' - Prefer 'var' + Préférer 'var' Preferred 'using' directive placement - Preferred 'using' directive placement + Emplacement par défaut de la directive 'using' @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Définir l'espacement des opérateurs @@ -464,47 +464,47 @@ Unused local - Unused local + Local inutilisé Use expression body for accessors - Use expression body for accessors + Utiliser un corps d'expression pour les accesseurs Use expression body for constructors - Use expression body for constructors + Utiliser un corps d'expression pour les constructeurs Use expression body for indexers - Use expression body for indexers + Utiliser un corps d'expression pour les indexeurs Use expression body for lambdas - Use expression body for lambdas + Utiliser un corps d'expression pour les expressions lambda Use expression body for local functions - Use expression body for local functions + Utiliser le corps d'expression pour des fonctions locales Use expression body for methods - Use expression body for methods + Utiliser un corps d'expression pour les méthodes Use expression body for operators - Use expression body for operators + Utiliser un corps d'expression pour les opérateurs Use expression body for properties - Use expression body for properties + Utiliser un corps d'expression pour les propriétés @@ -514,17 +514,17 @@ When on single line - When on single line + Sur une seule ligne When possible - When possible + Quand cela est possible When variable type is apparent - When variable type is apparent + Quand le type de variable est apparent diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf index 21c83449bca2e..410040f6b23a0 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Evita le istruzioni di espressione che ignorano il valore in modo implicito Avoid unused value assignments - Avoid unused value assignments + Evita le assegnazioni di valori inutilizzati @@ -44,12 +44,12 @@ Discard - Discard + Scarta Elsewhere - Elsewhere + Altrove @@ -59,7 +59,7 @@ For built-in types - For built-in types + Per tipi predefiniti @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Ignora gli spazi nelle istruzioni di dichiarazione Indent block contents - Indent block contents + Rientra contenuto blocco Indent case contents - Indent case contents + Rientra contenuto case Indent case contents (when block) - Indent case contents (when block) + Imposta rientro per contenuto case (quando è un blocco) Indent case labels - Indent case labels + Rientra etichette case Indent open and close braces - Indent open and close braces + Rientra parentesi graffe di apertura e chiusura Insert space after cast - Insert space after cast + Inserisci spazio dopo il cast Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Inserisci spazio dopo i due punti per base o interfaccia nella dichiarazione di tipo Insert space after comma - Insert space after comma + Inserisci spazio dopo la virgola Insert space after dot - Insert space after dot + Inserisci spazio dopo il punto Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Inserisci spazio dopo le parole chiave nelle istruzioni del flusso di controllo Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Inserisci spazio dopo il punto e virgola nell'istruzione "for" Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Inserisci spazio prima dei due punti per base o interfaccia nella dichiarazione di tipo Insert space before comma - Insert space before comma + Inserisci spazio prima della virgola Insert space before dot - Insert space before dot + Inserisci spazio prima del punto Insert space before open square bracket - Insert space before open square bracket + Inserisci spazio prima della parentesi quadra di apertura Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Inserisci spazio prima del punto e virgola nell'istruzione "for" Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Inserisci spazio tra il nome del metodo e la parentesi di apertura corrispondente Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Inserisci spazio tra il nome del metodo e la parentesi di apertura corrispondente Insert space within argument list parentheses - Insert space within argument list parentheses + Inserisci spazio tra le parentesi dell'elenco di argomenti Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Inserisci spazio tra le parentesi dell'elenco di argomenti vuoto Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Inserisci spazio tra le parentesi dell'elenco di parametri vuoto Insert space within empty square brackets - Insert space within empty square brackets + Inserisci spazio tra parentesi quadre vuote Insert space within parameter list parentheses - Insert space within parameter list parentheses + Inserisci spazio tra le parentesi dell'elenco di parametri Insert space within parentheses of expressions - Insert space within parentheses of expressions + Inserisci spazio tra le parentesi delle espressioni Insert space within parentheses of type casts - Insert space within parentheses of type casts + Inserisci spazio tra le parentesi dei cast di tipo Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Inserisci spazi all'interno delle parentesi delle istruzioni del flusso di controllo Insert spaces within square brackets - Insert spaces within square brackets + Inserisci spazi tra parentesi quadre Inside namespace - Inside namespace + All'interno di namespace Label Indentation - Label Indentation + Rientro etichetta Leave block on single line - Leave block on single line + Lascia blocco su una sola riga Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Lascia istruzioni e dichiarazioni di membri sulla stessa riga @@ -259,177 +259,177 @@ Never - Never + Mai Outside namespace - Outside namespace + All'esterno di namespace Place "catch" on new line - Place "catch" on new line + Inserisci "catch" in una nuova riga Place "else" on new line - Place "else" on new line + Inserisci "else" in una nuova riga Place "finally" on new line - Place "finally" on new line + Inserisci "finally" in una nuova riga Place members in anonymous types on new line - Place members in anonymous types on new line + Inserisci membri di tipi anonimi in una nuova riga Place members in object initializers on new line - Place members in object initializers on new line + Inserisci membri di inizializzatori di oggetto in una nuova riga Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Inserisci parentesi graffa di apertura in una nuova riga per i metodi anonimi Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Inserisci parentesi graffa di apertura in una nuova riga per i tipi anonimi Place open brace on new line for control blocks - Place open brace on new line for control blocks + Inserisci parentesi graffa di apertura su nuova riga per i blocchi di controllo Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Inserisci parentesi graffa di apertura in una nuova riga per espressioni lambda Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Inserisci parentesi graffa di apertura su nuova riga per metodi e funzioni locali Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Inserisci parentesi graffa di apertura in una nuova riga per oggetto, raccolta, matrice e inizializzatori with Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Inserisci parentesi graffa di apertura in una nuova riga per proprietà, indicizzatori ed eventi Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Inserisci parentesi graffa di apertura in una nuova riga per funzioni di accesso a proprietà, indicizzatori ed eventi Place open brace on new line for types - Place open brace on new line for types + Inserisci parentesi graffa di apertura in una nuova riga per i tipi Place query expression clauses on new line - Place query expression clauses on new line + Inserisci clausole di espressione di query in una nuova riga Prefer conditional delegate call - Prefer conditional delegate call + Preferisci la chiamata al delegato condizionale Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Preferisci dichiarazione di variabile decostruita Prefer explicit type - Prefer explicit type + Preferisci tipo esplicito Prefer index operator - Prefer index operator + Preferisci operatore di indice Prefer inlined variable declaration - Prefer inlined variable declaration + Preferisci dichiarazione di variabile inline Prefer local function over anonymous function - Prefer local function over anonymous function + Preferisci la funzione locale a quella anonima Prefer pattern matching - Prefer pattern matching + Preferisci i criteri di ricerca Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Preferisci criteri di ricerca a 'as' con controllo 'null' Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Preferisci criteri di ricerca a 'is' con controllo 'cast' Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Preferisci criteri di ricerca al controllo dei tipi misti Prefer range operator - Prefer range operator + Preferisci operatore di intervallo Prefer simple 'default' expression - Prefer simple 'default' expression + Preferisci l'espressione 'default' semplice Prefer simple 'using' statement - Prefer simple 'using' statement + Preferisci l'istruzione 'using' semplice Prefer static local functions - Prefer static local functions + Preferisci funzioni locali statiche Prefer switch expression - Prefer switch expression + Preferisci espressione switch Prefer throw-expression - Prefer throw-expression + Preferisci l'espressione throw Prefer 'var' - Prefer 'var' + Preferisci 'var' Preferred 'using' directive placement - Preferred 'using' directive placement + Posizione preferita della direttiva 'using' @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Imposta spaziatura per operatori @@ -464,47 +464,47 @@ Unused local - Unused local + Variabile locale inutilizzata Use expression body for accessors - Use expression body for accessors + Usa il corpo dell'espressione per le funzioni di accesso Use expression body for constructors - Use expression body for constructors + Usa il corpo dell'espressione per i costruttori Use expression body for indexers - Use expression body for indexers + Usa il corpo dell'espressione per gli indicizzatori Use expression body for lambdas - Use expression body for lambdas + Usa il corpo dell'espressione per le espressioni lambda Use expression body for local functions - Use expression body for local functions + Usa corpo dell'espressione per funzioni locali Use expression body for methods - Use expression body for methods + Usa il corpo dell'espressione per i metodi Use expression body for operators - Use expression body for operators + Usa il corpo dell'espressione per gli operatori Use expression body for properties - Use expression body for properties + Usa il corpo dell'espressione per le proprietà @@ -514,17 +514,17 @@ When on single line - When on single line + Se su riga singola When possible - When possible + Quando possibile When variable type is apparent - When variable type is apparent + Quando il tipo della variabile è apparente diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf index a2619effa7f17..2276f9274aa04 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + 値を暗黙的に無視する式ステートメントを指定しないでください Avoid unused value assignments - Avoid unused value assignments + 使用されない値の代入を指定しないでください @@ -44,12 +44,12 @@ Discard - Discard + 破棄 Elsewhere - Elsewhere + その他の場所 @@ -59,7 +59,7 @@ For built-in types - For built-in types + ビルトイン型の場合 @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + 宣言ステートメント内のスペースを無視する Indent block contents - Indent block contents + ブロックの内容をインデントする Indent case contents - Indent case contents + case の内容をインデントする Indent case contents (when block) - Indent case contents (when block) + case の内容をインデントします (ブロックする場合) Indent case labels - Indent case labels + case ラベルをインデントする Indent open and close braces - Indent open and close braces + 始めと終わりのかっこをインデントする Insert space after cast - Insert space after cast + キャストの後にスペースを挿入する Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + 型宣言で、基本またはインターフェイス用のコロンの後にスペースを配置する Insert space after comma - Insert space after comma + コンマの後にスペースを追加する Insert space after dot - Insert space after dot + ピリオドの後にスペースを追加する Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + 制御フロー ステートメント内のキーワードの後にスペースを挿入する Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + "for" ステートメントでセミコロンの後にスペースを挿入する Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + 型宣言で、基本またはインターフェイス用のコロンの前にスペースを挿入する Insert space before comma - Insert space before comma + コンマの前にスペースを挿入する Insert space before dot - Insert space before dot + ピリオドの前にスペースを挿入する Insert space before open square bracket - Insert space before open square bracket + 始め角かっこの前にスペースを挿入する Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + "for" ステートメントの前にスペースを挿入する Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + メソッド名と始めかっこの間にスペースを挿入する Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + メソッド名と始めかっこの間にスペースを挿入する Insert space within argument list parentheses - Insert space within argument list parentheses + 引数リストのかっこ内にスペースを挿入する Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + 空の引数リストのかっこ内にスペースを挿入する Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + 空のパラメーター リストのかっこ内にスペースを挿入する Insert space within empty square brackets - Insert space within empty square brackets + 空の角かっこ内にスペースを挿入する Insert space within parameter list parentheses - Insert space within parameter list parentheses + パラメーター リストのかっこ内にスペースを挿入する Insert space within parentheses of expressions - Insert space within parentheses of expressions + 式のかっこ内にスペースを挿入する Insert space within parentheses of type casts - Insert space within parentheses of type casts + 型キャストのかっこ内にスペースを挿入する Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + 制御フロー ステートメントのかっこ内にスペースを挿入する Insert spaces within square brackets - Insert spaces within square brackets + 角かっこ内にスペースを挿入する Inside namespace - Inside namespace + namespace 内 Label Indentation - Label Indentation + ラベル インデント Leave block on single line - Leave block on single line + ブロックを単一行に配置する Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + 1 行に複数のステートメントとメンバー宣言を表示する @@ -259,177 +259,177 @@ Never - Never + 行わない Outside namespace - Outside namespace + namespace 外 Place "catch" on new line - Place "catch" on new line + 新しい行に "catch" を配置する Place "else" on new line - Place "else" on new line + 新しい行に "else" を配置する Place "finally" on new line - Place "finally" on new line + 新しい行に "finally" を配置する Place members in anonymous types on new line - Place members in anonymous types on new line + 新しい行に匿名型のメンバーを配置する Place members in object initializers on new line - Place members in object initializers on new line + 新しい行にオブジェクト初期化子のメンバーを配置する Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + 新しい行に匿名メソッドの始めかっこを配置する Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + 新しい行に匿名型の始めかっこを配置する Place open brace on new line for control blocks - Place open brace on new line for control blocks + 新しい行にコントロール ブロック用の始めかっこを配置する Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + 新しい行にラムダ式の始めかっこを配置する Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + 新しい行にメソッドとローカル関数の始めかっこを配置する Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 新しい行にオブジェクト、コレクション、配列、with 初期化子用の始めかっこを配置する Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + プロパティ、インデクサー、イベントの新しい行に始めかっこを配置します Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + プロパティ、インデクサー、イベント アクセサーの新しい行に始めかっこを配置します Place open brace on new line for types - Place open brace on new line for types + 新しい行に型の始めかっこを配置する Place query expression clauses on new line - Place query expression clauses on new line + 新しい行にクエリ式の句を配置する Prefer conditional delegate call - Prefer conditional delegate call + 条件付きの代理呼び出しを優先する Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + 分解された変数宣言を優先する Prefer explicit type - Prefer explicit type + 明示的な型を優先してください Prefer index operator - Prefer index operator + インデックス演算子を優先 Prefer inlined variable declaration - Prefer inlined variable declaration + インライン変数宣言を優先する Prefer local function over anonymous function - Prefer local function over anonymous function + 匿名関数よりローカル関数を優先します Prefer pattern matching - Prefer pattern matching + パターン マッチングを優先する Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + as' を 'null' チェックで使用するよりもパターン マッチングを優先します Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + is' を 'cast' チェックで使用するよりもパターン マッチングを優先します Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + 混合型チェックよりパターン マッチングを優先する Prefer range operator - Prefer range operator + 範囲演算子を優先 Prefer simple 'default' expression - Prefer simple 'default' expression + 単純な 'default' 式を優先する Prefer simple 'using' statement - Prefer simple 'using' statement + 単純な 'using' ステートメントを優先する Prefer static local functions - Prefer static local functions + 静的ローカル関数を優先する Prefer switch expression - Prefer switch expression + switch 式を優先する Prefer throw-expression - Prefer throw-expression + スロー式を優先する Prefer 'var' - Prefer 'var' + var' を優先してください Preferred 'using' directive placement - Preferred 'using' directive placement + 優先する 'using' ディレクティブの配置 @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + 演算子のスペースを設定する @@ -464,47 +464,47 @@ Unused local - Unused local + 未使用のローカル Use expression body for accessors - Use expression body for accessors + アクセサーに式本体を使用する Use expression body for constructors - Use expression body for constructors + コンストラクターに式本体を使用する Use expression body for indexers - Use expression body for indexers + インデクサーに式本体を使用する Use expression body for lambdas - Use expression body for lambdas + ラムダに式本体を使用します Use expression body for local functions - Use expression body for local functions + ローカル関数に式本体を使用します Use expression body for methods - Use expression body for methods + メソッドに式本体を使用する Use expression body for operators - Use expression body for operators + オペレーターに式本体を使用する Use expression body for properties - Use expression body for properties + プロパティに式本体を使用する @@ -514,17 +514,17 @@ When on single line - When on single line + 1 行の場合 When possible - When possible + 可能な場合 When variable type is apparent - When variable type is apparent + 変数の型が明らかな場合 diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf index b5f3de05a1c71..44dd19bfe303e 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + 암시적으로 값을 무시하는 식 문을 사용하지 마세요. Avoid unused value assignments - Avoid unused value assignments + 사용되지 않는 값 할당을 사용하지 마세요. @@ -44,12 +44,12 @@ Discard - Discard + 폐기 Elsewhere - Elsewhere + 다른 곳 @@ -59,7 +59,7 @@ For built-in types - For built-in types + 기본 제공 형식인 경우 @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + 선언문의 공백 무시 Indent block contents - Indent block contents + 블록 내용 들여쓰기 Indent case contents - Indent case contents + case 내용 들여쓰기 Indent case contents (when block) - Indent case contents (when block) + case 내용 들여쓰기(블록) Indent case labels - Indent case labels + case 레이블 들여쓰기 Indent open and close braces - Indent open and close braces + 여는 중괄호 및 닫는 중괄호 들여쓰기 Insert space after cast - Insert space after cast + 캐스트 뒤에 공백 삽입 Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + 형식 선언의 기본 또는 인터페이스에 대한 콜론 뒤에 공백 삽입 Insert space after comma - Insert space after comma + 쉼표 뒤에 공백 삽입 Insert space after dot - Insert space after dot + 점 뒤에 공백 삽입 Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + 제어 흐름 문의 키워드 뒤에 공백 삽입 Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + "for" 문의 세미콜론 뒤에 공백 삽입 Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + 형식 선언의 기본 또는 인터페이스에 대한 콜론 앞에 공백 삽입 Insert space before comma - Insert space before comma + 쉼표 앞에 공백 삽입 Insert space before dot - Insert space before dot + 점 앞에 공백 삽입 Insert space before open square bracket - Insert space before open square bracket + 여는 대괄호 앞에 공백 삽입 Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + "for" 문의 세미콜론 앞에 공백 삽입 Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + 메서드 이름과 여는 괄호 사이에 공백 삽입 Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + 메서드 이름과 여는 괄호 사이에 공백 삽입 Insert space within argument list parentheses - Insert space within argument list parentheses + 인수 목록 괄호의 내부에 공백 삽입 Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + 빈 인수 목록 괄호 내부에 공백 삽입 Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + 빈 매개 변수 목록 괄호 내에 공백 삽입 Insert space within empty square brackets - Insert space within empty square brackets + 빈 대괄호의 내부에 공백 삽입 Insert space within parameter list parentheses - Insert space within parameter list parentheses + 매개 변수 목록 괄호 내에 공백 삽입 Insert space within parentheses of expressions - Insert space within parentheses of expressions + 식의 괄호 내부에 공백 삽입 Insert space within parentheses of type casts - Insert space within parentheses of type casts + 형식 캐스팅의 괄호 내부에 공백 삽입 Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + 제어 흐름 문의 괄호 내부에 공백 삽입 Insert spaces within square brackets - Insert spaces within square brackets + 대괄호 내부에 공백 삽입 Inside namespace - Inside namespace + namespace 내부 Label Indentation - Label Indentation + 레이블 들여쓰기 Leave block on single line - Leave block on single line + 블록을 한 줄에 유지 Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + 문과 멤버 선언을 같은 줄에 유지 @@ -259,177 +259,177 @@ Never - Never + 안 함 Outside namespace - Outside namespace + 외부 namespace Place "catch" on new line - Place "catch" on new line + "catch"를 새 줄에 배치 Place "else" on new line - Place "else" on new line + "else"를 새 줄에 배치 Place "finally" on new line - Place "finally" on new line + "finally"를 새 줄에 배치 Place members in anonymous types on new line - Place members in anonymous types on new line + 익명 형식의 멤버를 새 줄에 배치 Place members in object initializers on new line - Place members in object initializers on new line + 개체 이니셜라이저의 멤버를 새 줄에 배치 Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + 무명 메서드의 여는 중괄호를 새 줄에 배치 Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + 익명 형식의 여는 중괄호를 새 줄에 배치 Place open brace on new line for control blocks - Place open brace on new line for control blocks + 제어 블록의 여는 중괄호를 새 줄에 배치 Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + 람다 식의 여는 중괄호를 새 줄에 배치 Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + 메서드 및 로컬 함수의 여는 중괄호를 새 줄에 배치합니다. Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 개체, 컬렉션, 배열 및 with 이니셜라이저의 여는 중괄호를 새 줄에 배치 Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + 속성, 인덱서 및 이벤트의 여는 중괄호를 새 줄에 배치합니다. Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + 속성, 인덱서 및 이벤트 접근자의 여는 중괄호를 새 줄에 배치합니다. Place open brace on new line for types - Place open brace on new line for types + 형식의 여는 중괄호를 새 줄에 배치 Place query expression clauses on new line - Place query expression clauses on new line + 쿼리 식의 절을 새 줄에 배치 Prefer conditional delegate call - Prefer conditional delegate call + 조건부 대리자 호출 기본 사용 Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + 분해된 변수 선언 사용 Prefer explicit type - Prefer explicit type + 명시적 형식 기본 사용 Prefer index operator - Prefer index operator + 인덱스 연산자 선호 Prefer inlined variable declaration - Prefer inlined variable declaration + 인라인 변수 선언 사용 Prefer local function over anonymous function - Prefer local function over anonymous function + 익명 함수보다 로컬 함수 선호 Prefer pattern matching - Prefer pattern matching + 패턴 일치 선호 Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + null' 검사에서 'as'보다 패턴 일치 기본 사용 Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + 캐스트' 검사에서 'is'보다 패턴 일치 기본 사용 Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + 혼합 형식 검사 대신 패턴 일치 사용 Prefer range operator - Prefer range operator + 범위 연산자 선호 Prefer simple 'default' expression - Prefer simple 'default' expression + 간단한 'default' 식을 기본으로 사용 Prefer simple 'using' statement - Prefer simple 'using' statement + 간단한 'using' 문 선호 Prefer static local functions - Prefer static local functions + 정적 로컬 함수 선호 Prefer switch expression - Prefer switch expression + switch 식 선호 Prefer throw-expression - Prefer throw-expression + throw 식 기본 사용 Prefer 'var' - Prefer 'var' + var'을 기본으로 사용합니다. Preferred 'using' directive placement - Preferred 'using' directive placement + 선호하는 'using' 지시문 배치 @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + 연산자의 간격을 설정합니다. @@ -464,47 +464,47 @@ Unused local - Unused local + 사용하지 않는 로컬 Use expression body for accessors - Use expression body for accessors + 접근자에 식 본문 사용 Use expression body for constructors - Use expression body for constructors + 생성자에 식 본문 사용 Use expression body for indexers - Use expression body for indexers + 인덱서에 식 본문 사용 Use expression body for lambdas - Use expression body for lambdas + 람다에 식 본문 사용 Use expression body for local functions - Use expression body for local functions + 로컬 함수의 식 본문 사용 Use expression body for methods - Use expression body for methods + 메서드에 식 본문 사용 Use expression body for operators - Use expression body for operators + 연산자에 식 본문 사용 Use expression body for properties - Use expression body for properties + 속성에 식 본문 사용 @@ -514,17 +514,17 @@ When on single line - When on single line + 한 줄에 있는 경우 When possible - When possible + 가능한 경우 When variable type is apparent - When variable type is apparent + 변수 형식이 명백한 경우 diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf index 1dd96fe018a20..3dc3b375fdf0d 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Unikaj instrukcji wyrażeń, które niejawnie ignorują wartość Avoid unused value assignments - Avoid unused value assignments + Unikaj nieużywanych przypisań wartości @@ -44,12 +44,12 @@ Discard - Discard + Odrzuć Elsewhere - Elsewhere + W innych miejscach @@ -59,7 +59,7 @@ For built-in types - For built-in types + Dla typów wbudowanych @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Ignoruj spacje w instrukcjach deklaracji Indent block contents - Indent block contents + Wcięcie zawartości bloku Indent case contents - Indent case contents + Wcięcie zawartości elementu case Indent case contents (when block) - Indent case contents (when block) + Wcięcie zawartości elementu case (w przypadku bloku) Indent case labels - Indent case labels + Wcięcie etykiet elementu case Indent open and close braces - Indent open and close braces + Wcięcie otwierających i zamykających nawiasów klamrowych Insert space after cast - Insert space after cast + Wstaw spację po rzutowaniu Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Wstaw spację po dwukropku dla elementu base lub interface w deklaracji typu Insert space after comma - Insert space after comma + Wstaw spację po przecinku Insert space after dot - Insert space after dot + Wstaw spację po kropce Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Wstaw spację po słowach kluczowych w instrukcjach przepływu sterowania Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Wstaw spację po średniku w instrukcji „for” Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Wstaw spację przed dwukropkiem dla elementu base lub interface w deklaracji typu Insert space before comma - Insert space before comma + Wstaw spację przed przecinkiem Insert space before dot - Insert space before dot + Wstaw spację przed kropką Insert space before open square bracket - Insert space before open square bracket + Wstaw spację przed otwierającym nawiasem kwadratowym Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Wstaw spację przed średnikiem w instrukcji „for” Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Wstaw spację między nazwę metody i jej nawias otwierający Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Wstaw spację między nazwę metody i jej nawias otwierający Insert space within argument list parentheses - Insert space within argument list parentheses + Wstaw spację wewnątrz nawiasów listy argumentów Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Wstaw spację wewnątrz nawiasów pustej listy argumentów Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Wstaw spację wewnątrz nawiasów pustej listy parametrów Insert space within empty square brackets - Insert space within empty square brackets + Wstaw spację wewnątrz pustych nawiasów kwadratowych Insert space within parameter list parentheses - Insert space within parameter list parentheses + Wstaw spację wewnątrz nawiasów listy parametrów Insert space within parentheses of expressions - Insert space within parentheses of expressions + Wstaw spację wewnątrz nawiasów wyrażeń Insert space within parentheses of type casts - Insert space within parentheses of type casts + Wstaw spację wewnątrz nawiasów rzutowania typu Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Wstaw spacje wewnątrz nawiasów instrukcji przepływu sterowania Insert spaces within square brackets - Insert spaces within square brackets + Wstaw spacje wewnątrz nawiasów kwadratowych Inside namespace - Inside namespace + W elemencie namespace Label Indentation - Label Indentation + Wcięcia etykiet Leave block on single line - Leave block on single line + Pozostaw blok w pojedynczym wierszu Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Pozostaw instrukcje i deklaracje składowych w tym samym wierszu @@ -259,177 +259,177 @@ Never - Never + Nigdy Outside namespace - Outside namespace + Poza elementem namespace Place "catch" on new line - Place "catch" on new line + Umieść element „catch” w nowym wierszu Place "else" on new line - Place "else" on new line + Umieść element „else” w nowym wierszu Place "finally" on new line - Place "finally" on new line + Umieść element „finally” w nowym wierszu Place members in anonymous types on new line - Place members in anonymous types on new line + Umieść składowe typów anonimowych w nowym wierszu Place members in object initializers on new line - Place members in object initializers on new line + Umieść składowe w inicjatorach obiektów w nowym wierszu Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Umieść otwierający nawias klamrowy w nowym wierszu dla metod anonimowych Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Umieść otwierający nawias klamrowy w nowym wierszu dla typów anonimowych Place open brace on new line for control blocks - Place open brace on new line for control blocks + Umieść otwierający nawias klamrowy w nowym wierszu dla bloków sterowania Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Umieść otwierający nawias klamrowy w nowym wierszu dla wyrażenia lambda Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Umieść otwierający nawias klamrowy w nowym wierszu dla metod i funkcji lokalnych Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Umieść otwierający nawias klamrowy w nowym wierszu dla inicjatorów obiektów, kolekcji, tablic i with Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Umieść otwierający nawias klamrowy w nowym wierszu dla właściwości, indeksatorów i zdarzeń Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Umieść otwierający nawias klamrowy w nowym wierszu dla metod dostępu właściwości, indeksatora i zdarzenia Place open brace on new line for types - Place open brace on new line for types + Umieść otwierający nawias klamrowy w nowym wierszu dla typów Place query expression clauses on new line - Place query expression clauses on new line + Umieść klauzule wyrażenia zapytania w nowym wierszu Prefer conditional delegate call - Prefer conditional delegate call + Preferuj warunkowe wywołanie delegowane Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Preferuj śródwierszową deklarację zmiennej Prefer explicit type - Prefer explicit type + Preferuj jawny typ Prefer index operator - Prefer index operator + Preferuj operator indeksowania Prefer inlined variable declaration - Prefer inlined variable declaration + Preferuj śródwierszową deklarację zmiennej Prefer local function over anonymous function - Prefer local function over anonymous function + Preferuj funkcję lokalną zamiast funkcji anonimowej Prefer pattern matching - Prefer pattern matching + Preferuj dopasowywanie do wzorca Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Preferuj dopasowywanie wzorców względem klauzuli „As” podczas testu na obecność wartości „null” Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Preferuj dopasowywanie wzorców względem klauzuli „Is” podczas sprawdzania „cast” Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Preferuj sprawdzanie dopasowania wzorca do typów mieszanych Prefer range operator - Prefer range operator + Preferuj operator zakresu Prefer simple 'default' expression - Prefer simple 'default' expression + Preferuj proste wyrażenie „default” Prefer simple 'using' statement - Prefer simple 'using' statement + Preferuj prostą instrukcję „using” Prefer static local functions - Prefer static local functions + Preferuj statyczne funkcje lokalne Prefer switch expression - Prefer switch expression + Preferuj wyrażenie switch Prefer throw-expression - Prefer throw-expression + Preferuj wyrażenie throw Prefer 'var' - Prefer 'var' + Preferuj element „var” Preferred 'using' directive placement - Preferred 'using' directive placement + Preferowane położenie dyrektywy „using” @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Ustaw odstępy dla operatorów @@ -464,47 +464,47 @@ Unused local - Unused local + Nieużywane zmienne lokalne Use expression body for accessors - Use expression body for accessors + Użyj treści wyrażenia dla metod dostępu Use expression body for constructors - Use expression body for constructors + Użyj treści wyrażenia dla konstruktorów Use expression body for indexers - Use expression body for indexers + Użyj treści wyrażenia dla indeksatorów Use expression body for lambdas - Use expression body for lambdas + Użyj treści wyrażenia dla wyrażeń lambda Use expression body for local functions - Use expression body for local functions + Użyj treści wyrażenia dla funkcji lokalnych Use expression body for methods - Use expression body for methods + Użyj treści wyrażenia dla metod Use expression body for operators - Use expression body for operators + Użyj treści wyrażenia dla operatorów Use expression body for properties - Use expression body for properties + Użyj treści wyrażenia dla właściwości @@ -514,17 +514,17 @@ When on single line - When on single line + W pojedynczym wierszu When possible - When possible + Gdy jest to możliwe When variable type is apparent - When variable type is apparent + Gdy typ zmiennej jest oczywisty diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf index 248bf1ba90a5c..9003529dc846b 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Evitar instruções de expressão que implicitamente ignoram valor Avoid unused value assignments - Avoid unused value assignments + Evitar atribuições de valor não utilizadas @@ -44,12 +44,12 @@ Discard - Discard + Descartar Elsewhere - Elsewhere + Em outro lugar @@ -59,7 +59,7 @@ For built-in types - For built-in types + Para tipos internos @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Ignorar espaços em instruções de declaração Indent block contents - Indent block contents + Recuar conteúdo do bloco Indent case contents - Indent case contents + Recuar conteúdo de case Indent case contents (when block) - Indent case contents (when block) + Recuar conteúdo de caso (quando bloquear) Indent case labels - Indent case labels + Recuar rótulos case Indent open and close braces - Indent open and close braces + Recuar chaves de abertura e fechamento Insert space after cast - Insert space after cast + Inserir espaço após conversão Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Inserir espaço após dois-pontos para base ou interface na declaração de tipo Insert space after comma - Insert space after comma + Inserir espaço após vírgula Insert space after dot - Insert space after dot + Inserir espaço após ponto Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Inserir espaço após palavras-chave em instruções de fluxo de controle Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Inserir espaço após ponto e vírgula em instruções "for" Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Inserir espaço antes de dois-pontos para base ou interface na declaração de tipo Insert space before comma - Insert space before comma + Inserir espaço antes da vírgula Insert space before dot - Insert space before dot + Inserir espaço antes do ponto Insert space before open square bracket - Insert space before open square bracket + Inserir espaço antes do colchete de abertura Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Inserir espaço antes de ponto e vírgula em instruções "for" Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Inserir espaço entre o nome do método e o parêntese inicial Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Inserir espaço entre o nome do método e o parêntese inicial Insert space within argument list parentheses - Insert space within argument list parentheses + Inserir espaço dentro dos parênteses da lista de argumentos Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Inserir espaço dentro dos parênteses da lista de argumentos vazia Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Inserir espaço no parênteses da lista de parâmetros vazia Insert space within empty square brackets - Insert space within empty square brackets + Inserir espaço dentro de colchetes vazios Insert space within parameter list parentheses - Insert space within parameter list parentheses + Inserir espaço dentro dos parênteses da lista de parâmetros Insert space within parentheses of expressions - Insert space within parentheses of expressions + Inserir espaços dentro dos parênteses das expressões Insert space within parentheses of type casts - Insert space within parentheses of type casts + Inserir espaço dentro dos parênteses das conversões de tipo Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Inserir espaços dentro dos parênteses das instruções de fluxo de controle Insert spaces within square brackets - Insert spaces within square brackets + Inserir espaços dentro de colchetes Inside namespace - Inside namespace + Namespace interno Label Indentation - Label Indentation + Recuo do Rótulo Leave block on single line - Leave block on single line + Deixar bloco em uma linha única Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Deixar instruções e declarações de membros na mesma linha @@ -259,177 +259,177 @@ Never - Never + Nunca Outside namespace - Outside namespace + Namespace externo Place "catch" on new line - Place "catch" on new line + Colocar "catch" em nova linha Place "else" on new line - Place "else" on new line + Colocar "else" em nova linha Place "finally" on new line - Place "finally" on new line + Colocar "finally" em nova linha Place members in anonymous types on new line - Place members in anonymous types on new line + Colocar membros em tipos anônimos em nova linha Place members in object initializers on new line - Place members in object initializers on new line + Colocar membros em inicializadores de objetos em nova linha Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Colocar chave de abertura em nova linha para métodos anônimos Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Colocar chave de abertura em nova linha para tipos anônimos Place open brace on new line for control blocks - Place open brace on new line for control blocks + Colocar chave de abertura em nova linha para blocos de controle Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Colocar chave de abertura em nova linha para expressão lambda Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Colocar chave de abertura na nova linha para métodos e funções locais Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Colocar uma chave de abertura em uma nova linha para inicializadores de objeto, coleção, matriz e with Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Colocar chave de abertura em nova linha para propriedades, indexadores e eventos Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Colocar chave de abertura em nova linha para acessadores de eventos, propriedades e indexadores Place open brace on new line for types - Place open brace on new line for types + Colocar chave de abertura em nova linha para tipos Place query expression clauses on new line - Place query expression clauses on new line + Colocar cláusulas de expressão de consulta na nova linha Prefer conditional delegate call - Prefer conditional delegate call + Preferir chamada de representante condicional Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Preferir declaração de variável desconstruída Prefer explicit type - Prefer explicit type + Preferir tipo explícito Prefer index operator - Prefer index operator + Preferir operador de índice Prefer inlined variable declaration - Prefer inlined variable declaration + Preferir declaração de variável embutida Prefer local function over anonymous function - Prefer local function over anonymous function + Preferir usar função anônima em vez de local Prefer pattern matching - Prefer pattern matching + Preferir a correspondência de padrões Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Preferir a correspondência de padrões 'as' com a seleção 'null' Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Preferir a correspondência de padrões 'is' com a seleção 'cast' Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Preferir a correspondência de padrões em vez da verificação de tipo misto Prefer range operator - Prefer range operator + Preferir operador de intervalo Prefer simple 'default' expression - Prefer simple 'default' expression + Preferir a expressão 'default' simples Prefer simple 'using' statement - Prefer simple 'using' statement + Preferir a instrução 'using' simples Prefer static local functions - Prefer static local functions + Preferir as funções locais estáticas Prefer switch expression - Prefer switch expression + Preferir a expressão switch Prefer throw-expression - Prefer throw-expression + Preferir expressão throw Prefer 'var' - Prefer 'var' + Preferir 'var' Preferred 'using' directive placement - Preferred 'using' directive placement + Posicionamento da diretiva 'using' preferencial @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Definir espaçamento para operadores @@ -464,47 +464,47 @@ Unused local - Unused local + Local não usado Use expression body for accessors - Use expression body for accessors + Usar o corpo da expressão para acessadores Use expression body for constructors - Use expression body for constructors + Usar o corpo da expressão para construtores Use expression body for indexers - Use expression body for indexers + Usar o corpo da expressão para indexadores Use expression body for lambdas - Use expression body for lambdas + Usar o corpo da expressão para lambdas Use expression body for local functions - Use expression body for local functions + Usar o corpo da expressão para funções locais Use expression body for methods - Use expression body for methods + Usar o corpo da expressão para métodos Use expression body for operators - Use expression body for operators + Usar o corpo da expressão para operadores Use expression body for properties - Use expression body for properties + Usar o corpo da expressão para propriedades @@ -514,17 +514,17 @@ When on single line - When on single line + Quando em linha única When possible - When possible + Quando possível When variable type is apparent - When variable type is apparent + Quando o tipo de variável é aparente diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf index 965ebbbf69613..faa1c9676dd99 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Избегайте операторов-выражений, неявно игнорирующих значение. Avoid unused value assignments - Avoid unused value assignments + Избегайте присваивания неиспользуемых значений. @@ -44,12 +44,12 @@ Discard - Discard + Отменить Elsewhere - Elsewhere + В другом месте @@ -59,7 +59,7 @@ For built-in types - For built-in types + Для встроенных типов @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Игнорировать пробелы в операторах объявления Indent block contents - Indent block contents + Делать отступ перед блоком комментариев Indent case contents - Indent case contents + Отступ в конструкции case Indent case contents (when block) - Indent case contents (when block) + Размещать содержимое case с отступами (для блока) Indent case labels - Indent case labels + Отступ заголовков конструкции case Indent open and close braces - Indent open and close braces + Отступ для открывающих и закрывающих фигурных скобок Insert space after cast - Insert space after cast + Вставлять пробел после приведения Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Вставлять пробел после двоеточия для базового типа или интерфейса в объявлениях типов Insert space after comma - Insert space after comma + Вставлять пробел после запятой Insert space after dot - Insert space after dot + Вставлять пробел после точки Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Вставлять пробел после ключевых слов в операторах потока управления Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + Вставлять пробел после точки с запятой в операторе "for" Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Вставлять пробел перед двоеточием для базового типа или интерфейса в объявлениях типов Insert space before comma - Insert space before comma + Вставлять пробел перед запятой Insert space before dot - Insert space before dot + Вставлять пробел перед точкой Insert space before open square bracket - Insert space before open square bracket + Вставлять пробел перед открывающей квадратной скобкой Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + Вставлять пробел перед точкой с запятой в операторе "for" Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Вставлять пробел между именем метода и открывающей скобкой Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Вставлять пробел между именем метода и открывающей скобкой Insert space within argument list parentheses - Insert space within argument list parentheses + Вставлять пробел между скобками со списком аргументов Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Вставлять пробел между скобками с пустым списком аргументов Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Вставлять пробел между скобками с пустым списком параметров Insert space within empty square brackets - Insert space within empty square brackets + Вставлять пробел между пустыми квадратными скобками Insert space within parameter list parentheses - Insert space within parameter list parentheses + Вставлять пробел между скобками со списком параметров Insert space within parentheses of expressions - Insert space within parentheses of expressions + Вставлять пробел между скобками для выражений Insert space within parentheses of type casts - Insert space within parentheses of type casts + Вставлять пробел между скобками для приведения типа Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Вставлять пробел между скобками в операторах потока управления Insert spaces within square brackets - Insert spaces within square brackets + Вставлять пробелы между квадратными скобками Inside namespace - Inside namespace + Внутри пространства имен Label Indentation - Label Indentation + Отступ для меток Leave block on single line - Leave block on single line + Оставить блок на одной строке Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Оставлять выражения и объявления членов на той же строке @@ -259,177 +259,177 @@ Never - Never + Никогда Outside namespace - Outside namespace + Вне пространства имен Place "catch" on new line - Place "catch" on new line + Помещать "catch" на новую строку Place "else" on new line - Place "else" on new line + Помещать "else" на новую строку Place "finally" on new line - Place "finally" on new line + Помещать "finally" на новую строку Place members in anonymous types on new line - Place members in anonymous types on new line + Помещать члены в анонимных типах в новой строке Place members in object initializers on new line - Place members in object initializers on new line + Помещать члены инициализатора объекта в новой строке Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Помещать открывающую фигурную скобку в новой строке для анонимных методов Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Помещать открывающую фигурную скобку в новой строке для анонимных типов Place open brace on new line for control blocks - Place open brace on new line for control blocks + Помещать открывающую фигурную скобку в новой строке для блоков управления Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Помещать открывающую фигурную скобку в новой строке для лямбда-выражения Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Располагать открывающую фигурную скобку в новой строке для методов и локальных функций Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Помещать открывающую фигурную скобку на новой строке для объекта, коллекции, массива и инициализаторов with Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Поместите открывающую скобку на новой строке для указания свойства, индексатора или события Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Поместите открывающую скобку на новой строке для указания метода доступа к свойству, индексатору или событию Place open brace on new line for types - Place open brace on new line for types + Помещать открывающую фигурную скобку в новой строке для типов Place query expression clauses on new line - Place query expression clauses on new line + Помещать выражения запроса в новой строке Prefer conditional delegate call - Prefer conditional delegate call + Предпочитать вызов условного делегата Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Предпочитать деконструированное объявление переменной Prefer explicit type - Prefer explicit type + Предпочитать явный тип Prefer index operator - Prefer index operator + Предпочитать оператор index Prefer inlined variable declaration - Prefer inlined variable declaration + Предпочитать встроенное объявление переменной Prefer local function over anonymous function - Prefer local function over anonymous function + Предпочитать локальную функцию анонимной функции Prefer pattern matching - Prefer pattern matching + Предпочитать соответствие шаблону Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + Предпочитать сопоставление шаблонов элементу "as" с проверкой значений "null" Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + Предпочитать сопоставление шаблонов элементу "is" с проверкой значений "cast" Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Предпочитать сопоставление шаблонов проверке смешанных типов Prefer range operator - Prefer range operator + Предпочитать оператор range Prefer simple 'default' expression - Prefer simple 'default' expression + Предпочитать простое выражение default Prefer simple 'using' statement - Prefer simple 'using' statement + Предпочитать простой оператор using Prefer static local functions - Prefer static local functions + Предпочитать статические локальные функции Prefer switch expression - Prefer switch expression + Предпочитать выражение switch Prefer throw-expression - Prefer throw-expression + Предпочитать выражения throw Prefer 'var' - Prefer 'var' + Предпочитать "var" Preferred 'using' directive placement - Preferred 'using' directive placement + Предпочтительное размещение директивы using @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Задание интервала для операторов @@ -464,47 +464,47 @@ Unused local - Unused local + Не использовать локальный аргумент Use expression body for accessors - Use expression body for accessors + Использовать тело выражения для методов доступа Use expression body for constructors - Use expression body for constructors + Использовать тело выражения для конструкторов Use expression body for indexers - Use expression body for indexers + Использовать тело выражения для индексаторов Use expression body for lambdas - Use expression body for lambdas + Использовать тело выражения для лямбда-выражений Use expression body for local functions - Use expression body for local functions + Использовать тело выражения для локальных функций Use expression body for methods - Use expression body for methods + Использовать тело выражения для методов Use expression body for operators - Use expression body for operators + Использовать тело выражения для операторов Use expression body for properties - Use expression body for properties + Использовать тело выражения для свойств @@ -514,17 +514,17 @@ When on single line - When on single line + В одной строке When possible - When possible + При возможности When variable type is apparent - When variable type is apparent + Если тип переменной очевиден diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf index 9ff6a72081600..c41985167cadc 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + Değeri örtük olarak yok sayan ifade deyimlerini engelle Avoid unused value assignments - Avoid unused value assignments + Kullanılmayan değer atamalarını engelle @@ -44,12 +44,12 @@ Discard - Discard + At Elsewhere - Elsewhere + Başka bir yerde @@ -59,7 +59,7 @@ For built-in types - For built-in types + Yerleşik türler için @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + Bildirim deyimlerindeki boşlukları yoksay Indent block contents - Indent block contents + Blok içeriğini girintile Indent case contents - Indent case contents + Case içeriğini girintile Indent case contents (when block) - Indent case contents (when block) + Case içeriklerini girintile (engelliyken) Indent case labels - Indent case labels + Case etiketlerini girintile Indent open and close braces - Indent open and close braces + Açma ve kapama küme ayraçlarını girintile Insert space after cast - Insert space after cast + Atamadan sonra boşluk ekle Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + Tür bildiriminde base veya interface için iki noktadan sonra boşluk ekle Insert space after comma - Insert space after comma + Virgülden sonra boşluk ekle Insert space after dot - Insert space after dot + Noktadan sonra boşluk ekle Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + Denetim akışı deyimlerinde anahtar sözcüklerden sonra boşluk ekle Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + "for" deyiminde noktalı virgülden sonra boşluk ekle Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + Tür bildiriminde base veya interface için iki noktadan önce boşluk ekle Insert space before comma - Insert space before comma + Virgülden önce boşluk ekle Insert space before dot - Insert space before dot + Noktadan önce boşluk ekle Insert space before open square bracket - Insert space before open square bracket + Açma köşeli ayracından önce boşluk ekle Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + "for" deyiminde noktalı virgülden önce boşluk ekle Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Metot adı ile metodun açma ayracı arasına boşluk ekle Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + Metot adı ile metodun açma ayracı arasına boşluk ekle Insert space within argument list parentheses - Insert space within argument list parentheses + Bağımsız değişken listesi ayraçları içine boşluk ekle Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + Boş bağımsız değişken listesi ayraçları içine boşluk ekle Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + Boş parametre listesi ayraçları içine boşluk ekle Insert space within empty square brackets - Insert space within empty square brackets + Boş köşeli ayraçların içine boşluk ekle Insert space within parameter list parentheses - Insert space within parameter list parentheses + Parametre listesi ayraçları içine boşluk ekle Insert space within parentheses of expressions - Insert space within parentheses of expressions + İfadelerin ayraçları içine boşluk ekle Insert space within parentheses of type casts - Insert space within parentheses of type casts + Tür atamalarının ayraçları içine boşluk ekle Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + Denetim akışı deyimlerinin ayraçları içine boşluk ekle Insert spaces within square brackets - Insert spaces within square brackets + Köşeli ayraçlar içine boşluk ekle Inside namespace - Inside namespace + namespace içinde Label Indentation - Label Indentation + Etiket Girintileme Leave block on single line - Leave block on single line + Bloğu tek satır olarak bırak Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + Deyimleri ve üye bildirimlerini aynı satırda bırak @@ -259,177 +259,177 @@ Never - Never + Hiçbir zaman Outside namespace - Outside namespace + namespace dışında Place "catch" on new line - Place "catch" on new line + "catch" anahtar sözcüğünü yeni satıra yerleştir Place "else" on new line - Place "else" on new line + "else" anahtar sözcüğünü yeni satıra yerleştir Place "finally" on new line - Place "finally" on new line + "finally" anahtar sözcüğünü yeni satıra yerleştir Place members in anonymous types on new line - Place members in anonymous types on new line + Anonim türlerdeki üyeleri yeni satıra yerleştir Place members in object initializers on new line - Place members in object initializers on new line + Nesne başlatıcısındaki üyeleri yeni satıra yerleştir Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + Anonim metotlar için açma küme ayracını yeni satıra yerleştir Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + Anonim türler için açma küme ayracını yeni satıra yerleştir Place open brace on new line for control blocks - Place open brace on new line for control blocks + Denetim blokları için açma küme ayracını yeni satıra yerleştir Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + Lambda ifadesi için açma küme ayracını yeni satıra yerleştir Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + Metotlar ve yerel işlemler için yeni satıra açma küme ayracı yerleştirin Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Nesne, koleksiyon, dizi ve with başlatıcıları için açma küme ayracını yeni satıra yerleştir Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + Özellikler, dizin oluşturucular ve olaylar için yeni satıra açık ayraç yerleştir Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + Özellik, dizin oluşturucusu ve olay erişimcileri için yeni satıra açık ayraç yerleştir Place open brace on new line for types - Place open brace on new line for types + Türler için açma küme ayracını yeni satıra yerleştir Place query expression clauses on new line - Place query expression clauses on new line + Sorgu ifadesi yan tümcesini yeni satıra yerleştir Prefer conditional delegate call - Prefer conditional delegate call + Koşullu temsilci aramasını tercih et Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + Ayrıştırılmış değişken bildirimini tercih et Prefer explicit type - Prefer explicit type + Açık türü tercih et Prefer index operator - Prefer index operator + Dizin işlecini tercih et Prefer inlined variable declaration - Prefer inlined variable declaration + Satır içine alınmış değişken bildirimini tercih et Prefer local function over anonymous function - Prefer local function over anonymous function + Anonim işlevler yerine yerel işlevleri tercih et Prefer pattern matching - Prefer pattern matching + Desen eşleştirmeyi tercih et Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + 'null' ile 'as' denetimi yerine desen eşleştirme kullan Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + 'cast' ile 'is' denetimi yerine desen eşleştirme kullan Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + Karışık tür denetimi yerine desen eşleştirmeyi tercih edin Prefer range operator - Prefer range operator + Aralık işlecini tercih et Prefer simple 'default' expression - Prefer simple 'default' expression + Basit 'default' ifadesini tercih et Prefer simple 'using' statement - Prefer simple 'using' statement + Basit 'using' deyimini tercih et Prefer static local functions - Prefer static local functions + Statik yerel işlevleri tercih et Prefer switch expression - Prefer switch expression + Switch ifadesini tercih et Prefer throw-expression - Prefer throw-expression + Throw ifadesini tercih et Prefer 'var' - Prefer 'var' + 'var' tercih et Preferred 'using' directive placement - Preferred 'using' directive placement + Tercih edilen 'using' yönergesi yerleştirmesi @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + Operatörler için boşlukları ayarla @@ -464,47 +464,47 @@ Unused local - Unused local + Kullanılmayan yerel Use expression body for accessors - Use expression body for accessors + Erişimciler için ifade gövdesi kullan Use expression body for constructors - Use expression body for constructors + Oluşturucular için ifade gövdesi kullan Use expression body for indexers - Use expression body for indexers + Dizin oluşturucular için ifade gövdesi kullan Use expression body for lambdas - Use expression body for lambdas + Lambdalar için ifade gövdesi kullan Use expression body for local functions - Use expression body for local functions + Yerel işlevler için ifade gövdesi kullan Use expression body for methods - Use expression body for methods + Metotlar için ifade gövdesi kullan Use expression body for operators - Use expression body for operators + İşleçler için ifade gövdesi kullan Use expression body for properties - Use expression body for properties + Özellikler için ifade gövdesi kullan @@ -514,17 +514,17 @@ When on single line - When on single line + Tek satırdayken When possible - When possible + Mümkün olduğunda When variable type is apparent - When variable type is apparent + Değişken türü görünür olduğunda diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf index ead8ce446a96d..cea90185fea94 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + 避免会隐式忽略值的表达式语句 Avoid unused value assignments - Avoid unused value assignments + 避免未使用的值赋值 @@ -44,12 +44,12 @@ Discard - Discard + 放弃 Elsewhere - Elsewhere + 其他位置 @@ -59,7 +59,7 @@ For built-in types - For built-in types + 对于内置类型 @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + 忽略声明语句中的空格 Indent block contents - Indent block contents + 缩进块内容 Indent case contents - Indent case contents + 缩进 case 内容 Indent case contents (when block) - Indent case contents (when block) + 缩进 case 内容(阻止时) Indent case labels - Indent case labels + 缩进 case 标签 Indent open and close braces - Indent open and close braces + 缩进左大括号和右大括号 Insert space after cast - Insert space after cast + 在强制转换后插入空格 Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + 在类型声明中的“base”或接口的冒号后插入空格 Insert space after comma - Insert space after comma + 在逗号后插入空格 Insert space after dot - Insert space after dot + 在点后插入空格 Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + 在控制流语句中的关键字后面插入空格 Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + 在“for”语句中的分号后插入空格 Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + 在类型声明中的“base”或接口的冒号前插入空格 Insert space before comma - Insert space before comma + 在逗号前插入空格 Insert space before dot - Insert space before dot + 在点前插入空格 Insert space before open square bracket - Insert space before open square bracket + 在左方括号前插入空格 Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + 在“for”语句中的分号前插入空格 Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + 在方法名称与其左括号之间插入空格 Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + 在方法名称与其左括号之间插入空格 Insert space within argument list parentheses - Insert space within argument list parentheses + 在参数列表的括号中插入空格 Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + 在空参数列表的括号中插入空格 Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + 在空参数列表的括号中插入空格 Insert space within empty square brackets - Insert space within empty square brackets + 在空方括号中插入空格 Insert space within parameter list parentheses - Insert space within parameter list parentheses + 在参数列表的括号中插入空格 Insert space within parentheses of expressions - Insert space within parentheses of expressions + 在表达式的括号中插入空格 Insert space within parentheses of type casts - Insert space within parentheses of type casts + 在类型转换的括号中插入空格 Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + 在控制流语句的括号中插入空格 Insert spaces within square brackets - Insert spaces within square brackets + 在方括号中插入空格 Inside namespace - Inside namespace + 在命名空间中 Label Indentation - Label Indentation + 标签缩进 Leave block on single line - Leave block on single line + 将块保留在一行上 Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + 将语句和成员声明保留在同一行上 @@ -259,177 +259,177 @@ Never - Never + 从不 Outside namespace - Outside namespace + 命名空间外 Place "catch" on new line - Place "catch" on new line + 将“catch”置于新行 Place "else" on new line - Place "else" on new line + 将“else”置于新行 Place "finally" on new line - Place "finally" on new line + 将“finally”置于新行 Place members in anonymous types on new line - Place members in anonymous types on new line + 将匿名类型中的成员置于新行 Place members in object initializers on new line - Place members in object initializers on new line + 将对象初始值设定项中的成员置于新行 Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + 将匿名方法的左大括号置于新行 Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + 将匿名类型的左大括号置于新行 Place open brace on new line for control blocks - Place open brace on new line for control blocks + 将控制块的左大括号置于新行 Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + 将 lambda 表达式的左大括号置于新行 Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + 将方法和本地函数的左大括号置于新行 Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 对于对象、集合、数组和 with 初始值设定项,另起一行放置左花括号 Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + 将左大括号放置在新行中,以表示属性、索引器和事件 Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + 将左大括号放置在新行中,以表示属性、索引器和事件访问器 Place open brace on new line for types - Place open brace on new line for types + 将类型的左大括号置于新行 Place query expression clauses on new line - Place query expression clauses on new line + 将查询表达式子句置于新行 Prefer conditional delegate call - Prefer conditional delegate call + 更喜欢有条件的委托调用 Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + 首选析构变量声明 Prefer explicit type - Prefer explicit type + 首选显式类型 Prefer index operator - Prefer index operator + 首选索引运算符 Prefer inlined variable declaration - Prefer inlined variable declaration + 首选内联的变量声明 Prefer local function over anonymous function - Prefer local function over anonymous function + 首选本地函数而不是匿名函数 Prefer pattern matching - Prefer pattern matching + 首选模式匹配 Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + 使用 "null" 检查时首选模式匹配而不是 "as" Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + 使用 "cast" 检查时首选模式匹配而不是 "is" Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + 首选模式匹配而不是混合类型检查 Prefer range operator - Prefer range operator + 首选范围运算符 Prefer simple 'default' expression - Prefer simple 'default' expression + 偏爱简单的 "default" 表达式 Prefer simple 'using' statement - Prefer simple 'using' statement + 首选简单的 "using" 语句 Prefer static local functions - Prefer static local functions + 首选静态本地函数 Prefer switch expression - Prefer switch expression + 首选 switch 表达式 Prefer throw-expression - Prefer throw-expression + 更喜欢 throw 表达式 Prefer 'var' - Prefer 'var' + 首选 "var" Preferred 'using' directive placement - Preferred 'using' directive placement + 首选 "using" 指令放置 @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + 设置运算符的间距 @@ -464,47 +464,47 @@ Unused local - Unused local + 未使用的本地 Use expression body for accessors - Use expression body for accessors + 使用访问器的表达式主体 Use expression body for constructors - Use expression body for constructors + 使用构造函数的表达式主体 Use expression body for indexers - Use expression body for indexers + 使用索引器的表达式主体 Use expression body for lambdas - Use expression body for lambdas + 使用 lambdas 的表达式主体 Use expression body for local functions - Use expression body for local functions + 将表达式主体用于本地函数 Use expression body for methods - Use expression body for methods + 使用方法的表达式主体 Use expression body for operators - Use expression body for operators + 使用运算符的表达式主体 Use expression body for properties - Use expression body for properties + 使用属性的表达式主体 @@ -514,17 +514,17 @@ When on single line - When on single line + 处于单行上时 When possible - When possible + 在可能的情况下 When variable type is apparent - When variable type is apparent + 当变量类型明显时 diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf index 0bb3db86969f4..13988058c5938 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ Avoid expression statements that implicitly ignore value - Avoid expression statements that implicitly ignore value + 避免會隱含地忽略值的運算陳述式 Avoid unused value assignments - Avoid unused value assignments + 避免未使用的值指派 @@ -44,12 +44,12 @@ Discard - Discard + 捨棄 Elsewhere - Elsewhere + 其他地方 @@ -59,7 +59,7 @@ For built-in types - For built-in types + 適用於內建類型 @@ -89,162 +89,162 @@ Ignore spaces in declaration statements - Ignore spaces in declaration statements + 忽略宣告陳述式中的空格 Indent block contents - Indent block contents + 縮排區塊內容 Indent case contents - Indent case contents + 縮排 case 內容 Indent case contents (when block) - Indent case contents (when block) + 縮排 case 內容 (當封鎖時) Indent case labels - Indent case labels + 縮排 case 標籤 Indent open and close braces - Indent open and close braces + 縮排左括號及右括號 Insert space after cast - Insert space after cast + 在轉換後面插入空格 Insert space after colon for base or interface in type declaration - Insert space after colon for base or interface in type declaration + 在類型宣告中的基底或介面冒號後面插入空格 Insert space after comma - Insert space after comma + 在逗號後面插入空格 Insert space after dot - Insert space after dot + 在點 (.) 後面插入空格 Insert space after keywords in control flow statements - Insert space after keywords in control flow statements + 在控制流程陳述式中的關鍵字後面插入空格 Insert space after semicolon in "for" statement - Insert space after semicolon in "for" statement + 在 "for" 陳述式中的分號後面插入空格 Insert space before colon for base or interface in type declaration - Insert space before colon for base or interface in type declaration + 在類型宣告中的基底或介面冒號前面插入空格 Insert space before comma - Insert space before comma + 在逗號前面插入空格 Insert space before dot - Insert space before dot + 在點 (.) 前面插入空格 Insert space before open square bracket - Insert space before open square bracket + 在左方括號前面插入空格 Insert space before semicolon in "for" statement - Insert space before semicolon in "for" statement + 在 "for" 陳述式中的分號前面插入空格 Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + 在方法名稱和左括號之間插入空格 Insert space between method name and its opening parenthesis - Insert space between method name and its opening parenthesis + 在方法名稱和左括號之間插入空格 Insert space within argument list parentheses - Insert space within argument list parentheses + 在引數清單括號內插入空格 Insert space within empty argument list parentheses - Insert space within empty argument list parentheses + 在空的引數清單括號內插入空格 Insert space within empty parameter list parentheses - Insert space within empty parameter list parentheses + 在空白參數清單括號內插入空格 Insert space within empty square brackets - Insert space within empty square brackets + 在空的方括號中插入空格 Insert space within parameter list parentheses - Insert space within parameter list parentheses + 在參數清單括號內插入空格 Insert space within parentheses of expressions - Insert space within parentheses of expressions + 在運算式的括號內插入空格 Insert space within parentheses of type casts - Insert space within parentheses of type casts + 在類型轉換的括號內插入空格 Insert spaces within parentheses of control flow statements - Insert spaces within parentheses of control flow statements + 在控制流程陳述式的括號內插入空格 Insert spaces within square brackets - Insert spaces within square brackets + 在方括號中插入空格 Inside namespace - Inside namespace + 位於 namespace 內 Label Indentation - Label Indentation + 標籤縮排 Leave block on single line - Leave block on single line + 讓區塊維持在同一行 Leave statements and member declarations on the same line - Leave statements and member declarations on the same line + 讓陳述式與成員宣告維持在同一行 @@ -259,177 +259,177 @@ Never - Never + 永不 Outside namespace - Outside namespace + 位於 namespace 外 Place "catch" on new line - Place "catch" on new line + 將 "catch" 置於新行 Place "else" on new line - Place "else" on new line + 將 "else" 置於新行 Place "finally" on new line - Place "finally" on new line + 將 "finally" 置於新行 Place members in anonymous types on new line - Place members in anonymous types on new line + 將匿名類型的成員置於新行 Place members in object initializers on new line - Place members in object initializers on new line + 將物件初始設定式中的成員置於新行 Place open brace on new line for anonymous methods - Place open brace on new line for anonymous methods + 將匿名方法的左括號置於新行 Place open brace on new line for anonymous types - Place open brace on new line for anonymous types + 將匿名類型的左括號置於新行 Place open brace on new line for control blocks - Place open brace on new line for control blocks + 將控制區塊的左括號置於新行 Place open brace on new line for lambda expression - Place open brace on new line for lambda expression + 將 Lambda 運算式的左括號置於新行 Place open brace on new line for methods and local functions - Place open brace on new line for methods and local functions + 將左大括號置於方法與區域函式的新行上 Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 在物件、集合、陣列以及 with 初始設定式的新行上放上左大括弧 Place open brace on new line for properties, indexers, and events - Place open brace on new line for properties, indexers, and events + 將屬性、索引子及事件的左邊大括號放在新行 Place open brace on new line for property, indexer, and event accessors - Place open brace on new line for property, indexer, and event accessors + 將屬性、索引子及事件存取子的左邊大括號放在新行 Place open brace on new line for types - Place open brace on new line for types + 將類型的左括號置於新行 Place query expression clauses on new line - Place query expression clauses on new line + 將查詢運算式子句置於新行 Prefer conditional delegate call - Prefer conditional delegate call + 建議使用條件式委派呼叫 Prefer deconstructed variable declaration - Prefer deconstructed variable declaration + 偏好解構的變數宣告 Prefer explicit type - Prefer explicit type + 建議使用明確類型 Prefer index operator - Prefer index operator + 優先使用索引運算子 Prefer inlined variable declaration - Prefer inlined variable declaration + 偏好內置變數宣告 Prefer local function over anonymous function - Prefer local function over anonymous function + 使用區域函式優先於匿名函式 Prefer pattern matching - Prefer pattern matching + 建議使用模式比對 Prefer pattern matching over 'as' with 'null' check - Prefer pattern matching over 'as' with 'null' check + 建議使用 'null' 檢查對 'as' 進行模式比對 Prefer pattern matching over 'is' with 'cast' check - Prefer pattern matching over 'is' with 'cast' check + 建議使用 'cast' 檢查對 'is' 進行模式比對 Prefer pattern matching over mixed type check - Prefer pattern matching over mixed type check + 建議使用模式比對而非混合類型檢查 Prefer range operator - Prefer range operator + 優先使用範圍運算子 Prefer simple 'default' expression - Prefer simple 'default' expression + 選擇精簡的 'default' 運算式 Prefer simple 'using' statement - Prefer simple 'using' statement + 優先使用簡單的 'using' 陳述式 Prefer static local functions - Prefer static local functions + 優先使用靜態區域函式 Prefer switch expression - Prefer switch expression + 建議使用 switch 運算式 Prefer throw-expression - Prefer throw-expression + 建議使用 throw 運算式 Prefer 'var' - Prefer 'var' + 建議使用 'var' Preferred 'using' directive placement - Preferred 'using' directive placement + 優先使用的 'using' 指示詞位置 @@ -449,7 +449,7 @@ Set spacing for operators - Set spacing for operators + 設定運算子間距 @@ -464,47 +464,47 @@ Unused local - Unused local + 未使用的區域函式 Use expression body for accessors - Use expression body for accessors + 使用存取子的運算式主體 Use expression body for constructors - Use expression body for constructors + 使用建構函式的運算式主體 Use expression body for indexers - Use expression body for indexers + 使用索引子的運算式主體 Use expression body for lambdas - Use expression body for lambdas + 使用 lambda 的運算式主體 Use expression body for local functions - Use expression body for local functions + 為區域函式使用運算式主體 Use expression body for methods - Use expression body for methods + 使用方法的運算式主體 Use expression body for operators - Use expression body for operators + 使用運算子的運算式主體 Use expression body for properties - Use expression body for properties + 使用屬性的運算式主體 @@ -514,17 +514,17 @@ When on single line - When on single line + 在單行上時 When possible - When possible + 在可行的情況下 When variable type is apparent - When variable type is apparent + 當變數類型為實際型態時 diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs index f4715d5cbc51a..7657991b3467e 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Editing; @@ -6124,5 +6125,241 @@ public class Goo } }", testHost); } + + [WorkItem(1239, @"https://github.com/dotnet/roslyn/issues/1239")] + [Fact] + public async Task TestIncompleteLambda1() + { + await TestInRegularAndScriptAsync( +@"using System.Linq; + +class C +{ + C() + { + """".Select(() => { + new [|Byte|]", +@"using System; +using System.Linq; + +class C +{ + C() + { + """".Select(() => { + new Byte"); + } + + [WorkItem(1239, @"https://github.com/dotnet/roslyn/issues/1239")] + [Fact] + public async Task TestIncompleteLambda2() + { + await TestInRegularAndScriptAsync( +@"using System.Linq; + +class C +{ + C() + { + """".Select(() => { + new [|Byte|]() }", +@"using System; +using System.Linq; + +class C +{ + C() + { + """".Select(() => { + new Byte() }"); + } + + [WorkItem(860648, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/860648")] + [WorkItem(902014, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/902014")] + [Fact] + public async Task TestIncompleteSimpleLambdaExpression() + { + await TestInRegularAndScriptAsync( +@"using System.Linq; + +class Program +{ + static void Main(string[] args) + { + args[0].Any(x => [|IBindCtx|] + string a; + } +}", +@"using System.Linq; +using System.Runtime.InteropServices.ComTypes; + +class Program +{ + static void Main(string[] args) + { + args[0].Any(x => IBindCtx + string a; + } +}"); + } + + [Theory] + [CombinatorialData] + [WorkItem(1266354, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1266354")] + public async Task TestAddUsingsEditorBrowsableNeverSameProject(TestHost testHost) + { + const string InitialWorkspace = @" + + + +using System.ComponentModel; +namespace ProjectLib +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public class Project + { + } +} + + +class Program +{ + static void Main(string[] args) + { + Project p = new [|Project()|]; + } +} + + +"; + + const string ExpectedDocumentText = @" +using ProjectLib; + +class Program +{ + static void Main(string[] args) + { + Project p = new [|Project()|]; + } +} +"; + + await TestAsync(InitialWorkspace, ExpectedDocumentText, testHost); + } + + [Theory] + [CombinatorialData] + [WorkItem(1266354, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1266354")] + public async Task TestAddUsingsEditorBrowsableNeverDifferentProject(TestHost testHost) + { + const string InitialWorkspace = @" + + + +imports System.ComponentModel +namespace ProjectLib + <EditorBrowsable(EditorBrowsableState.Never)> + public class Project + end class +end namespace + + + + lib + +class Program +{ + static void Main(string[] args) + { + [|Project|] p = new Project(); + } +} + + +"; + await TestMissingAsync(InitialWorkspace, new TestParameters(testHost: testHost)); + } + + [Theory] + [CombinatorialData] + [WorkItem(1266354, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1266354")] + public async Task TestAddUsingsEditorBrowsableAdvancedDifferentProjectOptionOn(TestHost testHost) + { + const string InitialWorkspace = @" + + + +imports System.ComponentModel +namespace ProjectLib + <EditorBrowsable(EditorBrowsableState.Advanced)> + public class Project + end class +end namespace + + + + lib + +class Program +{ + static void Main(string[] args) + { + [|Project|] p = new Project(); + } +} + + +"; + + const string ExpectedDocumentText = @" +using ProjectLib; + +class Program +{ + static void Main(string[] args) + { + Project p = new [|Project()|]; + } +} +"; + await TestAsync(InitialWorkspace, ExpectedDocumentText, testHost); + } + + [Theory] + [CombinatorialData] + [WorkItem(1266354, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1266354")] + public async Task TestAddUsingsEditorBrowsableAdvancedDifferentProjectOptionOff(TestHost testHost) + { + const string InitialWorkspace = @" + + + +imports System.ComponentModel +namespace ProjectLib + <EditorBrowsable(EditorBrowsableState.Advanced)> + public class Project + end class +end namespace + + + + lib + +class Program +{ + static void Main(string[] args) + { + [|Project|] p = new Project(); + } +} + + +"; + + await TestMissingAsync(InitialWorkspace, new TestParameters( + options: Option(CompletionOptions.HideAdvancedMembers, true), + testHost: testHost)); + } } } diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTestsWithAddImportDiagnosticProvider.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTestsWithAddImportDiagnosticProvider.cs index 233786eb19765..9509533cb3615 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTestsWithAddImportDiagnosticProvider.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTestsWithAddImportDiagnosticProvider.cs @@ -28,83 +28,6 @@ public AddUsingTestsWithAddImportDiagnosticProvider(ITestOutputHelper logger) internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) => (new CSharpUnboundIdentifiersDiagnosticAnalyzer(), new CSharpAddImportCodeFixProvider()); - [WorkItem(1239, @"https://github.com/dotnet/roslyn/issues/1239")] - [Fact] - public async Task TestIncompleteLambda1() - { - await TestInRegularAndScriptAsync( -@"using System.Linq; - -class C -{ - C() - { - """".Select(() => { - new [|Byte|]", -@"using System; -using System.Linq; - -class C -{ - C() - { - """".Select(() => { - new Byte"); - } - - [WorkItem(1239, @"https://github.com/dotnet/roslyn/issues/1239")] - [Fact] - public async Task TestIncompleteLambda2() - { - await TestInRegularAndScriptAsync( -@"using System.Linq; - -class C -{ - C() - { - """".Select(() => { - new [|Byte|]() }", -@"using System; -using System.Linq; - -class C -{ - C() - { - """".Select(() => { - new Byte() }"); - } - - [WorkItem(860648, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/860648")] - [WorkItem(902014, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/902014")] - [Fact] - public async Task TestIncompleteSimpleLambdaExpression() - { - await TestInRegularAndScriptAsync( -@"using System.Linq; - -class Program -{ - static void Main(string[] args) - { - args[0].Any(x => [|IBindCtx|] - string a; - } -}", -@"using System.Linq; -using System.Runtime.InteropServices.ComTypes; - -class Program -{ - static void Main(string[] args) - { - args[0].Any(x => IBindCtx - string a; - } -}"); - } - [WorkItem(829970, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/829970")] [Fact] public async Task TestUnknownIdentifierGenericName() diff --git a/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.cs index 6e65f4658ae12..62457ed87a60a 100644 --- a/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.cs @@ -36,7 +36,6 @@ public async Task TestGetTagsOnBufferTagger() var provider = new CopyPasteAndPrintingClassificationBufferTaggerProvider( workspace.ExportProvider.GetExportedValue(), - workspace.ExportProvider.GetExportedValue(), workspace.ExportProvider.GetExportedValue(), listenerProvider); diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index 65af723b67a96..a55f7f03b6f95 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -2610,7 +2610,6 @@ public async Task TestCreateWithBufferNotInWorkspace() var provider = new SemanticClassificationViewTaggerProvider( workspace.ExportProvider.GetExportedValue(), - workspace.ExportProvider.GetExportedValue(), workspace.ExportProvider.GetExportedValue(), listenerProvider); @@ -4412,5 +4411,33 @@ class C testHost, Record("R")); } + + [Theory] + [CombinatorialData] + public async Task BasicRecordClassClassification(TestHost testHost) + { + await TestAsync( +@"record class R +{ + R r; + + R() { } +}", + testHost, + Record("R")); + } + + [Theory] + [CombinatorialData] + public async Task BasicRecordStructClassification(TestHost testHost) + { + await TestAsync( +@"record struct R +{ + R property { get; set; } +}", + testHost, + RecordStruct("R")); + } } } diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs index e691f11ffea24..4d03f8e74369e 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs @@ -5561,5 +5561,80 @@ public async Task TestXmlAttributeNameSpan2() new ClassifiedSpan(ClassificationTypeNames.XmlDocCommentDelimiter, new TextSpan(38, 1)) }, classifications); } + + [Theory, WorkItem(52290, "https://github.com/dotnet/roslyn/issues/52290")] + [CombinatorialData] + public async Task TestStaticLocalFunction(TestHost testHost) + { + var code = @" +class C +{ + public static void M() + { + static void LocalFunc() { } + } +}"; + + await TestAsync(code, + testHost, + Keyword("class"), + Class("C"), + Punctuation.OpenCurly, + Keyword("public"), + Keyword("static"), + Keyword("void"), + Method("M"), + Static("M"), + Punctuation.OpenParen, + Punctuation.CloseParen, + Punctuation.OpenCurly, + Keyword("static"), + Keyword("void"), + Method("LocalFunc"), + Static("LocalFunc"), + Punctuation.OpenParen, + Punctuation.CloseParen, + Punctuation.OpenCurly, + Punctuation.CloseCurly, + Punctuation.CloseCurly, + Punctuation.CloseCurly); + } + + [Theory, WorkItem(52290, "https://github.com/dotnet/roslyn/issues/52290")] + [CombinatorialData] + public async Task TestConstantLocalVariable(TestHost testHost) + { + var code = @" +class C +{ + public static void M() + { + const int Zero = 0; + } +}"; + + await TestAsync(code, + testHost, + Keyword("class"), + Class("C"), + Punctuation.OpenCurly, + Keyword("public"), + Keyword("static"), + Keyword("void"), + Method("M"), + Static("M"), + Punctuation.OpenParen, + Punctuation.CloseParen, + Punctuation.OpenCurly, + Keyword("const"), + Keyword("int"), + Constant("Zero"), + Static("Zero"), + Operators.Equals, + Number("0"), + Punctuation.Semicolon, + Punctuation.CloseCurly, + Punctuation.CloseCurly); + } } } diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs index f06a7f3d458ff..17d1c3ffdb580 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -13,16 +14,20 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Classification { + [UseExportProvider] public class SyntacticTaggerTests { [WorkItem(1032665, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1032665")] - [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/19822"), Trait(Traits.Feature, Traits.Features.Classification)] - public async Task TestTagsChangedForEntireFile() + [WpfFact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task TestTagsChangedForPortionThatChanged() { var code = @"class Program2 @@ -33,13 +38,18 @@ public async Task TestTagsChangedForEntireFile() using var workspace = TestWorkspace.CreateCSharp(code); var document = workspace.Documents.First(); var subjectBuffer = document.GetTextBuffer(); + var checkpoint = new Checkpoint(); + var tagComputer = new SyntacticClassificationTaggerProvider.TagComputer( + new SyntacticClassificationTaggerProvider( + workspace.ExportProvider.GetExportedValue(), + typeMap: null, + AsynchronousOperationListenerProvider.NullProvider), subjectBuffer, - workspace.GetService(), AsynchronousOperationListenerProvider.NullListener, - null, - new SyntacticClassificationTaggerProvider(workspace.ExportProvider.GetExportedValue(), null, null, null)); + typeMap: null, + diffTimeout: TimeSpan.MaxValue); // Capture the expected value before the await, in case it changes. var expectedLength = subjectBuffer.CurrentSnapshot.Length; @@ -55,7 +65,7 @@ public async Task TestTagsChangedForEntireFile() }; await checkpoint.Task; - Assert.Equal(1, actualVersionNumber); + Assert.Equal(0, actualVersionNumber); Assert.Equal(expectedLength, actualLength); Assert.Equal(1, callstacks.Count); @@ -69,8 +79,71 @@ public async Task TestTagsChangedForEntireFile() // assigning expected here and verifying in the event handler, because the // event handler can't run until we await. await checkpoint.Task; - Assert.Equal(2, actualVersionNumber); + Assert.Equal(1, actualVersionNumber); + Assert.Equal(37, actualLength); + Assert.Equal(2, callstacks.Count); + } + + [WorkItem(1032665, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1032665")] + [WpfFact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task TestTagsChangedAfterDelete() + { + var code = +@"class Goo"; + using var workspace = TestWorkspace.CreateCSharp(code); + var document = workspace.Documents.First(); + var subjectBuffer = document.GetTextBuffer(); + + var checkpoint = new Checkpoint(); + + var typeMap = workspace.ExportProvider.GetExportedValue(); + + var tagComputer = new SyntacticClassificationTaggerProvider.TagComputer( + new SyntacticClassificationTaggerProvider( + workspace.ExportProvider.GetExportedValue(), + typeMap, + AsynchronousOperationListenerProvider.NullProvider), + subjectBuffer, + AsynchronousOperationListenerProvider.NullListener, + typeMap, + diffTimeout: TimeSpan.MaxValue); + + // Capture the expected value before the await, in case it changes. + var expectedLength = subjectBuffer.CurrentSnapshot.Length; + int? actualVersionNumber = null; + int? actualLength = null; + var callstacks = new List(); + tagComputer.TagsChanged += (s, e) => + { + actualVersionNumber = e.Span.Snapshot.Version.VersionNumber; + actualLength = e.Span.Length; + callstacks.Add(new StackTrace().ToString()); + checkpoint.Release(); + }; + + await checkpoint.Task; + Assert.Equal(0, actualVersionNumber); Assert.Equal(expectedLength, actualLength); + Assert.Equal(1, callstacks.Count); + + checkpoint = new Checkpoint(); + + // Now delete the last character. + var snapshot = subjectBuffer.Delete(new Span(subjectBuffer.CurrentSnapshot.Length - 1, 1)); + + // Try to get the tags prior to TagsChanged firing. This will force us to use the previous + // data we've cached to produce the new results. + tagComputer.GetTags(new NormalizedSnapshotSpanCollection(subjectBuffer.CurrentSnapshot.GetFullSpan())); + + expectedLength = snapshot.Length; + + // NOTE: TagsChanged is raised on the UI thread, so there is no race between + // assigning expected here and verifying in the event handler, because the + // event handler can't run until we await. + await checkpoint.Task; + + Assert.Equal(1, actualVersionNumber); + Assert.Equal(2, actualLength); Assert.Equal(2, callstacks.Count); } } diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index ee85fc5dac98a..230bc23d1b54e 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -175,6 +175,56 @@ await TestAsync( Punctuation.CloseCurly); } + [Theory] + [CombinatorialData] + public async Task TestRecordClass(TestHost testHost) + { + await TestAsync( +@"record class R +{ + R() + { + } +}", + testHost, + Keyword("record"), + Keyword("class"), + Record("R"), + Punctuation.OpenCurly, + Record("R"), + Punctuation.OpenParen, + Punctuation.CloseParen, + Punctuation.OpenCurly, + Punctuation.CloseCurly, + Punctuation.CloseCurly); + } + + [Theory] + [CombinatorialData] + public async Task TestRecordStruct(TestHost testHost) + { + await TestAsync( +@"record struct R +{ + R(int i) + { + } +}", + testHost, + Keyword("record"), + Keyword("struct"), + RecordStruct("R"), + Punctuation.OpenCurly, + RecordStruct("R"), + Punctuation.OpenParen, + Keyword("int"), + Parameter("i"), + Punctuation.CloseParen, + Punctuation.OpenCurly, + Punctuation.CloseCurly, + Punctuation.CloseCurly); + } + [Theory] [CombinatorialData] public async Task UsingAliasGlobalNamespace(TestHost testHost) @@ -995,6 +1045,7 @@ await TestInMethodAsync( Keyword("const"), Keyword("int"), Constant("Number"), + Static("Number"), Operators.Equals, Number("42"), Punctuation.Semicolon, diff --git a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs index 41475a258b766..4720fd6dfebeb 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs @@ -117,6 +117,68 @@ private static bool NewMethod(bool b) }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] + public async Task TestSelectionOfSwitchExpressionArm() + { + await TestInRegularAndScript1Async( +@"class Program +{ + int Foo(int x) => x switch + { + 1 => 1, + _ => [|1 + x|] + }; +}", +@"class Program +{ + int Foo(int x) => x switch + { + 1 => 1, + _ => {|Rename:NewMethod|}(x) + }; + private static int NewMethod(int x) => 1 + x; +}", +new TestParameters(options: Option(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement))); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] + public async Task TestSelectionOfSwitchExpressionArmContainingVariables() + { + await TestInRegularAndScript1Async( +@"using System; +using System.Collections.Generic; + +class TestClass +{ + public static T RecursiveExample(IEnumerable sequence) => + sequence switch + { + Array { Length: 0 } => default(T), + Array { Length: 1 } array => [|(T)array.GetValue(0)|], + Array { Length: 2 } array => (T)array.GetValue(1), + Array array => (T)array.GetValue(2), + _ => throw new NotImplementedException(), + }; +}", +@"using System; +using System.Collections.Generic; + +class TestClass +{ + public static T RecursiveExample(IEnumerable sequence) => + sequence switch + { + Array { Length: 0 } => default(T), + Array { Length: 1 } array => {|Rename:NewMethod|}(array), + Array { Length: 2 } array => (T)array.GetValue(1), + Array array => (T)array.GetValue(2), + _ => throw new NotImplementedException(), + }; + private static T NewMethod(Array array) => (T)array.GetValue(0); +}", +new TestParameters(options: Option(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement))); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] public async Task TestUseExpressionBodyWhenPossible() { @@ -4206,24 +4268,50 @@ void Test() } [WorkItem(48453, "https://github.com/dotnet/roslyn/issues/48453")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] + [InlineData("record")] + [InlineData("record class")] + public async Task TestInRecord(string record) + { + await TestInRegularAndScript1Async($@" +{record} Program +{{ + int field; + + public int this[int i] => [|this.field|]; +}}", +$@" +{record} Program +{{ + int field; + + public int this[int i] => {{|Rename:GetField|}}(); + + private int GetField() + {{ + return this.field; + }} +}}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] - public async Task TestInRecord() + public async Task TestInRecordStruct() { await TestInRegularAndScript1Async(@" -record Program +record struct Program { int field; public int this[int i] => [|this.field|]; }", @" -record Program +record struct Program { int field; public int this[int i] => {|Rename:GetField|}(); - private int GetField() + private readonly int GetField() { return this.field; } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs new file mode 100644 index 0000000000000..fea96d9c25894 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceParameter/IntroduceParameterTests.cs @@ -0,0 +1,1779 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.IntroduceVariable; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.IntroduceParameter +{ + public class IntroduceParameterTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new CSharpIntroduceParameterCodeRefactoringProvider(); + + protected override ImmutableArray MassageActions(ImmutableArray actions) + => FlattenActions(actions); + + private OptionsCollection UseExpressionBody => + Option(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithNoMethodCallsCase() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * y * z;|] + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z, int m) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithLocal() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int l = 5; + int m = [|l * y * z;|] + } +}"; + + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestBasicComplexExpressionCase() + { + var code = +@"using System; +class TestClass +{ + void M(string x, int y, int z) + { + int m = [|x.Length * y * z|]; + } + + void M1(string y) + { + M(y, 5, 2); + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(string x, int y, int z, int m) + { + } + + void M1(string y) + { + M(y, 5, 2, y.Length * 5 * 2); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithSingleMethodCall() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * z * z|]; + } + + void M1(int x, int y, int z) + { + M(z, x, z); + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z, int m) + { + } + + void M1(int x, int y, int z) + { + M(z, x, z, z * z * z); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestLocalDeclarationMultipleDeclarators() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * z * z|], y = 0; + } + + void M1(int x, int y, int z) + { + M(z, x, z); + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z, int v) + { + int m = {|Rename:v|}, y = 0; + } + + void M1(int x, int y, int z) + { + M(z, x, z, z * z * z); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestDeclarationInForLoop() + { + var code = +@"using System; +class TestClass +{ + void M(int a, int b) + { + var v = () => + { + for (var y = [|a * b|]; ;) { } + }; + } +}"; + + await TestMissingAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithSingleMethodCallInLocalFunction() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = M2(x, z); + + int M2(int x, int y) + { + int val = [|x * y|]; + return val; + } + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = M2(x, z, x * z); + + int M2(int x, int y, int val) + { + return val; + } + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithSingleMethodCallInStaticLocalFunction() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = M2(x, z); + + static int M2(int x, int y) + { + int val = [|x * y|]; + return val; + } + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = M2(x, z, x * z); + + static int M2(int x, int y, int val) + { + return val; + } + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestHighlightIncompleteExpressionCaseWithSingleMethodCall() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = 5 * [|x * y * z|]; + } + + void M1(int x, int y, int z) + { + M(z, y, x); + } +}"; + + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithMultipleMethodCall() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * y * z|]; + } + + void M1(int x, int y, int z) + { + M(a + b, 5, x); + M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z, int m) + { + } + + void M1(int x, int y, int z) + { + M(a + b, 5, x, (a + b) * 5 * x); + M(z, y, x, z * y * x); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionAllOccurrences() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = x * y * z; + int f = [|x * y * z|]; + } + + void M1(int x, int y, int z) + { + M(a + b, 5, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z, int f) + { + int m = f; + } + + void M1(int x, int y, int z) + { + M(a + b, 5, x, (a + b) * 5 * x); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 3); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestxpressionWithNoMethodCallTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * y * z;|] + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y, int z) + { + return x * y * z; + } + + void M(int x, int y, int z, int m) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|y * x|]; + } + + void M1(int x, int y, int z) + { + M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y) + { + return y * x; + } + + void M(int x, int y, int z, int m) + { + } + + void M1(int x, int y, int z) + { + M(z, y, x, GetM(z, y)); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallAndAccessorsTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|y * x|]; + } + + void M1(int x, int y, int z) + { + this.M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y) + { + return y * x; + } + + void M(int x, int y, int z, int m) + { + } + + void M1(int x, int y, int z) + { + this.M(z, y, x, GetM(z, y)); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallAndAccessorsConditionalTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|y * x|]; + } + + void M1(int x, int y, int z) + { + this?.M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y) + { + return y * x; + } + + void M(int x, int y, int z, int m) + { + } + + void M1(int x, int y, int z) + { + this?.M(z, y, x, this?.GetM(z, y)); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallMultipleAccessorsTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + A a = new A(); + var age = a.Prop.ComputeAge(x, y); + } +} + +class A +{ + public B Prop { get; set; } +} +class B +{ + public int ComputeAge(int x, int y) + { + var age = [|x + y|]; + return age; + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + A a = new A(); + var age = a.Prop.ComputeAge(x, y, a.Prop.GetAge(x, y)); + } +} + +class A +{ + public B Prop { get; set; } +} +class B +{ + public int GetAge(int x, int y) + { + return x + y; + } + + public int ComputeAge(int x, int y, int age) + { + return age; + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallMultipleAccessorsConditionalTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + A a = new A(); + var age = a?.Prop?.ComputeAge(x, y); + } +} + +class A +{ + public B Prop { get; set; } +} +class B +{ + public int ComputeAge(int x, int y) + { + var age = [|x + y|]; + return age; + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + A a = new A(); + var age = a?.Prop?.ComputeAge(x, y, a?.Prop?.GetAge(x, y)); + } +} + +class A +{ + public B Prop { get; set; } +} +class B +{ + public int GetAge(int x, int y) + { + return x + y; + } + + public int ComputeAge(int x, int y, int age) + { + return age; + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallAccessorsMixedConditionalTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + A a = new A(); + var age = a.Prop?.ComputeAge(x, y); + } +} + +class A +{ + public B Prop { get; set; } +} +class B +{ + public int ComputeAge(int x, int y) + { + var age = [|x + y|]; + return age; + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + A a = new A(); + var age = a.Prop?.ComputeAge(x, y, a.Prop?.GetAge(x, y)); + } +} + +class A +{ + public B Prop { get; set; } +} +class B +{ + public int GetAge(int x, int y) + { + return x + y; + } + + public int ComputeAge(int x, int y, int age) + { + return age; + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallTrampolineAllOccurrences() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * y * z|]; + int l = x * y * z; + } + + void M1(int x, int y, int z) + { + M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y, int z) + { + return x * y * z; + } + + void M(int x, int y, int z, int m) + { + int l = m; + } + + void M1(int x, int y, int z) + { + M(z, y, x, GetM(z, y, x)); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 4, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithNoMethodCallOverload() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * y * z;|] + } +}"; + + var expected = +@"using System; +class TestClass +{ + private void M(int x, int y, int z) + { + M(x, y, z, x * y * z); + } + + void M(int x, int y, int z, int m) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 2, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionWithSingleMethodCallOverload() + { + var code = +@"using System; +class TestClass +{ + void M(int x, int y, int z) + { + int m = [|x * y * z|]; + } + + void M1(int x, int y, int z) + { + M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private void M(int x, int y, int z) + { + M(x, y, z, x * y * z); + } + + void M(int x, int y, int z, int m) + { + } + + void M1(int x, int y, int z) + { + M(z, y, x); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 2, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionBodiedMemberOverload() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y, int z) => [|x * y * z|]; + + void M1(int x, int y, int z) + { + int prod = M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int M(int x, int y, int z) => M(x, y, z, x * y * z); + int M(int x, int y, int z, int v) => {|Rename:v|}; + + void M1(int x, int y, int z) + { + int prod = M(z, y, x); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 2, options: UseExpressionBody, parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionBodiedMemberTrampoline() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y, int z) => [|x * y * z|]; + + void M1(int x, int y, int z) + { + int prod = M(z, y, x); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetV(int x, int y, int z) + { + return x * y * z; + } + + int M(int x, int y, int z, int v) => {|Rename:v|}; + + void M1(int x, int y, int z) + { + int prod = M(z, y, x, GetV(z, y, x)); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithRecursiveCall() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y, int z) + { + int m = [|x * y * z|]; + return M(x, x, z); + } +}"; + + var expected = +@"using System; +class TestClass +{ + int M(int x, int y, int z, int m) + { + return M(x, x, z, x * x * z); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithNestedRecursiveCall() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y, int z) + { + int m = [|x * y * z|]; + return M(x, x, M(x, y, z)); + } +}"; + + var expected = +@"using System; +class TestClass +{ + int M(int x, int y, int z, int m) + { + return M(x, x, M(x, y, z, x * y * z), x * x * M(x, y, z, x * y * z)); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithParamsArg() + { + var code = +@"using System; +class TestClass +{ + int M(params int[] args) + { + int m = [|args[0] + args[1]|]; + return m; + } + + void M1() + { + M(5, 6, 7); + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithOptionalParameters() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = 5) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(5, 3); + } +}"; + + var expected = +@"using System; +class TestClass +{ + int M(int x, int m, int y = 5) + { + return m; + } + + void M1() + { + M(5, 5 * 3, 3); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithOptionalParametersUsed() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = 5) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(7); + } +}"; + + var expected = +@"using System; +class TestClass +{ + int M(int x, int m, int y = 5) + { + return m; + } + + void M1() + { + M(7, 7 * 5); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithOptionalParametersUsedOverload() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = 5) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(7); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int M(int x, int y = 5) + { + return M(x, x * y, y); + } + + int M(int x, int m, int y = 5) + { + return m; + } + + void M1() + { + M(7); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 2); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithOptionalParametersUsedTrampoline() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = 5) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(7); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y = 5) + { + return x * y; + } + + int M(int x, int m, int y = 5) + { + return m; + } + + void M1() + { + M(7, GetM(7)); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 1); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithOptionalParametersUnusedTrampoline() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = 5) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(7, 2); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y = 5) + { + return x * y; + } + + int M(int x, int m, int y = 5) + { + return m; + } + + void M1() + { + M(7, GetM(7, 2), 2); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 1); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithCancellationToken() + { + var code = +@"using System; +using System.Threading; +class TestClass +{ + int M(int x, CancellationToken cancellationToken) + { + int m = [|x * x|]; + return m; + } + + void M1(CancellationToken cancellationToken) + { + M(7, cancellationToken); + } +}"; + + var expected = +@"using System; +using System.Threading; +class TestClass +{ + int M(int x, int m, CancellationToken cancellationToken) + { + return m; + } + + void M1(CancellationToken cancellationToken) + { + M(7, 7 * 7, cancellationToken); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithRecursiveCallTrampoline() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y, int z) + { + int m = [|x * y * z|]; + return M(x, x, z); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y, int z) + { + return x * y * z; + } + + int M(int x, int y, int z, int m) + { + return M(x, x, z, GetM(x, x, z)); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseWithNestedRecursiveCallTrampoline() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y, int z) + { + int m = [|x * y * z|]; + return M(x, x, M(x, y, x)); + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(int x, int y, int z) + { + return x * y * z; + } + + int M(int x, int y, int z, int m) + { + return M(x, x, M(x, y, x, GetM(x, y, x)), GetM(x, x, M(x, y, x, GetM(x, y, x)))); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 1, options: new OptionsCollection(GetLanguage()), parseOptions: CSharpParseOptions.Default); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionCaseInConstructor() + { + var code = +@"using System; +class TestClass +{ + public TestClass(int x, int y) + { + Math.Max([|x + y|], x * y); + } + + void TestMethod() + { + var test = new TestClass(5, 6); + } +}"; + var expected = +@"using System; +class TestClass +{ + public TestClass(int x, int y, int val1) + { + Math.Max({|Rename:val1|}, x * y); + } + + void TestMethod() + { + var test = new TestClass(5, 6, 5 + 6); + } +}"; + + await TestInRegularAndScriptAsync(code, expected, 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestLambdaCaseNormal() + { + var code = +@"using System; +class TestClass +{ + Func mult = (x, y) => [|x * y|]; +}"; + + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestLambdaCaseTrampoline() + { + var code = +@"using System; +class TestClass +{ + Func mult = (x, y) => [|x * y|]; +}"; + + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestLambdaCaseOverload() + { + var code = +@"using System; +class TestClass +{ + Func mult = (x, y) => [|x * y|]; +}"; + + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestTopLevelStatements() + { + var code = +@"using System; +Math.Max(5 + 5, [|6 + 7|]);"; + + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestFieldInitializer() + { + var code = +@"using System; +class TestClass +{ + int a = [|5 + 3|]; +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestIndexer() + { + var code = +@"using System; +class SampleCollection +{ + private T[] arr = new T[100]; + + public T this[int i] => arr[[|i + 5|]]; +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestPropertyGetter() + { + var code = +@"using System; + +class TimePeriod +{ + private double _seconds; + + public double Hours + { + get { return [|_seconds / 3600|]; } + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestPropertySetter() + { + var code = +@"using System; + +class TimePeriod +{ + private double _seconds; + + public double Hours + { + set { + _seconds = [|value * 3600|]; + } + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestDestructor() + { + var code = +@"using System; +class TestClass +{ + public ~TestClass() + { + Math.Max([|5 + 5|], 5 * 5); + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestExpressionInParameter() + { + var code = +@"using System; +class TestClass +{ + public void M(int x = [|5 * 5|]) + { + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestCrossLanguageInvocations() + { + var code = +@" + + +public class Program +{ + public Program() {} + + public int M(int x, int y) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(7, 2); + } +} + + + + + Assembly1 + +Class ProgramVB + Sub M2() + Dim programC = New Program() + programC.M(7, 2) + End Sub +End Class + + + +"; + + var expected = +@" + + +public class Program +{ + public Program() {} + + public int M(int x, int y, int m) + { + return m; + } + + void M1() + { + M(7, 2, 7 * 2); + } +} + + + + + Assembly1 + +Class ProgramVB + Sub M2() + Dim programC = New Program() + programC.M(7, 2) + End Sub +End Class + + + +"; + await TestInRegularAndScriptAsync(code, expected, 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestConvertedTypeInExpression() + { + var code = +@"using System; +class TestClass +{ + void M(double x, double y) + { + int m = [|(int)(x * y);|] + } +}"; + + var expected = +@"using System; +class TestClass +{ + void M(double x, double y, int m) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestConvertedTypeInExpressionTrampoline() + { + var code = +@"using System; +class TestClass +{ + void M(double x, double y) + { + int m = [|(int)(x * y);|] + } +}"; + + var expected = +@"using System; +class TestClass +{ + private int GetM(double x, double y) + { + return (int)(x * y); + } + + void M(double x, double y, int m) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 1); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestThisKeywordInExpression() + { + var code = +@"using System; +class TestClass +{ + public int M1() + { + return 5; + } + + public int M(int x, int y) + { + int m = [|x * this.M1();|] + return m; + } +}"; + + var expected = +@"using System; +class TestClass +{ + public int M1() + { + return 5; + } + + public int GetM(int x) + { + return x * this.M1(); + } + + public int M(int x, int y, int m) + { + return m; + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestThisImplicitInExpression() + { + var code = +@"using System; +class TestClass +{ + public int M1() + { + return 5; + } + + public int M(int x, int y) + { + int m = [|x * M1();|] + return m; + } +}"; + + var expected = +@"using System; +class TestClass +{ + public int M1() + { + return 5; + } + + public int GetM(int x) + { + return x * M1(); + } + + public int M(int x, int y, int m) + { + return m; + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestStaticMethodCallInExpression() + { + var code = +@"using System; +class TestClass +{ + public static int M1() + { + return 5; + } + + public int M(int x, int y) + { + int m = [|x * M1();|] + return m; + } +}"; + + var expected = +@"using System; +class TestClass +{ + public static int M1() + { + return 5; + } + + public int M(int x, int y, int m) + { + return m; + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestBaseKeywordInExpression() + { + var code = +@"using System; +class Net +{ + public int _value = 6; +} + +class Perl : Net +{ + public new int _value = 7; + + public void Write() + { + int x = [|base._value + 1;|] + } +}"; + + var expected = +@"using System; +class Net +{ + public int _value = 6; +} + +class Perl : Net +{ + public new int _value = 7; + + public int GetX() + { + return base._value + 1; + } + + public void Write(int x) + { + } +}"; + + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestFieldReferenceInOptionalParameter() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = int.MaxValue) + { + int m = [|x * y|]; + return m; + } + + void M1() + { + M(7); + } +}"; + + var expected = +@"using System; +class TestClass +{ + int M(int x, int m, int y = int.MaxValue) + { + return m; + } + + void M1() + { + M(7, 7 * int.MaxValue); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceParameter)] + public async Task TestNamedParameterNecessary() + { + var code = +@"using System; +class TestClass +{ + int M(int x, int y = 5, int z = 3) + { + int m = [|z * y|]; + return m; + } + + void M1() + { + M(z: 0, y: 2); + } +}"; + + var expected = +@"using System; +class TestClass +{ + int M(int x, int m, int y = 5, int z = 3) + { + return m; + } + + void M1() + { + M(z: 0, m: 0 * 2, y: 2); + } +}"; + await TestInRegularAndScriptAsync(code, expected, index: 0); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs index c7a7e1933c3e0..3ab4a4831464e 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs @@ -7912,5 +7912,111 @@ void M() } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + [WorkItem(52833, "https://github.com/dotnet/roslyn/issues/52833")] + public async Task UniqueParameterName() + { + await TestInRegularAndScriptAsync(@" +using System.IO; + +public class SomeClass +{ + public void Foo() + { + var somePath = Path.Combine(""one"", ""two""); + Other([|""someParam""|]); + } + + public void Other(string path) + { + } +}", +@" +using System.IO; + +public class SomeClass +{ + public void Foo() + { + var somePath = Path.Combine(""one"", ""two""); + const string {|Rename:Path1|} = ""someParam""; + Other(Path1); + } + + public void Other(string path) + { + } +}", 2); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + [WorkItem(47772, "https://github.com/dotnet/roslyn/issues/47772")] + public async Task DoNotIntroduceConstantForConstant_Local() + { + await TestMissingAsync( +@" +class C +{ + void M() + { + const int foo = [|10|]; + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + [WorkItem(47772, "https://github.com/dotnet/roslyn/issues/47772")] + public async Task DoNotIntroduceConstantForConstant_Member() + { + await TestMissingAsync( +@" +class C +{ + const int foo = [|10|]; +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + [WorkItem(47772, "https://github.com/dotnet/roslyn/issues/47772")] + public async Task DoNotIntroduceConstantForConstant_Parentheses() + { + await TestMissingAsync( +@" +class C +{ + const int foo = ([|10|]); +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + [WorkItem(47772, "https://github.com/dotnet/roslyn/issues/47772")] + public async Task DoNotIntroduceConstantForConstant_NotForSubExpression() + { + await TestInRegularAndScriptAsync( +@" +class C +{ + void M() + { + const int foo = [|10|] + 10; + } +} +", +@" +class C +{ + void M() + { + const int {|Rename:V|} = 10; + const int foo = V + 10; + } +} +", + index: 2); + } } } diff --git a/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs b/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs index 8f43e4be2e8df..efb3df175f11f 100644 --- a/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs @@ -215,35 +215,38 @@ public class A await RunMethodReferenceTest(input); } - [Fact, Trait(Traits.Feature, Traits.Features.CodeLens)] - public async Task TestFullyQualifiedName() + [Theory, Trait(Traits.Feature, Traits.Features.CodeLens)] + [InlineData("class")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestFullyQualifiedName(string typeKind) { - const string input = @" + var input = $@" diff --git a/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs index 59113b9e53a04..fca445bce63c1 100644 --- a/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs @@ -55,6 +55,8 @@ internal static int MethodM(int a, int b) [InlineData("public record C(int X, $$int Y)", "public record C(int X, int Y)")] [InlineData("public record C(int X, int$$ Y)", "public record C(int X, int Y)")] [InlineData("public record C(int X, int Y$$)", "public record C(int X, int Y)")] + [InlineData("public record class C(int X, int Y$$)", "public record class C(int X, int Y)")] + [InlineData("public record struct C(int X, int Y$$)", "public record struct C(int X, int Y)")] public void ParameterList_CouldBeHandled(string signature, string expectedSignature) { var code = $@" diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs index 7e943d722b0a4..61bb8ef0a1039 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs @@ -330,16 +330,19 @@ public interface IAsyncEnumerator : IAsyncDisposable internal override Type GetCompletionProviderType() => typeof(DeclarationNameCompletionProvider); - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] [WorkItem(48310, "https://github.com/dotnet/roslyn/issues/48310")] - public async Task TreatRecordPositionalParameterAsProperty() + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TreatRecordPositionalParameterAsProperty(string record) { - var markup = @" + var markup = $@" public class MyClass -{ -} +{{ +}} -public record R(MyClass $$ +public {record} R(MyClass $$ "; await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.PropertyPublic); } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs index c9fccc90daae5..21bec8998c987 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs @@ -35,19 +35,22 @@ class C : IList await VerifyItemExistsAsync(markup, "IList"); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestAtStartOfRecord() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestAtStartOfRecord(string record) { - var markup = @" + var markup = $@" using System.Collections; -record C : IList -{ +{record} C : IList +{{ int $$ -} +}} "; diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs index 9607425cfaf0a..de1176e685356 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs @@ -412,7 +412,10 @@ public async Task SuggestEventAfterReadonlyInStruct() [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [WorkItem(39265, "https://github.com/dotnet/roslyn/issues/39265")] [InlineData("struct", true)] + [InlineData("record struct", true)] [InlineData("class", false)] + [InlineData("record", false)] + [InlineData("record class", false)] [InlineData("interface", false)] public async Task SuggestReadonlyPropertyAccessor(string declarationType, bool present) { diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index 178e2bb855d32..98fbf90a80ee8 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -2979,7 +2979,7 @@ public override void M(in int x) var completionList = await GetCompletionListAsync(service, document, testDocument.CursorPosition.Value, CompletionTrigger.Invoke); var completionItem = completionList.Items.Where(c => c.DisplayText == "M(in int x)").Single(); - var commit = await service.GetChangeAsync(document, completionItem, completionList.Span, commitKey: null, disallowAddingImports: false, CancellationToken.None); + var commit = await service.GetChangeAsync(document, completionItem, commitKey: null, CancellationToken.None); var text = await document.GetTextAsync(); var newText = text.WithChanges(commit.TextChange); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index 881b54f84e7fe..01a35d5e86898 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -7482,18 +7482,31 @@ struct C { ~$$ }"; + await VerifyItemIsAbsentAsync(markup, "C"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("record")] + [InlineData("record class")] + public async Task RecordDestructor(string record) + { + var markup = $@" +{record} C +{{ + ~$$ +}}"; await VerifyItemExistsAsync(markup, "C"); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task RecordDestructor() + public async Task RecordStructDestructor() { - var markup = @" -record C -{ + var markup = $@" +record struct C +{{ ~$$ -}"; - await VerifyItemExistsAsync(markup, "C"); +}}"; + await VerifyItemIsAbsentAsync(markup, "C"); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs index 606e8c0377bd0..e96646c6dcb36 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs @@ -29,8 +29,6 @@ internal override Type GetCompletionProviderType() private bool IsExpandedCompletion { get; set; } = true; - private bool DisallowAddingImports { get; set; } - private bool HideAdvancedMembers { get; set; } private bool UsePartialSemantic { get; set; } = false; @@ -40,7 +38,6 @@ 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(CompletionServiceOptions.UsePartialSemanticForImportCompletion, UsePartialSemantic); } @@ -932,41 +929,6 @@ class Bat await VerifyCustomCommitProviderAsync(markup, "Bar", expectedCodeAfterCommit, sourceCodeKind: kind); } - [InlineData(SourceCodeKind.Regular)] - [InlineData(SourceCodeKind.Script)] - [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task Commit_NoImport_InProject_DisallowAddingImports(SourceCodeKind kind) - { - DisallowAddingImports = true; - - var file1 = $@" -namespace Foo -{{ - public class Bar - {{ - }} -}}"; - - var file2 = @" -namespace Baz -{ - class Bat - { - $$ - } -}"; - var expectedCodeAfterCommit = @" -namespace Baz -{ - class Bat - { - Foo.Bar$$ - } -}"; - var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp); - await VerifyCustomCommitProviderAsync(markup, "Bar", expectedCodeAfterCommit, sourceCodeKind: kind); - } - [InlineData(SourceCodeKind.Regular)] [InlineData(SourceCodeKind.Script)] [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] @@ -1553,7 +1515,7 @@ class Bat private static void AssertRelativeOrder(List expectedTypesInRelativeOrder, ImmutableArray allCompletionItems) { var hashset = new HashSet(expectedTypesInRelativeOrder); - var actualTypesInRelativeOrder = allCompletionItems.Where(item => hashset.Contains(item.DisplayText)).Select(item => item.DisplayText).ToImmutableArray(); + var actualTypesInRelativeOrder = allCompletionItems.SelectAsArray(item => hashset.Contains(item.DisplayText), item => item.DisplayText); Assert.Equal(expectedTypesInRelativeOrder.Count, actualTypesInRelativeOrder.Length); for (var i = 0; i < expectedTypesInRelativeOrder.Count; ++i) diff --git a/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs b/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs index 8eda9b9e8f126..2116a5ac38b23 100644 --- a/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs @@ -2,34 +2,67 @@ // 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.Immutable; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.ConvertTupleToStruct; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.ConvertTupleToStruct; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.NamingStyles; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertTupleToStruct { - public class ConvertTupleToStructTests : AbstractCSharpCodeActionTest + using VerifyCS = CSharpCodeRefactoringVerifier; + + [UseExportProvider] + public class ConvertTupleToStructTests { - protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) - => new CSharpConvertTupleToStructCodeRefactoringProvider(); + private static OptionsCollection PreferImplicitTypeWithInfo() + => new OptionsCollection(LanguageNames.CSharp) + { + { CSharpCodeStyleOptions.VarElsewhere, true, NotificationOption2.Suggestion }, + { CSharpCodeStyleOptions.VarWhenTypeIsApparent, true, NotificationOption2.Suggestion }, + { CSharpCodeStyleOptions.VarForBuiltInTypes, true, NotificationOption2.Suggestion }, + }; + + private static async Task TestAsync( + string text, + string expected, + int index = 0, + string? equivalenceKey = null, + LanguageVersion languageVersion = LanguageVersion.CSharp9, + OptionsCollection? options = null, + TestHost testHost = TestHost.InProcess, + string[]? actions = null) + { + if (index != 0) + Assert.NotNull(equivalenceKey); + + var test = new VerifyCS.Test + { + TestCode = text, + FixedCode = expected, + TestHost = testHost, + LanguageVersion = languageVersion, + CodeActionIndex = index, + CodeActionEquivalenceKey = equivalenceKey, + ExactActionSetOffered = actions, + }; - protected override ImmutableArray MassageActions(ImmutableArray actions) - => FlattenActions(actions); + if (options != null) + test.Options.AddRange(options); + await test.RunAsync(); + } #region update containing member tests @@ -50,7 +83,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -96,7 +129,94 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task ConvertSingleTupleTypeToRecord(TestHost host) + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||](a: 1, b: 2); + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = new NewStruct(a: 1, b: 2); + } +} + +internal record struct NewStruct(int a, int b) +{ + public static implicit operator (int a, int b)(NewStruct value) + { + return (value.a, value.b); + } + + public static implicit operator NewStruct((int a, int b) value) + { + return new NewStruct(value.a, value.b); + } +}"; + await TestAsync(text, expected, languageVersion: LanguageVersion.Preview, options: PreferImplicitTypeWithInfo(), testHost: host); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task ConvertSingleTupleTypeToRecord_MismatchedNameCasing(TestHost host) + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||](A: 1, B: 2); + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = new NewStruct(a: 1, b: 2); + } +} + +internal record struct NewStruct +{ + public int A; + public int B; + + public NewStruct(int a, int b) + { + A = a; + B = b; + } + + public void Deconstruct(out int a, out int b) + { + a = A; + b = B; + } + + public static implicit operator (int A, int B)(NewStruct value) + { + return (value.A, value.B); + } + + public static implicit operator NewStruct((int A, int B) value) + { + return new NewStruct(value.A, value.B); + } +}"; + await TestAsync(text, expected, languageVersion: LanguageVersion.Preview, options: PreferImplicitTypeWithInfo(), testHost: host); } [WorkItem(45451, "https://github.com/dotnet/roslyn/issues/45451")] @@ -117,7 +237,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -163,7 +283,7 @@ public static implicit operator NewStruct((int A, int B) value) return new NewStruct(value.A, value.B); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [WorkItem(45451, "https://github.com/dotnet/roslyn/issues/45451")] @@ -184,7 +304,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(p_a_: 1, p_b_: 2); + var t1 = new NewStruct(p_a_: 1, p_b_: 2); } } @@ -257,10 +377,10 @@ public static implicit operator NewStruct((int A, int B) value) ImmutableArray.Create(namingStyle), ImmutableArray.Create(namingRule)); - var options = this.PreferImplicitTypeWithInfo(); + var options = PreferImplicitTypeWithInfo(); options.Add(NamingStyleOptions.NamingPreferences, info); - await TestInRegularAndScriptAsync(text, expected, options: options, testHost: host); + await TestAsync(text, expected, options: options, testHost: host); } [WorkItem(39916, "https://github.com/dotnet/roslyn/issues/39916")] @@ -281,7 +401,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -327,7 +447,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, testHost: host); + await TestAsync(text, expected, testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -347,7 +467,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(1, 2); + var t1 = new NewStruct(1, 2); } } @@ -393,7 +513,7 @@ public static implicit operator NewStruct((int, int) value) return new NewStruct(value.Item1, value.Item2); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -413,7 +533,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(1, b: 2); + var t1 = new NewStruct(1, b: 2); } } @@ -459,7 +579,7 @@ public static implicit operator NewStruct((int, int b) value) return new NewStruct(value.Item1, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -480,7 +600,7 @@ class Test { void Method() { - {|Rename:NewStruct|} t1 = new NewStruct(a: 1, b: 2); + NewStruct t1 = new NewStruct(a: 1, b: 2); NewStruct t2 = new NewStruct(a: 1, b: 2); } } @@ -527,7 +647,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -540,6 +660,7 @@ class Test { [||](int a, int b) t1 = (a: 1, b: 2); (int a, int b) t2 = (a: 1, b: 2); + return default; } } "; @@ -548,8 +669,9 @@ class Test { NewStruct Method() { - {|Rename:NewStruct|} t1 = new NewStruct(a: 1, b: 2); + NewStruct t1 = new NewStruct(a: 1, b: 2); NewStruct t2 = new NewStruct(a: 1, b: 2); + return default; } } @@ -595,7 +717,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -608,6 +730,7 @@ class Test { [||](int a, int b) t1 = (a: 1, b: 2); (int b, int a) t2 = (b: 1, a: 2); + return default; } } "; @@ -616,8 +739,9 @@ class Test { NewStruct Method() { - {|Rename:NewStruct|} t1 = new NewStruct(a: 1, b: 2); + NewStruct t1 = new NewStruct(a: 1, b: 2); (int b, int a) t2 = (b: 1, a: 2); + return default; } } @@ -663,7 +787,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -685,7 +809,7 @@ class Test void Method() { NewStruct t1 = new NewStruct(a: 1, b: 2); - {|Rename:NewStruct|} t2 = new NewStruct(a: 1, b: 2); + NewStruct t2 = new NewStruct(a: 1, b: 2); } } @@ -731,7 +855,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -756,7 +880,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -804,18 +928,86 @@ public static implicit operator NewStruct((int a, int b) value) } } "; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task TestNonLiteralNames_WithUsings(TestHost host) + { + var text = @" +using System.Collections.Generic; +class Test +{ + void Method() + { + var t1 = [||](a: {|CS0103:Goo|}(), b: {|CS0103:Bar|}()); + } +} +"; + var expected = @" +using System.Collections.Generic; +class Test +{ + void Method() + { + var t1 = new NewStruct({|CS0103:Goo|}(), {|CS0103:Bar|}()); + } +} + +internal struct NewStruct +{ + public object a; + public object b; + + public NewStruct(object a, object b) + { + this.a = a; + this.b = b; + } + + public override bool Equals(object obj) + { + return obj is NewStruct other && + EqualityComparer.Default.Equals(a, other.a) && + EqualityComparer.Default.Equals(b, other.b); + } + + public override int GetHashCode() + { + var hashCode = 2118541809; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(a); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(b); + return hashCode; + } + + public void Deconstruct(out object a, out object b) + { + a = this.a; + b = this.b; + } + + public static implicit operator (object a, object b)(NewStruct value) + { + return (value.a, value.b); + } + + public static implicit operator NewStruct((object a, object b) value) + { + return new NewStruct(value.a, value.b); + } +}"; + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task TestNonLiteralNames(TestHost host) + public async Task TestNonLiteralNames_WithoutUsings(TestHost host) { var text = @" class Test { void Method() { - var t1 = [||](a: Goo(), b: Bar()); + var t1 = [||](a: {|CS0103:Goo|}(), b: {|CS0103:Bar|}()); } } "; @@ -824,7 +1016,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(Goo(), Bar()); + var t1 = new NewStruct({|CS0103:Goo|}(), {|CS0103:Bar|}()); } } @@ -870,7 +1062,7 @@ public static implicit operator NewStruct((object a, object b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -890,7 +1082,7 @@ class Test { void Method(int b) { - var t1 = new {|Rename:NewStruct|}(a: 1, b); + var t1 = new NewStruct(a: 1, b); } } @@ -936,7 +1128,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -957,7 +1149,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); var t2 = new NewStruct(a: 3, b: 4); } } @@ -1004,7 +1196,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1031,7 +1223,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); var t2 = new NewStruct(a: 3, b: 4); } @@ -1084,7 +1276,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1107,7 +1299,7 @@ class Test { void Method(int b) { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); var t2 = new NewStruct(a: 3, b); var t3 = (a: 4, b: 5, c: 6); var t4 = (b: 5, a: 6); @@ -1156,7 +1348,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1179,7 +1371,7 @@ class Test { void Method(int b) { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); var t2 = new NewStruct(a: 3, b); var t3 = (a: 4, b: 5, c: 6); var t4 = (b: 5, a: 6); @@ -1228,7 +1420,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1255,7 +1447,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); var t2 = new NewStruct(a: 3, b: 4); } @@ -1308,18 +1500,86 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task TestTrivia_WithUsings(TestHost host) + { + var text = @" +using System.Collections.Generic; +class Test +{ + void Method() + { + var t1 = /*1*/ [||]( /*2*/ a /*3*/ : /*4*/ 1 /*5*/ , /*6*/ {|CS0103:b|} /*7*/ = /*8*/ 2 /*9*/ ) /*10*/ ; + } +} +"; + var expected = @" +using System.Collections.Generic; +class Test +{ + void Method() + { + var t1 = /*1*/ new NewStruct( /*2*/ a /*3*/ : /*4*/ 1 /*5*/ , /*6*/ {|CS0103:b|} /*7*/ = /*8*/ 2 /*9*/ ) /*10*/ ; + } +} + +internal struct NewStruct +{ + public int a; + public object Item2; + + public NewStruct(int a, object item2) + { + this.a = a; + Item2 = item2; + } + + public override bool Equals(object obj) + { + return obj is NewStruct other && + a == other.a && + EqualityComparer.Default.Equals(Item2, other.Item2); + } + + public override int GetHashCode() + { + var hashCode = 913311208; + hashCode = hashCode * -1521134295 + a.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Item2); + return hashCode; + } + + public void Deconstruct(out int a, out object item2) + { + a = this.a; + item2 = Item2; + } + + public static implicit operator (int a, object)(NewStruct value) + { + return (value.a, value.Item2); + } + + public static implicit operator NewStruct((int a, object) value) + { + return new NewStruct(value.a, value.Item2); + } +}"; + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task TestTrivia(TestHost host) + public async Task TestTrivia_WithoutUsings(TestHost host) { var text = @" class Test { void Method() { - var t1 = /*1*/ [||]( /*2*/ a /*3*/ : /*4*/ 1 /*5*/ , /*6*/ b /*7*/ = /*8*/ 2 /*9*/ ) /*10*/ ; + var t1 = /*1*/ [||]( /*2*/ a /*3*/ : /*4*/ 1 /*5*/ , /*6*/ {|CS0103:b|} /*7*/ = /*8*/ 2 /*9*/ ) /*10*/ ; } } "; @@ -1328,7 +1588,7 @@ class Test { void Method() { - var t1 = /*1*/ new {|Rename:NewStruct|}( /*2*/ a /*3*/ : /*4*/ 1 /*5*/ , /*6*/ b /*7*/ = /*8*/ 2 /*9*/ ) /*10*/ ; + var t1 = /*1*/ new NewStruct( /*2*/ a /*3*/ : /*4*/ 1 /*5*/ , /*6*/ {|CS0103:b|} /*7*/ = /*8*/ 2 /*9*/ ) /*10*/ ; } } @@ -1374,7 +1634,7 @@ public static implicit operator NewStruct((int a, object) value) return new NewStruct(value.a, value.Item2); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1390,11 +1650,11 @@ void Method() } "; - await TestMissingInRegularAndScriptAsync(text, new TestParameters(testHost: host)); + await TestAsync(text, text, testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task ConvertMultipleNestedInstancesInSameMethod1(TestHost host) + public async Task ConvertMultipleNestedInstancesInSameMethod1_WithUsings(TestHost host) { var text = @" class Test @@ -1412,7 +1672,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, (object)new NewStruct(a: 1, default(object))); + var t1 = new NewStruct(a: 1, (object)new NewStruct(a: 1, default(object))); } } @@ -1458,18 +1718,18 @@ public static implicit operator NewStruct((int a, object b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task ConvertMultipleNestedInstancesInSameMethod2(TestHost host) + public async Task ConvertMultipleNestedInstancesInSameMethod1_WithoutUsings(TestHost host) { var text = @" class Test { void Method() { - var t1 = (a: 1, b: (object)[||](a: 1, b: default(object))); + var t1 = [||](a: 1, b: (object)(a: 1, b: default(object))); } } "; @@ -1480,7 +1740,7 @@ class Test { void Method() { - var t1 = new NewStruct(a: 1, (object)new {|Rename:NewStruct|}(a: 1, default(object))); + var t1 = new NewStruct(a: 1, (object)new NewStruct(a: 1, default(object))); } } @@ -1526,38 +1786,38 @@ public static implicit operator NewStruct((int a, object b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task RenameAnnotationOnStartingPoint(TestHost host) + public async Task ConvertMultipleNestedInstancesInSameMethod2_WithUsings(TestHost host) { var text = @" class Test { void Method() { - var t1 = (a: 1, b: 2); - var t2 = [||](a: 3, b: 4); + var t1 = (a: 1, b: (object)[||](a: 1, b: default(object))); } } "; var expected = @" +using System.Collections.Generic; + class Test { void Method() { - var t1 = new NewStruct(a: 1, b: 2); - var t2 = new {|Rename:NewStruct|}(a: 3, b: 4); + var t1 = new NewStruct(a: 1, (object)new NewStruct(a: 1, default(object))); } } internal struct NewStruct { public int a; - public int b; + public object b; - public NewStruct(int a, int b) + public NewStruct(int a, object b) { this.a = a; this.b = b; @@ -1567,54 +1827,192 @@ public override bool Equals(object obj) { return obj is NewStruct other && a == other.a && - b == other.b; + EqualityComparer.Default.Equals(b, other.b); } public override int GetHashCode() { var hashCode = 2118541809; hashCode = hashCode * -1521134295 + a.GetHashCode(); - hashCode = hashCode * -1521134295 + b.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(b); return hashCode; } - public void Deconstruct(out int a, out int b) + public void Deconstruct(out int a, out object b) { a = this.a; b = this.b; } - public static implicit operator (int a, int b)(NewStruct value) + public static implicit operator (int a, object b)(NewStruct value) { return (value.a, value.b); } - public static implicit operator NewStruct((int a, int b) value) + public static implicit operator NewStruct((int a, object b) value) { return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task CapturedMethodTypeParameters(TestHost host) + public async Task ConvertMultipleNestedInstancesInSameMethod2_WithoutUsings(TestHost host) { var text = @" -class Test where X : struct +class Test { - void Method(List x, Y[] y) where Y : class, new() + void Method() { - var t1 = [||](a: x, b: y); + var t1 = (a: 1, b: (object)[||](a: 1, b: default(object))); + } +} +"; + var expected = @" +using System.Collections.Generic; + +class Test +{ + void Method() + { + var t1 = new NewStruct(a: 1, (object)new NewStruct(a: 1, default(object))); + } +} + +internal struct NewStruct +{ + public int a; + public object b; + + public NewStruct(int a, object b) + { + this.a = a; + this.b = b; + } + + public override bool Equals(object obj) + { + return obj is NewStruct other && + a == other.a && + EqualityComparer.Default.Equals(b, other.b); + } + + public override int GetHashCode() + { + var hashCode = 2118541809; + hashCode = hashCode * -1521134295 + a.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(b); + return hashCode; + } + + public void Deconstruct(out int a, out object b) + { + a = this.a; + b = this.b; + } + + public static implicit operator (int a, object b)(NewStruct value) + { + return (value.a, value.b); + } + + public static implicit operator NewStruct((int a, object b) value) + { + return new NewStruct(value.a, value.b); + } +}"; + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task RenameAnnotationOnStartingPoint(TestHost host) + { + var text = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + var t2 = [||](a: 3, b: 4); + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = new NewStruct(a: 1, b: 2); + var t2 = new NewStruct(a: 3, b: 4); + } +} + +internal struct NewStruct +{ + public int a; + public int b; + + public NewStruct(int a, int b) + { + this.a = a; + this.b = b; + } + + public override bool Equals(object obj) + { + return obj is NewStruct other && + a == other.a && + b == other.b; + } + + public override int GetHashCode() + { + var hashCode = 2118541809; + hashCode = hashCode * -1521134295 + a.GetHashCode(); + hashCode = hashCode * -1521134295 + b.GetHashCode(); + return hashCode; + } + + public void Deconstruct(out int a, out int b) + { + a = this.a; + b = this.b; + } + + public static implicit operator (int a, int b)(NewStruct value) + { + return (value.a, value.b); + } + + public static implicit operator NewStruct((int a, int b) value) + { + return new NewStruct(value.a, value.b); + } +}"; + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task CapturedMethodTypeParameters_WithUsings(TestHost host) + { + var text = @" +using System.Collections.Generic; +class Test where X : struct +{ + void Method(System.Collections.Generic.List x, Y[] y) where Y : class, new() + { + var t1 = [||](a: x, b: y); } } "; var expected = @" +using System.Collections.Generic; class Test where X : struct { - void Method(List x, Y[] y) where Y : class, new() + void Method(System.Collections.Generic.List x, Y[] y) where Y : class, new() { - var t1 = new {|Rename:NewStruct|}(x, y); + var t1 = new NewStruct(x, y); } } @@ -1634,15 +2032,15 @@ public NewStruct(List a, Y[] b) public override bool Equals(object obj) { return obj is NewStruct other && - System.Collections.Generic.EqualityComparer>.Default.Equals(a, other.a) && - System.Collections.Generic.EqualityComparer.Default.Equals(b, other.b); + EqualityComparer>.Default.Equals(a, other.a) && + EqualityComparer.Default.Equals(b, other.b); } public override int GetHashCode() { var hashCode = 2118541809; - hashCode = hashCode * -1521134295 + System.Collections.Generic.EqualityComparer>.Default.GetHashCode(a); - hashCode = hashCode * -1521134295 + System.Collections.Generic.EqualityComparer.Default.GetHashCode(b); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(a); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(b); return hashCode; } @@ -1663,13 +2061,84 @@ public static implicit operator NewStruct((List a, Y[] b) value) } }"; - await TestExactActionSetOfferedAsync(text, new[] + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host, actions: new[] { FeaturesResources.updating_usages_in_containing_member - }, - parameters: new TestParameters(testHost: host)); + }); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task CapturedMethodTypeParameters_WithoutUsings(TestHost host) + { + var text = @" +class Test where X : struct +{ + void Method(System.Collections.Generic.List x, Y[] y) where Y : class, new() + { + var t1 = [||](a: x, b: y); + } +} +"; + var expected = @" +using System.Collections.Generic; + +class Test where X : struct +{ + void Method(System.Collections.Generic.List x, Y[] y) where Y : class, new() + { + var t1 = new NewStruct(x, y); + } +} + +internal struct NewStruct + where X : struct + where Y : class, new() +{ + public List a; + public Y[] b; + + public NewStruct(List a, Y[] b) + { + this.a = a; + this.b = b; + } + + public override bool Equals(object obj) + { + return obj is NewStruct other && + EqualityComparer>.Default.Equals(a, other.a) && + EqualityComparer.Default.Equals(b, other.b); + } + + public override int GetHashCode() + { + var hashCode = 2118541809; + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(a); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(b); + return hashCode; + } + + public void Deconstruct(out List a, out Y[] b) + { + a = this.a; + b = this.b; + } + + public static implicit operator (List a, Y[] b)(NewStruct value) + { + return (value.a, value.b); + } + + public static implicit operator NewStruct((List a, Y[] b) value) + { + return new NewStruct(value.a, value.b); + } +}"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host, actions: new[] + { + FeaturesResources.updating_usages_in_containing_member + }); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1693,7 +2162,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct1|}(a: 1, b: 2); + var t1 = new NewStruct1(a: 1, b: 2); } } @@ -1743,7 +2212,7 @@ public static implicit operator NewStruct1((int a, int b) value) return new NewStruct1(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1763,7 +2232,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, a: 2); + var t1 = new NewStruct(a: 1, a: 2); } } @@ -1809,7 +2278,82 @@ public static implicit operator NewStruct((int a, int a) value) return new NewStruct(value.a, value.a); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + var test = new VerifyCS.Test + { + TestCode = text, + FixedCode = expected, + TestHost = host, + ExpectedDiagnostics = + { + // /0/Test0.cs(6,25): error CS8127: Tuple element names must be unique. + DiagnosticResult.CompilerError("CS8127").WithSpan(6, 25, 6, 26), + }, + FixedState = + { + ExpectedDiagnostics = + { + // /0/Test0.cs(6,22): error CS7036: There is no argument given that corresponds to the required formal parameter 'a' of 'NewStruct.NewStruct(int, int)' + DiagnosticResult.CompilerError("CS7036").WithSpan(6, 22, 6, 31).WithArguments("a", "NewStruct.NewStruct(int, int)"), + // /0/Test0.cs(13,16): error CS0102: The type 'NewStruct' already contains a definition for 'a' + DiagnosticResult.CompilerError("CS0102").WithSpan(13, 16, 13, 17).WithArguments("NewStruct", "a"), + // /0/Test0.cs(15,12): error CS0171: Field 'NewStruct.a' must be fully assigned before control is returned to the caller + DiagnosticResult.CompilerError("CS0171").WithSpan(15, 12, 15, 21).WithArguments("NewStruct.a"), + // /0/Test0.cs(15,12): error CS0171: Field 'NewStruct.a' must be fully assigned before control is returned to the caller + DiagnosticResult.CompilerError("CS0171").WithSpan(15, 12, 15, 21).WithArguments("NewStruct.a"), + // /0/Test0.cs(15,33): error CS0100: The parameter name 'a' is a duplicate + DiagnosticResult.CompilerError("CS0100").WithSpan(15, 33, 15, 34).WithArguments("a"), + // /0/Test0.cs(17,14): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(17, 14, 17, 15).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(17,18): error CS0229: Ambiguity between 'int' and 'int' + DiagnosticResult.CompilerError("CS0229").WithSpan(17, 18, 17, 19).WithArguments("int", "int"), + // /0/Test0.cs(18,14): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(18, 14, 18, 15).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(18,18): error CS0229: Ambiguity between 'int' and 'int' + DiagnosticResult.CompilerError("CS0229").WithSpan(18, 18, 18, 19).WithArguments("int", "int"), + // /0/Test0.cs(24,21): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(24, 21, 24, 22).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(24,32): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(24, 32, 24, 33).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(25,21): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(25, 21, 25, 22).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(25,32): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(25, 32, 25, 33).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(31,50): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(31, 50, 31, 51).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(32,50): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(32, 50, 32, 51).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(36,17): error CS0177: The out parameter 'a' must be assigned to before control leaves the current method + DiagnosticResult.CompilerError("CS0177").WithSpan(36, 17, 36, 28).WithArguments("a"), + // /0/Test0.cs(36,17): error CS0177: The out parameter 'a' must be assigned to before control leaves the current method + DiagnosticResult.CompilerError("CS0177").WithSpan(36, 17, 36, 28).WithArguments("a"), + // /0/Test0.cs(36,48): error CS0100: The parameter name 'a' is a duplicate + DiagnosticResult.CompilerError("CS0100").WithSpan(36, 48, 36, 49).WithArguments("a"), + // /0/Test0.cs(38,9): error CS0229: Ambiguity between 'out int' and 'out int' + DiagnosticResult.CompilerError("CS0229").WithSpan(38, 9, 38, 10).WithArguments("out int", "out int"), + // /0/Test0.cs(38,18): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(38, 18, 38, 19).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(39,9): error CS0229: Ambiguity between 'out int' and 'out int' + DiagnosticResult.CompilerError("CS0229").WithSpan(39, 9, 39, 10).WithArguments("out int", "out int"), + // /0/Test0.cs(39,18): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(39, 18, 39, 19).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(42,49): error CS8127: Tuple element names must be unique. + DiagnosticResult.CompilerError("CS8127").WithSpan(42, 49, 42, 50), + // /0/Test0.cs(44,23): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(44, 23, 44, 24).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(44,32): error CS0229: Ambiguity between 'NewStruct.a' and 'NewStruct.a' + DiagnosticResult.CompilerError("CS0229").WithSpan(44, 32, 44, 33).WithArguments("NewStruct.a", "NewStruct.a"), + // /0/Test0.cs(47,59): error CS8127: Tuple element names must be unique. + DiagnosticResult.CompilerError("CS8127").WithSpan(47, 59, 47, 60), + // /0/Test0.cs(49,36): error CS0229: Ambiguity between '(int a, int a).a' and '(int a, int a).a' + DiagnosticResult.CompilerError("CS0229").WithSpan(49, 36, 49, 37).WithArguments("(int a, int a).a", "(int a, int a).a"), + // /0/Test0.cs(49,45): error CS0229: Ambiguity between '(int a, int a).a' and '(int a, int a).a' + DiagnosticResult.CompilerError("CS0229").WithSpan(49, 45, 49, 46).WithArguments("(int a, int a).a", "(int a, int a).a"), + } + } + }; + + test.Options.AddRange(PreferImplicitTypeWithInfo()); + await test.RunAsync(); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1837,7 +2381,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); Action a = () => { var t2 = new NewStruct(a: 3, b: 4); @@ -1887,7 +2431,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1918,7 +2462,7 @@ void Method() var t1 = new NewStruct(a: 1, b: 2); Action a = () => { - var t2 = new {|Rename:NewStruct|}(a: 3, b: 4); + var t2 = new NewStruct(a: 3, b: 4); }; } } @@ -1965,7 +2509,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -1993,7 +2537,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); void Goo() { var t2 = new NewStruct(a: 3, b: 4); @@ -2043,7 +2587,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -2074,7 +2618,7 @@ void Method() var t1 = new NewStruct(a: 1, b: 2); void Goo() { - var t2 = new {|Rename:NewStruct|}(a: 3, b: 4); + var t2 = new NewStruct(a: 3, b: 4); } } } @@ -2121,7 +2665,7 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -2145,7 +2689,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(1, 2); + var t1 = new NewStruct(1, 2); var t2 = new NewStruct(1, 2); var t3 = (a: 1, b: 2); var t4 = new NewStruct(item1: 1, item2: 2); @@ -2195,14 +2739,12 @@ public static implicit operator NewStruct((int, int) value) return new NewStruct(value.Item1, value.Item2); } }"; - await TestExactActionSetOfferedAsync(text, new[] + + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host, actions: new[] { FeaturesResources.updating_usages_in_containing_member, FeaturesResources.updating_usages_in_containing_type, - }, - parameters: new TestParameters(testHost: host)); - - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + }); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -2228,7 +2770,7 @@ void Method() var t1 = new NewStruct(1, 2); var t2 = new NewStruct(1, 2); var t3 = (a: 1, b: 2); - var t4 = new {|Rename:NewStruct|}(item1: 1, item2: 2); + var t4 = new NewStruct(item1: 1, item2: 2); var t5 = new NewStruct(item1: 1, item2: 2); } } @@ -2275,25 +2817,20 @@ public static implicit operator NewStruct((int Item1, int Item2) value) return new NewStruct(value.Item1, value.Item2); } }"; - await TestExactActionSetOfferedAsync(text, new[] + + await TestAsync(text, expected, options: PreferImplicitTypeWithInfo(), testHost: host, actions: new[] { FeaturesResources.updating_usages_in_containing_member, FeaturesResources.updating_usages_in_containing_type, - }, - parameters: new TestParameters(testHost: host)); - - await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), testHost: host); + }); } - protected override ParseOptions GetScriptOptions() - => null; - #endregion #region update containing type tests [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task TestCapturedTypeParameter_UpdateType(TestHost host) + public async Task TestCapturedTypeParameter_UpdateType_WithUsings(TestHost host) { var text = @" using System; @@ -2325,7 +2862,7 @@ class Test { void Method(T t) { - var t1 = new {|Rename:NewStruct|}(t, b: 2); + var t1 = new NewStruct(t, b: 2); } T t; @@ -2383,14 +2920,110 @@ public static implicit operator NewStruct((T a, int b) value) } }"; - await TestExactActionSetOfferedAsync(text, new[] + await TestAsync( + text, expected, index: 1, equivalenceKey: Scope.ContainingType.ToString(), + options: PreferImplicitTypeWithInfo(), testHost: host, actions: new[] { FeaturesResources.updating_usages_in_containing_member, FeaturesResources.updating_usages_in_containing_type - }, - parameters: new TestParameters(testHost: host)); + }); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] + public async Task TestCapturedTypeParameter_UpdateType_WithoutUsings(TestHost host) + { + var text = @" +class Test +{ + void Method(T t) + { + var t1 = [||](a: t, b: 2); + } + + T t; + void Goo() + { + var t2 = (a: t, b: 4); + } + + void Blah(T t) + { + var t2 = (a: t, b: 4); + } +} +"; + var expected = @" +using System.Collections.Generic; + +class Test +{ + void Method(T t) + { + var t1 = new NewStruct(t, b: 2); + } + + T t; + void Goo() + { + var t2 = new NewStruct(t, b: 4); + } + + void Blah(T t) + { + var t2 = (a: t, b: 4); + } +} + +internal struct NewStruct +{ + public T a; + public int b; + + public NewStruct(T a, int b) + { + this.a = a; + this.b = b; + } + + public override bool Equals(object obj) + { + return obj is NewStruct other && + EqualityComparer.Default.Equals(a, other.a) && + b == other.b; + } + + public override int GetHashCode() + { + var hashCode = 2118541809; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(a); + hashCode = hashCode * -1521134295 + b.GetHashCode(); + return hashCode; + } + + public void Deconstruct(out T a, out int b) + { + a = this.a; + b = this.b; + } - await TestInRegularAndScriptAsync(text, expected, index: 1, options: this.PreferImplicitTypeWithInfo(), testHost: host); + public static implicit operator (T a, int b)(NewStruct value) + { + return (value.a, value.b); + } + + public static implicit operator NewStruct((T a, int b) value) + { + return new NewStruct(value.a, value.b); + } +}"; + + await TestAsync( + text, expected, index: 1, equivalenceKey: Scope.ContainingType.ToString(), + options: PreferImplicitTypeWithInfo(), testHost: host, actions: new[] + { + FeaturesResources.updating_usages_in_containing_member, + FeaturesResources.updating_usages_in_containing_type + }); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -2427,7 +3060,7 @@ class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } void Goo() @@ -2486,7 +3119,9 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, index: 1, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync( + text, expected, index: 1, equivalenceKey: Scope.ContainingType.ToString(), + options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] @@ -2508,6 +3143,7 @@ partial class Test (int a, int b) Goo() { var t2 = (a: 3, b: 4); + return default; } } @@ -2526,7 +3162,7 @@ partial class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -2535,6 +3171,7 @@ partial class Test NewStruct Goo() { var t2 = new NewStruct(a: 3, b: 4); + return default; } } @@ -2588,16 +3225,15 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } }"; - await TestInRegularAndScriptAsync(text, expected, index: 1, options: this.PreferImplicitTypeWithInfo(), testHost: host); + await TestAsync( + text, expected, index: 1, equivalenceKey: Scope.ContainingType.ToString(), + options: PreferImplicitTypeWithInfo(), testHost: host); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] public async Task UpdateAllInType_MultiplePart_MultipleFile(TestHost host) { - var text = @" - - - + var text1 = @" using System; partial class Test @@ -2614,9 +3250,8 @@ void Method() { var t1 = (a: 1, b: 2); } -} - - +}"; + var text2 = @" using System; partial class Test @@ -2624,6 +3259,7 @@ partial class Test (int a, int b) Goo() { var t2 = (a: 3, b: 4); + return default; } } @@ -2633,22 +3269,16 @@ void Goo() { var t1 = (a: 1, b: 2); } -} - - -"; +}"; - var expected = @" - - - + var expected1 = @" using System; partial class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -2673,8 +3303,8 @@ public NewStruct(int a, int b) public override bool Equals(object obj) { - return obj is NewStruct other && - a == other.a && + return obj is NewStruct other && + a == other.a && b == other.b; } @@ -2701,8 +3331,8 @@ public static implicit operator NewStruct((int a, int b) value) { return new NewStruct(value.a, value.b); } -} - +}"; + var expected2 = @" using System; partial class Test @@ -2710,6 +3340,7 @@ partial class Test NewStruct Goo() { var t2 = new NewStruct(a: 3, b: 4); + return default; } } @@ -2719,11 +3350,33 @@ void Goo() { var t1 = (a: 1, b: 2); } -} - - -"; - await TestInRegularAndScriptAsync(text, expected, index: 1, options: this.PreferImplicitTypeWithInfo(), testHost: host); +}"; + + var test = new VerifyCS.Test + { + TestState = + { + Sources = + { + text1, + text2, + } + }, + FixedState = + { + Sources = + { + expected1, + expected2, + } + }, + CodeActionIndex = 1, + CodeActionEquivalenceKey = Scope.ContainingType.ToString(), + TestHost = host, + }; + + test.Options.AddRange(PreferImplicitTypeWithInfo()); + await test.RunAsync(); } #endregion update containing project tests @@ -2733,10 +3386,7 @@ void Goo() [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] public async Task UpdateAllInProject_MultiplePart_MultipleFile_WithNamespace(TestHost host) { - var text = @" - - - + var text1 = @" using System; namespace N @@ -2756,9 +3406,8 @@ void Method() var t1 = (a: 1, b: 2); } } -} - - +}"; + var text2 = @" using System; partial class Test @@ -2766,6 +3415,7 @@ partial class Test (int a, int b) Goo() { var t2 = (a: 3, b: 4); + return default; } } @@ -2775,15 +3425,9 @@ void Goo() { var t1 = (a: 1, b: 2); } -} - - -"; +}"; - var expected = @" - - - + var expected1 = @" using System; namespace N @@ -2792,7 +3436,7 @@ partial class Test { void Method() { - var t1 = new {|Rename:NewStruct|}(a: 1, b: 2); + var t1 = new NewStruct(a: 1, b: 2); } } @@ -2817,8 +3461,8 @@ public NewStruct(int a, int b) public override bool Equals(object obj) { - return obj is NewStruct other && - a == other.a && + return obj is NewStruct other && + a == other.a && b == other.b; } @@ -2846,9 +3490,8 @@ public static implicit operator NewStruct((int a, int b) value) return new NewStruct(value.a, value.b); } } -} - - +}"; + var expected2 = @" using System; partial class Test @@ -2856,6 +3499,7 @@ partial class Test N.NewStruct Goo() { var t2 = new N.NewStruct(a: 3, b: 4); + return default; } } @@ -2865,11 +3509,25 @@ void Goo() { var t1 = new N.NewStruct(a: 1, b: 2); } -} - - -"; - await TestInRegularAndScriptAsync(text, expected, index: 2, options: this.PreferImplicitTypeWithInfo(), testHost: host); +}"; + + var test = new VerifyCS.Test + { + CodeActionIndex = 2, + CodeActionEquivalenceKey = Scope.ContainingProject.ToString(), + TestHost = host, + TestState = + { + Sources = { text1, text2, }, + }, + FixedState = + { + Sources = { expected1, expected2 }, + }, + }; + + test.Options.AddRange(PreferImplicitTypeWithInfo()); + await test.RunAsync(); } #endregion @@ -2879,10 +3537,7 @@ void Goo() [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] public async Task UpdateDependentProjects_DirectDependency(TestHost host) { - var text = @" - - - + var text1 = @" using System; partial class Test @@ -2899,12 +3554,9 @@ void Method() { var t1 = (a: 1, b: 2); } -} - - - - Assembly1 - +}"; + + var text2 = @" using System; partial class Other @@ -2913,14 +3565,8 @@ void Goo() { var t1 = (a: 1, b: 2); } -} - - -"; - var expected = @" - - - +}"; + var expected1 = @" using System; partial class Test @@ -2952,8 +3598,8 @@ public NewStruct(int a, int b) public override bool Equals(object obj) { - return obj is NewStruct other && - a == other.a && + return obj is NewStruct other && + a == other.a && b == other.b; } @@ -2980,11 +3626,8 @@ public static implicit operator NewStruct((int a, int b) value) { return new NewStruct(value.a, value.b); } -} - - - Assembly1 - +}"; + var expected2 = @" using System; partial class Other @@ -2993,20 +3636,47 @@ void Goo() { var t1 = new NewStruct(a: 1, b: 2); } -} - - -"; - await TestInRegularAndScriptAsync(text, expected, index: 3, options: this.PreferImplicitTypeWithInfo(), testHost: host); +}"; + + var test = new VerifyCS.Test + { + CodeActionIndex = 3, + CodeActionEquivalenceKey = Scope.DependentProjects.ToString(), + TestHost = host, + TestState = + { + Sources = { text1 }, + AdditionalProjects = + { + ["DependencyProject"] = + { + Sources = { text2 }, + AdditionalProjectReferences = { "TestProject" }, + } + }, + }, + FixedState = + { + Sources = { expected1 }, + AdditionalProjects = + { + ["DependencyProject"] = + { + Sources = { expected2 }, + AdditionalProjectReferences = { "TestProject" }, + } + }, + }, + }; + + test.Options.AddRange(PreferImplicitTypeWithInfo()); + await test.RunAsync(); } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] public async Task UpdateDependentProjects_NoDependency(TestHost host) { - var text = @" - - - + var text1 = @" using System; partial class Test @@ -3023,11 +3693,8 @@ void Method() { var t1 = (a: 1, b: 2); } -} - - - - +}"; + var text2 = @" using System; partial class Other @@ -3036,14 +3703,8 @@ void Goo() { var t1 = (a: 1, b: 2); } -} - - -"; - var expected = @" - - - +}"; + var expected1 = @" using System; partial class Test @@ -3075,8 +3736,8 @@ public NewStruct(int a, int b) public override bool Equals(object obj) { - return obj is NewStruct other && - a == other.a && + return obj is NewStruct other && + a == other.a && b == other.b; } @@ -3103,10 +3764,9 @@ public static implicit operator NewStruct((int a, int b) value) { return new NewStruct(value.a, value.b); } -} - - - +}"; + + var expected2 = @" using System; partial class Other @@ -3115,11 +3775,33 @@ void Goo() { var t1 = (a: 1, b: 2); } -} - - -"; - await TestInRegularAndScriptAsync(text, expected, index: 3, options: this.PreferImplicitTypeWithInfo(), testHost: host); +}"; + + var test = new VerifyCS.Test + { + CodeActionIndex = 3, + CodeActionEquivalenceKey = Scope.DependentProjects.ToString(), + TestHost = host, + TestState = + { + Sources = { text1 }, + AdditionalProjects = + { + ["DependencyProject"] = { Sources = { text2 } } + }, + }, + FixedState = + { + Sources = { expected1 }, + AdditionalProjects = + { + ["DependencyProject"] = { Sources = { expected2 } } + }, + }, + }; + + test.Options.AddRange(PreferImplicitTypeWithInfo()); + await test.RunAsync(); } #endregion diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs index f945c6e925145..76b210500ddf9 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/AddExplicitCast/AddExplicitCastTests.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.CodeFixes.AddExplicitCast; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -3045,6 +3046,33 @@ class C void M() { } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddExplicitCast)] + [WorkItem(50493, "https://github.com/dotnet/roslyn/issues/50493")] + public async Task ArrayAccess() + { + await TestInRegularAndScriptAsync( + @" +class C +{ + public void M(object o) + { + var array = new int[10]; + + if (array[[||]o] > 0) {} + } +}", + @" +class C +{ + public void M(object o) + { + var array = new int[10]; + + if (array[(int)o] > 0) {} + } }"); } } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateDeconstructMethodTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateDeconstructMethodTests.cs index 61860111c260b..ff2c4baed80a9 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateDeconstructMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateMethod/GenerateDeconstructMethodTests.cs @@ -45,6 +45,33 @@ private void Deconstruct(out int x, out int y) throw new NotImplementedException(); } + void Method() + { + (int x, int y) = this; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)] + public async Task TestDeconstructionDeclaration_Simple_Record() + { + await TestInRegularAndScriptAsync( +@"record R +{ + void Method() + { + (int x, int y) = [|this|]; + } +}", +@"using System; + +record R +{ + private void Deconstruct(out int x, out int y) + { + throw new NotImplementedException(); + } + void Method() { (int x, int y) = this; diff --git a/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs b/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs index 4af6c89fe02ca..326a0fc2cdb94 100644 --- a/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs +++ b/src/EditorFeatures/CSharpTest/DocumentationComments/DocumentationCommentTests.cs @@ -38,6 +38,40 @@ class C VerifyTypingCharacter(code, expected); } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void TypingCharacter_Record() + { + var code = +@"//$$ +record R;"; + + var expected = +@"/// +/// $$ +/// +record R;"; + + VerifyTypingCharacter(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void TypingCharacter_RecordWithPositionalParameters() + { + var code = +@"//$$ +record R(string S, int I);"; + + var expected = +@"/// +/// $$ +/// +/// +/// +record R(string S, int I);"; + + VerifyTypingCharacter(code, expected); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] public void TypingCharacter_Class_NewLine() { @@ -1467,6 +1501,36 @@ class C VerifyInsertCommentCommand(code, expected); } + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void Command_Record() + { + var code = "record R$$;"; + + var expected = +@"/// +/// $$ +/// +record R;"; + + VerifyInsertCommentCommand(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] + public void Command_RecordWithPositionalParameters() + { + var code = "record R$$(string S, int I);"; + + var expected = +@"/// +/// $$ +/// +/// +/// +record R(string S, int I);"; + + VerifyInsertCommentCommand(code, expected); + } + [WorkItem(4817, "https://github.com/dotnet/roslyn/issues/4817")] [WpfFact, Trait(Traits.Feature, Traits.Features.DocumentationComments)] public void Command_Class_AutoGenerateXmlDocCommentsOff() diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index 46d7151fa5d2d..14c7ed46e2355 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -476,7 +476,7 @@ public void Constructor_BlockBodyToExpressionBody1() public void Constructor_BlockBodyToExpressionBody2() { var src1 = "class C { int x; C() { x = 1; } }"; - var src2 = "class C { int x; C() => x = 1; }"; + var src2 = "class C { int x; C() => x = 1; }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); @@ -488,7 +488,7 @@ public void Constructor_BlockBodyToExpressionBody2() public void Constructor_BlockBodyToExpressionBody3() { var src1 = "class C { int x; C() : base() { x = 1; } }"; - var src2 = "class C { int x; C() => x = 1; }"; + var src2 = "class C { int x; C() => x = 1; }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 3f5d01ca493bf..4fa5b89fd2ade 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -14,6 +14,7 @@ using Xunit; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.CSharp.UnitTests; +using System.Linq; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { @@ -1109,11 +1110,6 @@ public D(int d) { } class C : D { public C() {} - - static void Main(string[] args) - { - C c = new C(); - } }"; var src2 = @" class D @@ -1124,11 +1120,6 @@ public D(int d) { } class C : D { public C() : base(1) {} - - static void Main(string[] args) - { - C c = new C(); - } }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); @@ -1319,7 +1310,7 @@ class C public void InstanceConstructor_DeleteParameterless() { var src1 = "partial class C { public C() { System.Console.WriteLine(1); } }"; - var src2 = "partial class C { }"; + var src2 = "partial class C { }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); @@ -1495,26 +1486,12 @@ class C { int a { get; } = 1; int b { get; } = 2; - - public C() {} - - static void Main(string[] args) - { - C c = new C(); - } }"; var src2 = @" class C { int a { get { return 1; } } int b { get; } = 2; - - public C() { } - - static void Main(string[] args) - { - C c = new C(); - } }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); @@ -1655,13 +1632,6 @@ class C int a { get; } = 1; static int s { get; } = 2; int b = 2; - - public C() {} - - static void Main(string[] args) - { - C c = new C(); - } }"; var src2 = @" class C @@ -1669,13 +1639,6 @@ class C int a { get; } static int s { get; } = 2; int b = 3; - - public C() { } - - static void Main(string[] args) - { - C c = new C(); - } }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); @@ -5144,26 +5107,26 @@ public void IfBody_Update1() var src1 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - if (B()) - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + if (B()) + { + System.Console.WriteLine(0); } } }"; var src2 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - if (B()) - { - System.Console.WriteLine(1); + public static bool B() { return false; } + + public static void Main() + { + if (B()) + { + System.Console.WriteLine(1); } } }"; @@ -5179,26 +5142,26 @@ public void IfBody_Update2() var src1 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - if (B()) - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + if (B()) + { + System.Console.WriteLine(0); } } }"; var src2 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - if (!B()) - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + if (!B()) + { + System.Console.WriteLine(0); } } }"; @@ -5215,26 +5178,26 @@ public void IfBody_Update_Lambda() var src1 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - if (B(() => 1)) - { - System.Console.WriteLine(0); + public static bool B(Func a) => false; + + public static void Main() + { + if (B(() => 1)) + { + System.Console.WriteLine(0); } } }"; var src2 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - if (B(() => 2)) - { - System.Console.WriteLine(0); + public static bool B(Func a) => false; + + public static void Main() + { + if (B(() => 2)) + { + System.Console.WriteLine(0); } } }"; @@ -5250,26 +5213,26 @@ public void WhileBody_Update1() var src1 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - while (B()) - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + while (B()) + { + System.Console.WriteLine(0); } } }"; var src2 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - while (B()) - { - System.Console.WriteLine(1); + public static bool B() { return false; } + + public static void Main() + { + while (B()) + { + System.Console.WriteLine(1); } } }"; @@ -5285,26 +5248,26 @@ public void WhileBody_Update2() var src1 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - while (B()) - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + while (B()) + { + System.Console.WriteLine(0); } } }"; var src2 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - while (!B()) - { - System.Console.WriteLine(1); + public static bool B() { return false; } + + public static void Main() + { + while (!B()) + { + System.Console.WriteLine(1); } } }"; @@ -5321,26 +5284,26 @@ public void WhileBody_Update_Lambda() var src1 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - while (B(() => 1)) - { - System.Console.WriteLine(0); + public static bool B(Func a) => false; + + public static void Main() + { + while (B(() => 1)) + { + System.Console.WriteLine(0); } } }"; var src2 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - while (B(() => 2)) - { - System.Console.WriteLine(1); + public static bool B(Func a) => false; + + public static void Main() + { + while (B(() => 2)) + { + System.Console.WriteLine(1); } } }"; @@ -5356,13 +5319,13 @@ public void DoWhileBody_Update1() var src1 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - do - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + do + { + System.Console.WriteLine(0); } while (B()); } @@ -5370,15 +5333,15 @@ public static void Main() var src2 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - do - { - System.Console.WriteLine(1); + public static bool B() { return false; } + + public static void Main() + { + do + { + System.Console.WriteLine(1); } - while (B()); + while (B()); } }"; var edits = GetTopEdits(src1, src2); @@ -5393,13 +5356,13 @@ public void DoWhileBody_Update2() var src1 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - do - { - System.Console.WriteLine(0); + public static bool B() { return false; } + + public static void Main() + { + do + { + System.Console.WriteLine(0); } while (B()); } @@ -5407,15 +5370,15 @@ public static void Main() var src2 = @" class C { - public static bool B() { return false; } - - public static void Main() - { - do - { - System.Console.WriteLine(1); + public static bool B() { return false; } + + public static void Main() + { + do + { + System.Console.WriteLine(1); } - while (!B()); + while (!B()); } }"; var edits = GetTopEdits(src1, src2); @@ -5431,13 +5394,13 @@ public void DoWhileBody_Update_Lambda() var src1 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - do - { - System.Console.WriteLine(0); + public static bool B(Func a) => false; + + public static void Main() + { + do + { + System.Console.WriteLine(0); } while (B(() => 1)); } @@ -5445,15 +5408,15 @@ public static void Main() var src2 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - do - { - System.Console.WriteLine(1); + public static bool B(Func a) => false; + + public static void Main() + { + do + { + System.Console.WriteLine(1); } - while (B(() => 2)); + while (B(() => 2)); } }"; var edits = GetTopEdits(src1, src2); @@ -5495,28 +5458,28 @@ public void SwitchCase_Update1() var src1 = @" class C { - public static string F() { return null; } - - public static void Main() - { - switch (F()) - { - case ""a"": System.Console.WriteLine(0); break; - case ""b"": System.Console.WriteLine(1); break; + public static string F() { return null; } + + public static void Main() + { + switch (F()) + { + case ""a"": System.Console.WriteLine(0); break; + case ""b"": System.Console.WriteLine(1); break; } } }"; var src2 = @" class C { - public static string F() { return null; } - - public static void Main() - { - switch (F()) - { - case ""a"": System.Console.WriteLine(0); break; - case ""b"": System.Console.WriteLine(2); break; + public static string F() { return null; } + + public static void Main() + { + switch (F()) + { + case ""a"": System.Console.WriteLine(0); break; + case ""b"": System.Console.WriteLine(2); break; } } }"; @@ -5532,28 +5495,28 @@ public void SwitchCase_Update_Lambda() var src1 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - switch (B(() => 1)) - { - case ""a"": System.Console.WriteLine(0); break; - case ""b"": System.Console.WriteLine(1); break; + public static bool B(Func a) => false; + + public static void Main() + { + switch (B(() => 1)) + { + case ""a"": System.Console.WriteLine(0); break; + case ""b"": System.Console.WriteLine(1); break; } } }"; var src2 = @" class C { - public static bool B(Func a) => false; - - public static void Main() - { - switch (B(() => 2)) - { - case ""a"": System.Console.WriteLine(0); break; - case ""b"": System.Console.WriteLine(2); break; + public static bool B(Func a) => false; + + public static void Main() + { + switch (B(() => 2)) + { + case ""a"": System.Console.WriteLine(0); break; + case ""b"": System.Console.WriteLine(2); break; } } }"; @@ -5573,11 +5536,11 @@ public void SwitchWhenClause_PatternUpdate1() var src1 = @" class C { - public static int Main() - { - switch (F()) - { - case int a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case int a1 when G1(a1): case int a2 when G1(a2): return 10; @@ -5600,11 +5563,11 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F()) - { - case int a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case int a1 when G1(a1): case int a2 when G1(a2): return 10; @@ -5637,10 +5600,10 @@ public void SwitchWhenClause_PatternInsert() var src1 = @" class C { - public static int Main() - { - switch (F()) - { + public static int Main() + { + switch (F()) + { case int a2 when G1(a2): return 10; } @@ -5651,11 +5614,11 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F()) - { - case int a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case int a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5676,11 +5639,11 @@ public void SwitchWhenClause_PatternDelete() var src1 = @" class C { - public static int Main() - { - switch (F()) - { - case int a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case int a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5691,10 +5654,10 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F()) - { + public static int Main() + { + switch (F()) + { case int a2 when G1(a2): return 10; } @@ -5715,11 +5678,11 @@ public void SwitchWhenClause_WhenDelete() var src1 = @" class C { - public static int Main() - { - switch (F()) - { - case byte a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case byte a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5730,11 +5693,11 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F()) - { - case byte a1: + public static int Main() + { + switch (F()) + { + case byte a1: case int a2 when G1(a2): return 10; } @@ -5755,11 +5718,11 @@ public void SwitchWhenClause_WhenAdd() var src1 = @" class C { - public static int Main() - { - switch (F()) - { - case byte a1: + public static int Main() + { + switch (F()) + { + case byte a1: case int a2 when G1(a2): return 10; } @@ -5770,11 +5733,11 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F()) - { - case byte a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case byte a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5795,11 +5758,11 @@ public void SwitchWhenClause_WhenUpdate() var src1 = @" class C { - public static int Main() - { - switch (F()) - { - case byte a1 when G1(a1): + public static int Main() + { + switch (F()) + { + case byte a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5810,11 +5773,11 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F()) - { - case byte a1 when G1(a1 * 2): + public static int Main() + { + switch (F()) + { + case byte a1 when G1(a1 * 2): case int a2 when G1(a2): return 10; } @@ -5834,11 +5797,11 @@ public void SwitchWhenClause_UpdateGoverningExpression() var src1 = @" class C { - public static int Main() - { - switch (F(1)) - { - case int a1 when G1(a1): + public static int Main() + { + switch (F(1)) + { + case int a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5849,11 +5812,11 @@ public static int Main() var src2 = @" class C { - public static int Main() - { - switch (F(2)) - { - case int a1 when G1(a1): + public static int Main() + { + switch (F(2)) + { + case int a1 when G1(a1): case int a2 when G1(a2): return 10; } @@ -5876,11 +5839,11 @@ class C { public int X { get => 1; } - public static int F(object obj) - { - switch (obj) - { - case C { X: 1 }: + public static int F(object obj) + { + switch (obj) + { + case C { X: 1 }: return 1; } @@ -5892,11 +5855,11 @@ class C { public int X { get => 1; } - public static int F(object obj) - { - switch (obj) - { - case C { X: 2 }: + public static int F(object obj) + { + switch (obj) + { + case C { X: 2 }: return 1; } @@ -5918,11 +5881,11 @@ class C { public void Deconstruct(out int x) => x = X; - public static int F(object obj) - { - switch (obj) - { - case C ( x: 1 ): + public static int F(object obj) + { + switch (obj) + { + case C ( x: 1 ): return 1; } @@ -5934,11 +5897,11 @@ class C { public void Deconstruct(out int x) => x = X; - public static int F(object obj) - { - switch (obj) - { - case C ( x: 2 ): + public static int F(object obj) + { + switch (obj) + { + case C ( x: 2 ): return 1; } @@ -5959,15 +5922,15 @@ public void Switch_VarPattern_Update_NonLeaf() class C { public static object G() => null; - + public static int F(object obj) - { - switch (G()) - { - case var (x, y): + { + switch (G()) + { + case var (x, y): return 1; - case 2: + case 2: return 2; } @@ -5980,13 +5943,13 @@ class C public static object G() => null; public static int F(object obj) - { - switch (G()) - { - case var (x, y): + { + switch (G()) + { + case var (x, y): return 1; - case 3: + case 3: return 2; } @@ -6007,12 +5970,12 @@ public void Switch_DiscardPattern_Update_NonLeaf() class C { public static object G() => null; - + public static int F(object obj) - { - switch (G()) - { - case bool _: + { + switch (G()) + { + case bool _: return 1; } @@ -6024,11 +5987,11 @@ class C { public static object G() => null; - public static int F(object obj) - { - switch (G()) - { - case int _: + public static int F(object obj) + { + switch (G()) + { + case int _: return 1; } @@ -6049,12 +6012,12 @@ public void Switch_NoPatterns_Update_NonLeaf() class C { public static object G() => null; - + public static int F(object obj) - { - switch (G()) - { - case 1: + { + switch (G()) + { + case 1: return 1; } @@ -6066,11 +6029,11 @@ class C { public static object G() => null; - public static int F(object obj) - { - switch (G()) - { - case 2: + public static int F(object obj) + { + switch (G()) + { + case 2: return 1; } @@ -6090,12 +6053,29 @@ public static int F(object obj) [Fact] public void SwitchExpression() { - var src = @" + var src1 = @" +class C +{ + public static int Main() + { + Console.WriteLine(1); + + return F() switch + { + int a when F1() => F2(), + bool b => F3(), + _ => F4() + }; + } +}"; + var src2 = @" class C { - public static int Main() - { - return F() switch + public static int Main() + { + Console.WriteLine(2); + + return F() switch { int a when F1() => F2(), bool b => F3(), @@ -6104,8 +6084,48 @@ public static int Main() } }"; - var edits = GetTopEdits(src, src); - var active = GetActiveStatements(src, src); + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void SwitchExpression_Lambda1() + { + var src1 = @" +class C +{ + public static int Main() => F() switch { 0 => new Func(() => 1)(), _ => 2}; +}"; + var src2 = @" +class C +{ + public static int Main() => F() switch { 0 => new Func(() => 3)(), _ => 2}; +}"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void SwitchExpression_Lambda2() + { + var src1 = @" +class C +{ + public static int Main() => F() switch { i => new Func(() => i + 1)(), _ => 2}; +}"; + var src2 = @" +class C +{ + public static int Main() => F() switch { i => new Func(() => i + 3)(), _ => 2}; +}"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active); } @@ -6117,18 +6137,17 @@ public void SwitchExpression_MemberExpressionBody() var src1 = @" class C { - public static int Main() => F() switch { 0 => 1, _ => 2}; + public static int Main() => F() switch { 0 => 1, _ => 2}; }"; var src2 = @" class C { - public static int Main() => G() switch { 0 => 10, _ => 20}; + public static int Main() => G() switch { 0 => 10, _ => 20}; }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + edits.VerifyRudeDiagnostics(active); } [Fact] @@ -6138,12 +6157,12 @@ public void SwitchExpression_LambdaBody() var src1 = @" class C { - public static Func M() => () => F() switch { 0 => 1, _ => 2}; + public static Func M() => () => F() switch { 0 => 1, _ => 2}; }"; var src2 = @" class C { - public static Func M() => () => G() switch { 0 => 10, _ => 20}; + public static Func M() => () => G() switch { 0 => 10, _ => 20}; }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); @@ -6158,22 +6177,22 @@ public void SwitchExpression_QueryLambdaBody() var src1 = @" class C { - public static IEnumerable M() + public static IEnumerable M() { return from a in new[] { 1 } - where F() switch { 0 => true, _ => false} + where F() switch { 0 => true, _ => false}/**/ select a; } }"; var src2 = @" class C { - public static IEnumerable M() + public static IEnumerable M() { return - from a in new[] { 1 } - where F() switch { 0 => true, _ => false} + from a in new[] { 2 } + where F() switch { 0 => true, _ => false}/**/ select a; } }"; @@ -6190,19 +6209,18 @@ 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.SwitchExpressionUpdate, "switch", FeaturesResources.method), - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + Diagnostic(RudeEditKind.ActiveStatementUpdate, "(G() switch { 0 => 10, _ => 20 }) switch { 10 => 100 , _ => 200 }")); } [Fact] @@ -6212,7 +6230,7 @@ public void SwitchExpression_NestedInArm() var src1 = @" class C { - public static int Main() => F1() switch + public static int Main() => F1() switch { 1 when F2() switch { 0 => true, _ => false } => F3() switch { 0 => 1, _ => 2 }, _ => 20 @@ -6221,7 +6239,7 @@ 1 when F2() switch { 0 => true, _ => false } => F3() switch { var src2 = @" class C { - public static int Main() => F1() switch + public static int Main() => F1() switch { 1 when F2() switch { 0 => true, _ => false } => F3() switch { 0 => 1, _ => 2 }, _ => 20 @@ -6240,7 +6258,7 @@ public void SwitchExpression_Delete1() var src1 = @" class C { - public static int Main() + public static int Main() { return Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } }; } @@ -6248,7 +6266,7 @@ public static int Main() var src2 = @" class C { - public static int Main() + public static int Main() { return Method() switch { true => G(), _ => 1 }; } @@ -6256,8 +6274,7 @@ public static int Main() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + edits.VerifyRudeDiagnostics(active); } [Fact] @@ -6267,7 +6284,7 @@ public void SwitchExpression_Delete2() var src1 = @" class C { - public static int Main() + public static int Main() { return F1() switch { 1 => 0, _ => F2() switch { 1 => 0, _ => 2 } }; } @@ -6275,7 +6292,7 @@ public static int Main() var src2 = @" class C { - public static int Main() + public static int Main() { return F1() switch { 1 => 0, _ => 1 }; } @@ -6283,8 +6300,7 @@ public static int Main() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + edits.VerifyRudeDiagnostics(active); } [Fact] @@ -6294,7 +6310,7 @@ public void SwitchExpression_Delete3() var src1 = @" class C { - public static int Main() + public static int Main() { return F1() switch { 1 when F2() switch { 1 => true, _ => false } => 0, _ => 2 }; } @@ -6302,7 +6318,7 @@ public static int Main() var src2 = @" class C { - public static int Main() + public static int Main() { return F1() switch { 1 when F3() => 0, _ => 1 }; } @@ -6310,8 +6326,7 @@ public static int Main() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + edits.VerifyRudeDiagnostics(active); } #endregion @@ -6415,9 +6430,9 @@ static void Main(string[] args) { Goo(); } - catch + catch { - } + } } static void Goo() @@ -6500,9 +6515,9 @@ static void Main(string[] args) { Goo(); } - catch + catch { - } + } } static void Goo() @@ -6519,9 +6534,9 @@ static void Main(string[] args) { Goo(); } - catch (IOException) + catch (IOException) { - } + } } static void Goo() @@ -6777,10 +6792,10 @@ static void Main() { Console.WriteLine(1); } - finally + finally { Console.WriteLine(2); - } + } } }"; var src2 = @" @@ -6793,10 +6808,10 @@ static void Main() try { } - finally + finally { Console.WriteLine(2); - } + } } }"; var edits = GetTopEdits(src1, src2); @@ -6945,10 +6960,10 @@ static void Main(string[] args) try { } - catch + catch { Goo(); - } + } } static void Goo() @@ -6993,10 +7008,10 @@ static void Goo() try { } - catch + catch { Console.WriteLine(1); - } + } } }"; var src2 = @" @@ -7031,10 +7046,10 @@ static void Main(string[] args) try { } - catch + catch { Goo(); - } + } } static void Goo() @@ -7050,10 +7065,10 @@ static void Main(string[] args) try { } - catch (IOException) + catch (IOException) { Goo(); - } + } } static void Goo() @@ -7080,9 +7095,9 @@ static void Main(string[] args) try { } - catch (IOException) when (Goo(1)) + catch (IOException) when (Goo(1)) { - } + } } static void Goo() @@ -7098,9 +7113,9 @@ static void Main(string[] args) try { } - catch (Exception) when (Goo(1)) + catch (Exception) when (Goo(1)) { - } + } } static void Goo() @@ -7131,10 +7146,10 @@ static void Goo() try { } - catch + catch { Console.WriteLine(1); - } + } } }"; var src2 = @" @@ -7150,10 +7165,10 @@ static void Goo() try { } - catch (IOException) + catch (IOException) { Console.WriteLine(1); - } + } } } "; @@ -7175,9 +7190,9 @@ static void Main(string[] args) try { } - catch (IOException) when (Goo(1)) + catch (IOException) when (Goo(1)) { - } + } } static void Goo() @@ -7193,9 +7208,9 @@ static void Main(string[] args) try { } - catch (IOException) when (Goo(2)) + catch (IOException) when (Goo(2)) { - } + } } static void Goo() @@ -7222,9 +7237,9 @@ static void Main(string[] args) try { } - catch (IOException) when (Goo(1)) + catch (IOException) when (Goo(1)) { - } + } } }"; var src2 = @" @@ -7235,9 +7250,9 @@ static void Main(string[] args) try { } - catch (IOException) when (Goo(2)) + catch (IOException) when (Goo(2)) { - } + } } }"; var edits = GetTopEdits(src1, src2); @@ -7258,9 +7273,9 @@ static void Main(string[] args) try { } - catch (IOException) when (Goo(1)) + catch (IOException) when (Goo(1)) { - } + } } }"; var src2 = @" @@ -7271,9 +7286,9 @@ static void Main(string[] args) try { } - catch (Exception) when (Goo(1)) + catch (Exception) when (Goo(1)) { - } + } } }"; var edits = GetTopEdits(src1, src2); @@ -7381,13 +7396,13 @@ class C { static void Main(string[] args) { - try + try { } finally { Goo(); - } + } } static void Goo() @@ -7429,13 +7444,13 @@ static void Main(string[] args) static void Goo() { - try + try { } finally { Console.WriteLine(1); - } + } } }"; var src2 = @" @@ -7474,7 +7489,7 @@ static void Main(string[] args) try { } - catch (IOException) + catch (IOException) { try { @@ -7495,7 +7510,7 @@ static void Main(string[] args) finally { } - } + } } static void Goo() @@ -7511,7 +7526,7 @@ static void Main(string[] args) try { } - catch (Exception) + catch (Exception) { try { @@ -7532,7 +7547,7 @@ static void Main(string[] args) catch (Exception) { } - } + } } static void Goo() @@ -7878,9 +7893,9 @@ static void Main() { return 1 + Goo(x); } - catch + catch { - } + } }; Console.Write(f(2)); @@ -9992,97 +10007,6 @@ class C #endregion - #region Unmodified Documents - - [Fact] - public void UnmodifiedDocument1() - { - var src1 = @" -class C -{ - static void Main(string[] args) - { - try - { - } - catch (IOException e) if (e == null) - { - Goo(); - } - } - - static void Goo() - { - Console.WriteLine(1); - } -}"; - var src2 = @" -class C -{ - static void Main(string[] args) - { - try - { - } - catch (IOException e) when (e == null) - { - Goo(); - } - } - - static void Goo() - { - Console.WriteLine(1); - } -}"; - - var active = GetActiveStatements(src1, src2); - EditAndContinueValidation.VerifyUnchangedDocument(src2, active); - } - - [Fact] - public void UnmodifiedDocument_BadSpans1() - { - var src1 = @" -class C -{ - const int a = 1; - - static void Main(string[] args) - { - Goo(); - } - - static void Goo() - { - Console.WriteLine(1); - } -} - - -"; - var src2 = @" -class C -{ - const int a = 1; - - static void Main(string[] args) - { - Goo(); - } - - static void Goo() - { - Console.WriteLine(1); - } -}"; - - var active = GetActiveStatements(src1, src2); - EditAndContinueValidation.VerifyUnchangedDocument(src2, active); - } - - #endregion - #region C# 7.0 [Fact] @@ -10549,13 +10473,13 @@ public void InsertDeleteMethod_Inactive() new[] { DocumentResults( - activeStatements: GetActiveStatements(srcA1, srcA2), + activeStatements: GetActiveStatements(srcA1, srcA2, path: "0"), semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F2")), }), DocumentResults( - activeStatements: GetActiveStatements(srcB1, srcB2)) + activeStatements: GetActiveStatements(srcB1, srcB2, path: "1")) }); } @@ -10568,26 +10492,332 @@ public void InsertDeleteMethod_Active() 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 { }"; + var srcB2 = "partial class C { }"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults( - activeStatements: GetActiveStatements(srcA1, srcA2), + activeStatements: GetActiveStatements(srcA1, srcA2, path: "0"), semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), }), DocumentResults( - activeStatements: GetActiveStatements(srcB1, srcB2), + activeStatements: GetActiveStatements(srcB1, srcB2, path: "1"), diagnostics: new[] { Diagnostic(RudeEditKind.DeleteActiveStatement, "partial class C", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) }); } #endregion + #region Records + + [Fact] + public void Record() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } = 1; +}"; + var src2 = @" +record C(int X) +{ + public int X { get; init; } = 2; +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void Record_Constructor() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } = 1; + + static void Main(string[] args) + { + var x = new C(1); + } +}"; + var src2 = @" +record C(int X) +{ + public int X { get; init; } = 2; + + static void Main(string[] args) + { + var x = new C(1); + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void Record_FieldInitializer_Lambda2() + { + var src1 = @" +record C(int X) +{ + Func> a = z => () => z + 1; + + static void Main(string[] args) + { + new C(1).a(1)(); + } +}"; + var src2 = @" +record C(int X) +{ + Func> a = z => () => z + 2; + + static void Main(string[] args) + { + new C(1).a(1)(); + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + #endregion + + #region Line Mapping + + /// + /// Validates that changes in #line directives produce semantic updates of the containing method. + /// + [Fact] + public void LineMapping_ChangeLineNumber_WithinMethod() + { + var src1 = @" +class C +{ +#line 1 ""a"" + static void F() + { + A(); +#line 5 ""b"" + B(1); + B(); +#line 2 ""c"" + C(); + C(); +#line hidden + D(); +#line default + E(); + } +}"; + var src2 = @" +class C +{ +#line 1 ""a"" + static void F() + { + A(); +#line 5 ""b"" + B(2); + B(); +#line 9 ""c"" + C(); + C(); +#line hidden + D(); +#line default + E(); + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void LineMapping_ChangeFilePath() + { + var src1 = @" +class C +{ + static void F() + { + A(); +#line 1 ""a"" + B(); + } +}"; + var src2 = @" +class C +{ + static void F() + { + A(); +#line 1 ""b"" + B(); + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdateAroundActiveStatement, "B();", string.Format(FeaturesResources._0_directive, "line"))); + } + + [Fact] + public void LineMapping_ExceptionRegions_ChangeLineNumber() + { + var src1 = @" +class C +{ + static void Main() + { + try + { + try + { + Goo(); + } +#line 20 ""a"" + catch (E1 e) { } +#line default + } +#line 20 ""b"" + catch (E2 e) { } +#line default + } +}"; + var src2 = @" +class C +{ + static void Main() + { + try + { + try + { + Goo(); + } +#line 20 ""a"" + catch (E1 e) { } +#line default + } +#line 30 ""b"" + catch (E2 e) { } +#line default + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact, WorkItem(52971, "https://github.com/dotnet/roslyn/issues/52971")] + public void LineMapping_ExceptionRegions_ChangeFilePath() + { + var src1 = @" +class C +{ + static void Main() + { + try + { + try + { + Goo(); + } +#line 20 ""a"" + catch (E1 e) { } +#line default + } +#line 20 ""b"" + catch (E2 e) { } +#line default + } +}"; + var src2 = @" +class C +{ + static void Main() + { + try + { + try + { + Goo(); + } +#line 20 ""a"" + catch (E1 e) { } +#line default + } +#line 20 ""c"" + catch (E2 e) { } +#line default + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // TODO: rude edit should be reported + edits.VerifyRudeDiagnostics(active); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/52971"), WorkItem(52971, "https://github.com/dotnet/roslyn/issues/52971")] + public void LineMapping_ExceptionRegions_LineChange_MultipleMappedFiles() + { + var src1 = @" +class C +{ + static void Main() + { + try + { + Goo(); + } +#line 20 ""a"" + catch (E1 e) { } +#line 20 ""b"" + catch (E2 e) { } +#line default + } +}"; + var src2 = @" +class C +{ + static void Main() + { + try + { + Goo(); + } +#line 20 ""a"" + catch (E1 e) { } +#line 30 ""b"" + catch (E2 e) { } +#line default + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // TODO: rude edit? + edits.VerifyRudeDiagnostics(active); + } + + #endregion + #region Misc [Fact] @@ -10641,13 +10871,14 @@ public static void F() } }"; var edits = GetTopEdits(src1, src2); - var active = GetActiveStatements(src1, src2); - - active.OldStatements[0] = active.OldStatements[0].WithFlags(ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame); - active.OldStatements[1] = active.OldStatements[1].WithFlags(ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsNonLeafFrame); - active.OldStatements[2] = active.OldStatements[2].WithFlags(ActiveStatementFlags.IsLeafFrame); - active.OldStatements[3] = active.OldStatements[3].WithFlags(ActiveStatementFlags.IsNonLeafFrame); - active.OldStatements[4] = active.OldStatements[4].WithFlags(ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame); + var active = GetActiveStatements(src1, src2, flags: new[] + { + ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame, + ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsNonLeafFrame, + ActiveStatementFlags.IsLeafFrame, + ActiveStatementFlags.IsNonLeafFrame, + ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame + }); edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementUpdate, "Console.WriteLine(10);"), @@ -10675,9 +10906,10 @@ public static void F() } }"; var edits = GetTopEdits(src1, src2); - var active = GetActiveStatements(src1, src2); - - active.OldStatements[0] = active.OldStatements[0].WithFlags(ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame); + var active = GetActiveStatements(src1, src2, flags: new[] + { + ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame + }); edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementDelete, "{", FeaturesResources.code)); @@ -10702,9 +10934,10 @@ public static void F() } }"; var edits = GetTopEdits(src1, src2); - var active = GetActiveStatements(src1, src2); - - active.OldStatements[0] = active.OldStatements[0].WithFlags(ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame); + var active = GetActiveStatements(src1, src2, flags: new[] + { + ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame + }); edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs index c489967bc5bf5..c2b86945024a5 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; @@ -158,21 +159,21 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki encService.GetBaseActiveStatementSpansImpl = (_, documentIds) => ImmutableArray.Create( ImmutableArray.Create( - (span11, ActiveStatementFlags.IsNonLeafFrame), - (span12, ActiveStatementFlags.IsLeafFrame)), - ImmutableArray<(LinePositionSpan, ActiveStatementFlags)>.Empty); + new ActiveStatementSpan(0, span11, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null), + new ActiveStatementSpan(1, span12, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null)), + ImmutableArray.Empty); encService.GetAdjustedActiveStatementSpansImpl = (document, _) => document.Name switch { "1.cs" => ImmutableArray.Create( - (span21, ActiveStatementFlags.IsNonLeafFrame), - (span22, ActiveStatementFlags.IsLeafFrame)), - "2.cs" => ImmutableArray<(LinePositionSpan, ActiveStatementFlags)>.Empty, + new ActiveStatementSpan(0, span21, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null), + new ActiveStatementSpan(1, span22, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null)), + "2.cs" => ImmutableArray.Empty, _ => throw ExceptionUtilities.Unreachable }; - var testDocument1 = new TestHostDocument(text: source1, displayName: "1.cs", exportProvider: workspace.ExportProvider); - var testDocument2 = new TestHostDocument(text: source2, displayName: "2.cs", exportProvider: workspace.ExportProvider); + var testDocument1 = new TestHostDocument(text: source1, displayName: "1.cs", exportProvider: workspace.ExportProvider, filePath: "1.cs"); + var testDocument2 = new TestHostDocument(text: source2, displayName: "2.cs", exportProvider: workspace.ExportProvider, filePath: "2.cs"); workspace.AddTestProject(new TestHostProject(workspace, documents: new[] { testDocument1, testDocument2 })); // opens the documents @@ -199,12 +200,12 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki { $"V0 →←@[10..15): IsNonLeafFrame", $"V0 →←@[20..25): IsLeafFrame" - }, spans1[document1.Id].Select(s => $"{s.Span}: {s.Flags}")); + }, spans1[document1.FilePath].Select(s => $"{s.Span}: {s.Flags}")); - var spans2 = await trackingSession.GetSpansAsync(document1, CancellationToken.None); - AssertEx.Equal(new[] { "[10..15)", "[20..25)" }, spans2.Select(s => s.ToString())); + var spans2 = await trackingSession.GetSpansAsync(solution, document1.Id, document1.FilePath, CancellationToken.None); + AssertEx.Equal(new[] { "(0,10)-(0,15)", "(0,20)-(0,25)" }, spans2.Select(s => s.LineSpan.ToString())); - var spans3 = await trackingSession.GetSpansAsync(document2, CancellationToken.None); + var spans3 = await trackingSession.GetSpansAsync(solution, document2.Id, document2.FilePath, CancellationToken.None); Assert.Empty(spans3); } @@ -226,11 +227,11 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki { $"V0 →←@[11..16): IsNonLeafFrame", $"V0 →←@[21..26): IsLeafFrame" - }, spans5[document1.Id].Select(s => $"{s.Span}: {s.Flags}")); + }, spans5[document1.FilePath].Select(s => $"{s.Span}: {s.Flags}")); } // we are not able to determine active statements in a document: - encService.GetAdjustedActiveStatementSpansImpl = (_, _) => default; + encService.GetAdjustedActiveStatementSpansImpl = (_, _) => ImmutableArray.Empty; var spans6 = await trackingSession.GetAdjustedTrackingSpansAsync(document1, snapshot1, CancellationToken.None); AssertEx.Equal(new[] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs index 2a5edfe98239a..d4f26f5ad2fdc 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/BreakpointSpansTests.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.EditAndContinue; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -57,19 +58,34 @@ private static void Test(string markup, bool isMissing, bool isLine, ParseOption } } - private static void TestAll(string markup) + /// + /// Verifies all breakpoint spans of the declaration node marked by $$ in + /// and it's breakpoint span envelope (span that contains all breakpoint span of the declaration). + /// + /// Only test declarations that have a single possible body (e.g. , + /// not or ). + /// + private static void VerifyAllSpansInDeclaration(string markup) + where TDeclaration : SyntaxNode { MarkupTestFile.GetPositionAndSpans(markup, out var source, out var position, out ImmutableArray expectedSpans); var tree = SyntaxFactory.ParseSyntaxTree(source); var root = tree.GetRoot(); + var declarationNode = root.FindToken(position).Parent.FirstAncestorOrSelf(); - var actualSpans = GetBreakpointSequence(root, position).ToArray(); + var actualSpans = GetBreakpointSequence(declarationNode, position).ToArray(); AssertEx.Equal(expectedSpans, actualSpans, itemSeparator: "\r\n", itemInspector: span => "[|" + source.Substring(span.Start, span.Length) + "|]"); + + var expectedEnvelope = expectedSpans.IsEmpty ? default : TextSpan.FromBounds(expectedSpans[0].Start, expectedSpans[^1].End); + Assert.NotNull(declarationNode); + + var actualEnvelope = BreakpointSpans.GetEnvelope(declarationNode); + Assert.Equal(expectedEnvelope, actualEnvelope); } public static IEnumerable GetBreakpointSequence(SyntaxNode root, int position) @@ -91,11 +107,11 @@ public static IEnumerable GetBreakpointSequence(SyntaxNode root, int p [Fact] public void GetBreakpointSequence1() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - void Goo() - $$[|{|] + $$void Goo() + [|{|] [|int d = 5;|] [|int a = 1|], [|b = 2|], [|c = 3|]; for ([|int i = 0|], [|j = 1|], [|k = 2|]; [|i < 10|]; [|i++|], [|j++|], [|k--|]) @@ -125,11 +141,11 @@ void Goo() [Fact] public void GetBreakpointSequence2() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - void Goo() - $$[|{|] + $$void Goo() + [|{|] do [|{|] label: @@ -151,11 +167,11 @@ void Goo() [Fact] public void GetBreakpointSequence3() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - int Goo() - $$[|{|] + $$int Goo() + [|{|] [|switch(a)|] { case 1: @@ -180,11 +196,11 @@ int Goo() [Fact] public void GetBreakpointSequence4() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - IEnumerable Goo() - $$[|{|] + $$IEnumerable Goo() + [|{|] [|yield return 1;|] [|foreach|] ([|var f|] [|in|] [|Goo()|]) [|{|] @@ -204,22 +220,22 @@ IEnumerable Goo() [Fact] public void GetBreakpointSequence5() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - IEnumerable Goo() - $$[|{|][|while(t)|][|{|][|}|][|}|] + $$IEnumerable Goo() + [|{|][|while(t)|][|{|][|}|][|}|] }"); } [Fact] public void GetBreakpointSequence6() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - IEnumerable Goo() - $$[|{|] + $$IEnumerable Goo() + [|{|] checked [|{|] const int a = 1; @@ -239,14 +255,16 @@ IEnumerable Goo() }"); } + #region Switch Expression + [Fact] - public void GetBreakpointSequence7() + public void SwitchExpression_All() { - TestAll(@" + VerifyAllSpansInDeclaration(@" class C { - IEnumerable Goo() - $$[|{|] + $$IEnumerable Goo() + [|{|] [|_ = M2( c switch @@ -478,6 +496,10 @@ void Goo() }"); } + #endregion + + #region For and ForEach + [Fact] public void ForStatementInitializer1a() { @@ -634,6 +656,8 @@ void Goo() }"); } + #endregion + #region Lambdas [Fact] @@ -1237,6 +1261,18 @@ public void GroupBy_NoLambda3() #endregion + #region Field and Veriable Declarators + + [Fact] + public void FieldDeclarator_WithoutInitializer_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + int $$i, j; +}"); + } + [Fact] public void FieldDeclarator_WithoutInitializer1() { @@ -1258,7 +1294,72 @@ public void FieldDeclarator_WithoutInitializer2() } [Fact] - public void FieldDeclarator1() + public void FieldDeclarator_SingleVariable_Initializer_All1() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + private int $$i; +}"); + } + + [Fact] + public void FieldDeclarator_SingleVariable_Initializer_All2() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + [|int $$i = 0;|] +}"); + } + [Fact] + public void FieldDeclarator_SingleVariable_Initializer_All3() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + [|private int $$i = 0;|] +}"); + } + + [Fact] + public void FieldDeclarator_MultiVariable_Initializer_All1() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + [|private int $$i = 0|], j = 2; +}"); + } + + [Fact] + public void FieldDeclarator_MultiVariable_Initializer_All2() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + [|int $$i = 0|], j = 2; +}"); + } + + [Fact] + public void FieldDeclarator_MultiVariable_Initializer_All3() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + private int i = 0, [|$$j = 0|]; +}"); + } + + [Fact] + public void FieldDeclarator_Initializer1() { TestSpan( @"class C @@ -1268,7 +1369,7 @@ public void FieldDeclarator1() } [Fact] - public void FieldDeclarator2() + public void FieldDeclarator_Initializer2() { TestSpan( @"class C @@ -1278,7 +1379,7 @@ public void FieldDeclarator2() } [Fact] - public void FieldDeclarator3() + public void FieldDeclarator_Initializer3() { TestSpan( @"class C @@ -1289,7 +1390,7 @@ public void FieldDeclarator3() } [Fact] - public void FieldDeclarator4() + public void FieldDeclarator_Initializer4() { TestSpan( @"class C @@ -1299,7 +1400,7 @@ public void FieldDeclarator4() } [Fact] - public void FieldDeclarator5() + public void FieldDeclarator_Initializer5() { TestSpan( @"class C @@ -1320,6 +1421,17 @@ public void ConstVariableDeclarator1() public void ConstVariableDeclarator2() => TestMissing("class C { void Goo() { $$const int a = 1; } }"); + [Fact] + public void ConstFieldVariableDeclarator_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Goo] + private const int i = 0, j, $$k = 0; +}"); + } + [Fact] public void ConstFieldVariableDeclarator0() => TestMissing("class C { const int a = $$1; }"); @@ -1705,6 +1817,8 @@ public void EventFieldDeclarator8() }"); } + #endregion + [Fact] public void EventAccessorAdd() => TestSpan("class C { eve$$nt Action Goo { add [|{|] } remove { } } }"); @@ -4133,8 +4247,20 @@ public void MissingOnMethod() }"); } + #region Constructors + [Fact] - public void InstanceConstructor_NoInitializer() + public void InstanceConstructor_NoInitializer_BlockBody_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Attribute1, Attribute2][Attribute3][|$$public C()|] [|{|] [|}|] +}"); + } + + [Fact] + public void InstanceConstructor_NoInitializer_BlockBody() { // a sequence point for base constructor call TestSpan( @@ -4146,6 +4272,90 @@ public void InstanceConstructor_NoInitializer() }"); } + [Fact] + public void InstanceConstructor_NoInitializer_ExpressionBody_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Attribute1, Attribute2][Attribute3][|$$public C()|] => [|x = 1|]; +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody1() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + [|pub$$lic C()|] => F(); +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody2() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + [|public C()|] $$=> x = 1); +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody3() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + public C() =$$> [|x = 1|]; +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody4() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + public C() => [|$$x = 1|]; +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody5() + { + TestSpan( +@"class C +{ + public C() => [|x =$$ 1|]; +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody6() + { + TestSpan( +@"class C +{ + public C() => [|x = 1|]$$; +}"); + } + + [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] + public void InstanceConstructor_NoInitializer_ExpressionBody7() + { + TestSpan( +@"class C +{ + public C() => [|x = 1|];$$ +}"); + } + [Fact] public void InstanceConstructor_NoInitializer_Attributes() { @@ -4163,7 +4373,17 @@ public void InstanceConstructor_NoInitializer_Attributes() } [Fact] - public void InstanceConstructor_BaseInitializer() + public void InstanceConstructor_BaseInitializer_BlockBody_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Attribute1, Attribute2][Attribute3]$$public C() : [|base(42)|] [|{|][|}|] +}"); + } + + [Fact] + public void InstanceConstructor_BaseInitializer_BlockBody() { // a sequence point for base constructor call TestSpan( @@ -4176,6 +4396,64 @@ public void InstanceConstructor_BaseInitializer() }"); } + [Fact] + public void InstanceConstructor_BaseInitializer_ExpressionBody_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Attribute1, Attribute2][Attribute3]$$public C() : [|base(42)|] => [|x = 1|]; +}"); + } + + [Fact] + public void InstanceConstructor_BaseInitializer_ExpressionBody1() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + pub$$lic C() : [|base(42)|] => F(); + +}"); + } + + [Fact] + public void InstanceConstructor_BaseInitializer_ExpressionBody2() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + public C() : [|base(42)|] $$=> F(); + +}"); + } + + [Fact] + public void InstanceConstructor_BaseInitializer_ExpressionBody3() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + public C() : base(42) =$$> [|F()|]; + +}"); + } + + [Fact] + public void InstanceConstructor_BaseInitializer_ExpressionBody4() + { + // a sequence point for base constructor call + TestSpan( +@"class C +{ + public C() : base(42) => [|$$F()|]; + +}"); + } + [Fact] public void InstanceConstructor_ThisInitializer() { @@ -4191,7 +4469,17 @@ public void InstanceConstructor_ThisInitializer() } [Fact] - public void StaticConstructor() + public void StaticConstructor_BlockBody_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Attribute1, Attribute2][Attribute3]$$static public C() [|{|] [|}|] +}"); + } + + [Fact] + public void StaticConstructor_BlockBody() { TestSpan( @"class C @@ -4202,6 +4490,26 @@ public void StaticConstructor() }"); } + [Fact] + public void StaticConstructor_ExpressionBody_All() + { + VerifyAllSpansInDeclaration( +@"class C +{ + [Attribute1, Attribute2][Attribute3]$$static public C() => [|x = 1|]; +}"); + } + + [Fact] + public void StaticConstructor_ExpressionBody() + { + TestSpan( +@"class C +{ + static C() => [|$$F()|]; +}"); + } + [Fact] public void InstanceConstructorInitializer() { @@ -4244,6 +4552,8 @@ public void OnStaticConstructor() }"); } + #endregion + [Fact] public void OnDestructor() { @@ -4688,56 +4998,6 @@ public void OnAccessorExpressionBody8() }"); } - [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] - public void OnCtorExpressionBody1() - { - TestSpan( -@"class C -{ -$$ public C() => [|x = 1|]; -}"); - } - - [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] - public void OnCtorExpressionBody2() - { - TestSpan( -@"class C -{ - public C() => $$[|x = 1|]; -}"); - } - - [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] - public void OnCtorExpressionBody3() - { - TestSpan( -@"class C -{ - public C() => [|x =$$ 1|]; -}"); - } - - [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] - public void OnCtorExpressionBody4() - { - TestSpan( -@"class C -{ - public C() => [|x = 1|]$$; -}"); - } - - [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] - public void OnCtorExpressionBody5() - { - TestSpan( -@"class C -{ - public C() => [|x = 1|];$$ -}"); - } - [Fact, WorkItem(14438, "https://github.com/dotnet/roslyn/issues/14438")] public void OnDtorExpressionBody1() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index a6f68489a40e9..36ccd6cf6a05e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; -using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -123,6 +122,12 @@ public void ErrorSpans_TopLevel() [A, B] /**/public abstract partial class C/**/ { } +[A, B] +/**/public abstract partial record R/**/ { } + +[A, B] +/**/public abstract partial record struct R/**/ { } + /**/interface I/**/ : J, K, L { } [A] @@ -253,8 +258,7 @@ class C { public static void Main() { - // comment - System.Console.WriteLine(1); + /* comment */ System.Console.WriteLine(1); } } "; @@ -280,18 +284,30 @@ public static void Main() var newText = await newDocument.GetTextAsync(); var newSyntaxRoot = await newDocument.GetSyntaxRootAsync(); - const string oldStatementSource = "System.Console.WriteLine(1);"; + var oldStatementSource = "System.Console.WriteLine(1);"; var oldStatementPosition = source1.IndexOf(oldStatementSource, StringComparison.Ordinal); var oldStatementTextSpan = new TextSpan(oldStatementPosition, oldStatementSource.Length); var oldStatementSpan = oldText.Lines.GetLinePositionSpan(oldStatementTextSpan); var oldStatementSyntax = oldSyntaxRoot.FindNode(oldStatementTextSpan); - var baseActiveStatements = ImmutableArray.Create(ActiveStatementsDescription.CreateActiveStatement(ActiveStatementFlags.IsLeafFrame, oldStatementSpan, DocumentId.CreateNewId(ProjectId.CreateNewId()))); + var baseActiveStatements = new ActiveStatementsMap( + ImmutableDictionary.CreateRange(new[] + { + KeyValuePairUtil.Create(newDocument.FilePath, ImmutableArray.Create( + new ActiveStatement( + ordinal: 0, + ActiveStatementFlags.IsLeafFrame, + new SourceFileSpan(newDocument.FilePath, oldStatementSpan), + instructionId: default))) + }), + ActiveStatementsMap.Empty.InstructionMap); + var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.True(result.HasChanges); + var syntaxMap = result.SemanticEdits[0].SyntaxMap; Assert.NotNull(syntaxMap); @@ -332,10 +348,10 @@ public static void Main() var documentId = oldDocument.Id; var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.True(result.HasChanges); Assert.True(result.HasChangesAndErrors); @@ -358,10 +374,10 @@ public static void Main() using var workspace = TestWorkspace.CreateCSharp(source, composition: s_composition); var oldProject = workspace.CurrentSolution.Projects.Single(); var oldDocument = oldProject.Documents.Single(); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -399,10 +415,10 @@ public static void Main() var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -432,10 +448,10 @@ public static void Main() var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -483,10 +499,10 @@ public static void Main() var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.True(result.HasChanges); Assert.True(result.HasChangesAndErrors); @@ -516,10 +532,10 @@ public static void Main() var oldDocument = oldProject.Documents.Single(); var documentId = oldDocument.Id; - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -559,10 +575,10 @@ public static void Main() var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.True(result.HasChanges); @@ -602,10 +618,10 @@ public static void Main(Bar x) var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); Assert.True(result.HasChanges); @@ -657,12 +673,12 @@ public class D var changedDocuments = changes.GetChangedDocuments().Concat(changes.GetAddedDocuments()); var result = new List(); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); foreach (var changedDocumentId in changedDocuments) { - result.Add(await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, CancellationToken.None)); + result.Add(await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None)); } Assert.True(result.IsSingle()); @@ -709,12 +725,12 @@ class D var changedDocuments = changes.GetChangedDocuments().Concat(changes.GetAddedDocuments()); var result = new List(); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(); foreach (var changedDocumentId in changedDocuments) { - result.Add(await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, CancellationToken.None)); + result.Add(await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None)); } Assert.True(result.IsSingle()); @@ -738,7 +754,7 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) workspace.TryApplyChanges(newSolution); - var baseActiveStatements = ImmutableArray.Create(); + var baseActiveStatements = ActiveStatementsMap.Empty; var analyzer = new CSharpEditAndContinueAnalyzer(node => { @@ -748,7 +764,7 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) } }); - var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None); var expectedDiagnostic = outOfMemory ? $"ENC0089: {string.Format(FeaturesResources.Modifying_source_file_will_prevent_the_debug_session_from_continuing_because_the_file_is_too_big, "src.cs")}" : @@ -759,5 +775,44 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) AssertEx.Equal(new[] { expectedDiagnostic }, result.RudeEditErrors.Select(d => d.ToDiagnostic(newSyntaxTree)) .Select(d => $"{d.Id}: {d.GetMessage().Split(new[] { Environment.NewLine }, StringSplitOptions.None).First()}")); } + + [Fact] + public async Task AnalyzeDocumentAsync_NotSupportedByRuntime() + { + var source1 = @" +class C +{ + public static void Main() + { + System.Console.WriteLine(1); + } +} +"; + var source2 = @" +class C +{ + public static void Main() + { + System.Console.WriteLine(2); + } +} +"; + + using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); + + var oldSolution = workspace.CurrentSolution; + var oldProject = oldSolution.Projects.Single(); + var documentId = oldProject.Documents.Single().Id; + var newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)); + var newDocument = newSolution.GetDocument(documentId); + + var analyzer = new CSharpEditAndContinueAnalyzer(); + + var capabilities = EditAndContinueCapabilities.None; + + var result = await analyzer.AnalyzeDocumentAsync(oldProject, ActiveStatementsMap.Empty, newDocument, ImmutableArray.Empty, capabilities, CancellationToken.None); + + Assert.Equal(RudeEditKind.NotSupportedByRuntime, result.RudeEditErrors.Single().Kind); + } } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs index 206cbef8f3b8c..4b417edbe5a0a 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs @@ -28,9 +28,6 @@ public CSharpEditAndContinueTestHelpers(Action faultInjector = null) 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)); - public override SyntaxNode FindNode(SyntaxNode root, TextSpan span) { var result = root.FindToken(span.Start).Parent; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index b45ad8c484eaa..739938b3d8fd4 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -2,37 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue; -using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; +using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { internal static class EditAndContinueValidation { - internal static void VerifyUnchangedDocument( - string source, - ActiveStatementsDescription description) + internal static void VerifyRudeDiagnostics( + this EditScript editScript, + params RudeEditDiagnosticDescription[] expectedDiagnostics) { - new CSharpEditAndContinueTestHelpers().VerifyUnchangedDocument( - ActiveStatementsDescription.ClearTags(source), - description.OldStatements, - description.NewSpans, - description.NewRegions); + VerifySemanticDiagnostics( + editScript, + ActiveStatementsDescription.Empty, + capabilities: null, + expectedDiagnostics); } internal static void VerifyRudeDiagnostics( this EditScript editScript, + EditAndContinueCapabilities? capabilities = null, params RudeEditDiagnosticDescription[] expectedDiagnostics) { - VerifyRudeDiagnostics(editScript, ActiveStatementsDescription.Empty, expectedDiagnostics); + VerifySemanticDiagnostics( + editScript, + ActiveStatementsDescription.Empty, + capabilities, + expectedDiagnostics); } internal static void VerifyRudeDiagnostics( @@ -43,6 +46,7 @@ internal static void VerifyRudeDiagnostics( VerifySemanticDiagnostics( editScript, description, + capabilities: null, expectedDiagnostics); } @@ -51,6 +55,21 @@ internal static void VerifyLineEdits( IEnumerable expectedLineEdits, IEnumerable expectedNodeUpdates, params RudeEditDiagnosticDescription[] expectedDiagnostics) + { + Assert.NotEmpty(expectedLineEdits); + + VerifyLineEdits( + editScript, + new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, expectedLineEdits.ToImmutableArray()) }, + expectedNodeUpdates, + expectedDiagnostics); + } + + internal static void VerifyLineEdits( + this EditScript editScript, + IEnumerable expectedLineEdits, + IEnumerable expectedNodeUpdates, + params RudeEditDiagnosticDescription[] expectedDiagnostics) { new CSharpEditAndContinueTestHelpers().VerifyLineEdits( editScript, @@ -68,14 +87,24 @@ internal static void VerifySemanticDiagnostics( new[] { new DocumentAnalysisResultsDescription(diagnostics: expectedDiagnostics) }); } + internal static void VerifySemanticDiagnostics( + this EditScript editScript, + ActiveStatementsDescription activeStatements, + params RudeEditDiagnosticDescription[] expectedDiagnostics) + { + VerifySemanticDiagnostics(editScript, activeStatements, capabilities: null, expectedDiagnostics); + } + internal static void VerifySemanticDiagnostics( this EditScript editScript, ActiveStatementsDescription activeStatements, + EditAndContinueCapabilities? capabilities = null, params RudeEditDiagnosticDescription[] expectedDiagnostics) { VerifySemantics( new[] { editScript }, - new[] { new DocumentAnalysisResultsDescription(activeStatements: activeStatements, diagnostics: expectedDiagnostics) }); + new[] { new DocumentAnalysisResultsDescription(activeStatements: activeStatements, diagnostics: expectedDiagnostics) }, + capabilities: capabilities); } internal static void VerifySemanticDiagnostics( @@ -92,28 +121,31 @@ internal static void VerifySemanticDiagnostics( internal static void VerifySemantics( this EditScript editScript, ActiveStatementsDescription activeStatements, - SemanticEditDescription[] expectedSemanticEdits) + SemanticEditDescription[] expectedSemanticEdits, + EditAndContinueCapabilities? capabilities = null) { VerifySemantics( new[] { editScript }, - new[] { new DocumentAnalysisResultsDescription(activeStatements, semanticEdits: expectedSemanticEdits) }); + new[] { new DocumentAnalysisResultsDescription(activeStatements, semanticEdits: expectedSemanticEdits) }, + capabilities: capabilities); } internal static void VerifySemantics( this EditScript editScript, params SemanticEditDescription[] expectedSemanticEdits) { - VerifySemantics(editScript, ActiveStatementsDescription.Empty, expectedSemanticEdits); + VerifySemantics(editScript, ActiveStatementsDescription.Empty, expectedSemanticEdits, capabilities: null); } internal static void VerifySemantics( EditScript[] editScripts, DocumentAnalysisResultsDescription[] expected, - TargetFramework[]? targetFrameworks = null) + TargetFramework[]? targetFrameworks = null, + EditAndContinueCapabilities? capabilities = null) { foreach (var targetFramework in targetFrameworks ?? new[] { TargetFramework.NetStandard20, TargetFramework.NetCoreApp }) { - new CSharpEditAndContinueTestHelpers().VerifySemantics(editScripts, targetFramework, expected); + new CSharpEditAndContinueTestHelpers().VerifySemantics(editScripts, targetFramework, expected, capabilities); } } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 329d1b71c8b5f..fd1d4962d4c04 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UnitTests; @@ -13,6 +14,7 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests @@ -52,8 +54,11 @@ internal static DocumentAnalysisResultsDescription DocumentResults( RudeEditDiagnosticDescription[]? diagnostics = null) => new(activeStatements, semanticEdits, diagnostics); - private static SyntaxTree ParseSource(string source) - => new CSharpEditAndContinueTestHelpers().ParseText(ActiveStatementsDescription.ClearTags(source)); + private static SyntaxTree ParseSource(string markedSource) + => SyntaxFactory.ParseSyntaxTree( + ActiveStatementsDescription.ClearTags(markedSource), + CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview), + path: "test.cs"); internal static EditScript GetTopEdits(string src1, string src2) { @@ -149,8 +154,8 @@ internal static string WrapMethodBodyWithClass(string bodySource, MethodKind kin _ => "class C { void F() { " + bodySource + " } }", }; - internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource) - => new(oldSource, newSource); + internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource, ActiveStatementFlags[] flags = null, string path = "0") + => new(oldSource, newSource, source => SyntaxFactory.ParseSyntaxTree(source, path: path), flags); internal static SyntaxMapDescription GetSyntaxMap(string oldSource, string newSource) => new(oldSource, newSource); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs index 9e9d146525be9..1f9085b4ae8d2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs @@ -5,11 +5,14 @@ #nullable disable using System; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.UnitTests; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests @@ -51,7 +54,12 @@ static void Goo() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 9), new SourceLineUpdate(9, 4) }, + new[] + { + new SourceLineUpdate(4, 9), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(7), + new SourceLineUpdate(9, 4) + }, Array.Empty()); } @@ -98,7 +106,13 @@ static int Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 9), new SourceLineUpdate(10, 4) }, + new[] + { + new SourceLineUpdate(4, 9), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(8), + new SourceLineUpdate(10, 4), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(13), + }, Array.Empty()); } @@ -126,10 +140,41 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), Array.Empty()); } + [Fact] + public void Method_MultilineBreakpointSpans() + { + var src1 = @" +class C +{ + void F() + { + var x = +1; + } +} +"; + var src2 = @" +class C +{ + void F() + { + var x = + +1; + } +}"; + // We need to recompile the method since an active statement span [|var x = 1;|] + // needs to be updated but can't be by a line update. + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + Array.Empty(), + new[] { "void F()" }); + } + [Fact] public void Method_LineChange1() { @@ -185,6 +230,34 @@ static void Bar() Array.Empty()); } + [Fact] + public void Method_LineChange3() + { + var src1 = @" +class C +{ + static int X() => 1; + + static int Y() => 1; +} +"; + var src2 = @" +class C +{ + + static int X() => 1; + static int Y() => 1; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] + { + new SourceLineUpdate(3, 4), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(4) + }, + Array.Empty()); + } + [Fact] public void Method_Recompile1() { @@ -207,12 +280,12 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "static void Bar()" }); } [Fact] - public void Method_Recompile2() + public void Method_PartialBodyLineUpdate1() { var src1 = @" class C @@ -234,12 +307,12 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), - new string[] { "static void Bar()" }); + new[] { new SourceLineUpdate(6, 5) }, + Array.Empty()); } [Fact] - public void Method_Recompile3() + public void Method_PartialBodyLineUpdate2() { var src1 = @" class C @@ -262,8 +335,8 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), - new string[] { "static void Bar()" }); + new[] { new SourceLineUpdate(5, 4) }, + Array.Empty()); } [Fact] @@ -292,7 +365,7 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "static void Bar()" }); var active = GetActiveStatements(src1, src2); @@ -314,7 +387,7 @@ class C { /*--*/static void Bar() { } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "static void Bar() { }" }); } @@ -342,9 +415,8 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), - new string[] { "static void Bar()" }, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "\r\n ", FeaturesResources.method)); + new[] { new SourceLineUpdate(6, 5) }, + Array.Empty()); } [Fact] @@ -370,7 +442,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "static void Bar()" }, Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "\r\n /*edit*/", FeaturesResources.method)); } @@ -398,7 +470,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "static void Bar()" }, Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "\r\n ", FeaturesResources.method)); } @@ -428,7 +500,7 @@ static async Task Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "static async Task Bar()" }); } @@ -464,7 +536,12 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 8), new SourceLineUpdate(8, 4) }, + new[] + { + new SourceLineUpdate(4, 8), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(6), + new SourceLineUpdate(8, 4) + }, Array.Empty()); } @@ -624,7 +701,7 @@ public C(int a) } [Fact] - public void Constructor_ExpressionBodiedWithBase_Recompile1() + public void Constructor_ExpressionBodiedWithBase_LineChange2() { var src1 = @" class C @@ -645,12 +722,37 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + new SourceLineUpdate[] { new(5, 6) }, + Array.Empty()); + } + + [Fact] + public void Constructor_ExpressionBodiedWithBase_Recompile1() + { + var src1 = @" +class C +{ + int _a; + public C(int a) + : base() => _a + = a; +} +"; + var src2 = @" +class C +{ + int _a; + public C(int a) + : base() => _a = a; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + Array.Empty(), new string[] { "public C(int a)" }); } [Fact] - public void Constructor_Recompile1() + public void Constructor_PartialBodyLineChange1() { var src1 = @" class C @@ -672,8 +774,8 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), - new string[] { "public C(int a)" }); + new SourceLineUpdate[] { new(5, 6) }, + Array.Empty()); } [Fact] @@ -698,7 +800,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "public C(int a)" }); } @@ -724,7 +826,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "public C(int a)" }, Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, " ", FeaturesResources.constructor)); } @@ -821,7 +923,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), Array.Empty()); } @@ -842,7 +944,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), Array.Empty()); } @@ -908,8 +1010,8 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new SourceLineUpdate[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + Array.Empty(), + new[] { "Bar = 2" }); } [Fact] @@ -929,7 +1031,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new SourceLineUpdate[] { new SourceLineUpdate(3, 4), new SourceLineUpdate(3, 4) }, + new SourceLineUpdate[] { new SourceLineUpdate(3, 4) }, Array.Empty()); } @@ -950,7 +1052,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo = " }); } @@ -971,7 +1073,7 @@ static int Goo }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo " }); } @@ -992,7 +1094,7 @@ static int }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo = 1" }); } @@ -1013,7 +1115,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo = 1" }); } @@ -1034,7 +1136,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo = 1" }); } @@ -1054,7 +1156,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo = 1 + 1" }); } @@ -1074,7 +1176,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "Goo = 1 + 1" }, Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, " ", FeaturesResources.field)); } @@ -1100,7 +1202,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "get { return " }); } @@ -1142,7 +1244,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4), new SourceLineUpdate(3, 4) }, + new[] { new SourceLineUpdate(3, 4) }, Array.Empty()); } @@ -1267,12 +1369,142 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "int P { get; } = 1;" }); } #endregion + #region Properties + + [Fact] + public void Indexer1() + { + var src1 = @" +class C +{ + int this[int a] { get { return 1; } } +} +"; + var src2 = @" +class C +{ + int this[int a] { get { return + 1; } } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + Array.Empty(), + new string[] { "get { return " }); + } + + [Fact] + public void Indexer2() + { + var src1 = @" +class C +{ + int this[int a] { get { return 1; } } +} +"; + var src2 = @" +class C +{ + int this[int a] { get + { return 1; } } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 4) }, + Array.Empty()); + } + + [Fact] + public void Indexer3() + { + var src1 = @" +class C +{ + int this[int a] { get { return 1; } set { } } +} +"; + var src2 = @" +class C +{ + + int this[int a] { get { return 1; } set { } } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 4) }, + Array.Empty()); + } + + [Fact] + public void Indexer_ExpressionBody1() + { + var src1 = @" +class C +{ + int this[int a] => 1; +} +"; + var src2 = @" +class C +{ + int this[int a] => + 1; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 4) }, + Array.Empty()); + } + + [Fact] + public void Indexer_GetterExpressionBody1() + { + var src1 = @" +class C +{ + int this[int a] { get => 1; } +} +"; + var src2 = @" +class C +{ + int this[int a] { get => + 1; } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 4) }, + Array.Empty()); + } + + [Fact] + public void Indexer_SetterExpressionBody1() + { + var src1 = @" +class C +{ + int this[int a] { set => F(); } +} +"; + var src2 = @" +class C +{ + int this[int a] { set => + F(); } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 4) }, + Array.Empty()); + } + + #endregion + #region Events [Fact] @@ -1292,7 +1524,7 @@ event Action E { add { } remove { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4), new SourceLineUpdate(3, 4) }, + new[] { new SourceLineUpdate(3, 4) }, Array.Empty()); } @@ -1334,7 +1566,7 @@ event Action E { add { } remove { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), + Array.Empty(), new string[] { "remove { }" }); } @@ -1355,7 +1587,7 @@ event Action E { add { } remove { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 3), new SourceLineUpdate(4, 3) }, + new[] { new SourceLineUpdate(4, 3) }, Array.Empty()); } @@ -1374,14 +1606,15 @@ class C event Action E { add { } remove { } } }"; + // we can only apply one delta per line, but that would affect add and remove differently, so need to recompile var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + Array.Empty(), + new[] { "remove " }); } - [Fact] - public void Event_ExpressionBody1() + [Fact, WorkItem(53263, "https://github.com/dotnet/roslyn/issues/53263")] + public void Event_ExpressionBody_MultipleBodiesOnTheSameLine1() { var src1 = @" class C @@ -1398,12 +1631,12 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4), new SourceLineUpdate(3, 5) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }, + new[] { "remove => " }); } [Fact] - public void Event_ExpressionBody2() + public void Event_ExpressionBody() { var src1 = @" class C @@ -1459,10 +1692,241 @@ class C "; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 9), new SourceLineUpdate(4, 10), new SourceLineUpdate(9, 3), new SourceLineUpdate(10, 4) }, + new[] + { + new SourceLineUpdate(3, 9), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(5), + new SourceLineUpdate(9, 3) + }, + Array.Empty()); + } + + #endregion + + #region Line Mappings + + [Fact] + public void LineMapping_ChangeLineNumber_WithinMethod_NoSequencePointImpact() + { + var src1 = @" +class C +{ + static void F() + { + G( +#line 2 ""c"" + 123 +#line default + ); + } +}"; + var src2 = @" +class C +{ + static void F() + { + G( +#line 3 ""c"" + 123 +#line default + ); + } +}"; + var edits = GetTopEdits(src1, src2); + + // Line deltas can't be applied on the whole breakpoint span hence recompilation. + edits.VerifyLineEdits( + Array.Empty(), + new[] { "static void F()" }); + } + + /// + /// Validates that changes in #line directives produce semantic updates of the containing method. + /// + [Fact] + public void LineMapping_ChangeLineNumber_OutsideOfMethod() + { + var src1 = @" +#line 1 ""a"" +class C +{ + int x = 1; + static int y = 1; + void F1() { } + void F2() { } +} +class D +{ + public D() {} + +#line 5 ""a"" + public F3() {} + +#line 6 ""a"" + public F4() {} +}"; + var src2 = @" +#line 11 ""a"" +class C +{ + int x = 1; + static int y = 1; + void F1() { } + void F2() { } +} +class D +{ + public D() {} + +#line 5 ""a"" + public F3() {} + public F4() {} +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyLineEdits( + new SequencePointUpdates[] + { + new("a", ImmutableArray.Create( + new(2, 12), // x, y, F1, F2 + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(6), // lines between F2 and D ctor + new(9, 19))) // D ctor + }, + new[] + { + "public F3() {}", // overlaps with "void F1() { }" + "public F4() {}" // overlaps with "void F2() { }" + }); + } + + [Fact] + public void LineMapping_LineDirectivesAndWhitespace() + { + var src1 = @" +class C +{ +#line 5 ""a"" +#line 6 ""a"" + + + + static void F() { } // line 9 +}"; + var src2 = @" +class C +{ +#line 9 ""a"" + static void F() { } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics(); + } + + [Fact] + public void LineMapping_MultipleFiles() + { + var src1 = @" +class C +{ + static void F() + { +#line 1 ""a"" + A(); +#line 1 ""b"" + B(); +#line default + } +}"; + var src2 = @" +class C +{ + static void F() + { +#line 2 ""a"" + A(); +#line 2 ""b"" + B(); +#line default + } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyLineEdits( + new SequencePointUpdates[] + { + new("a", ImmutableArray.Create(new SourceLineUpdate(0, 1))), + new("b", ImmutableArray.Create(new SourceLineUpdate(0, 1))), + }, Array.Empty()); } + [Fact] + public void LineMapping_FileChange_Recompile() + { + var src1 = @" +class C +{ + static void F() + { + A(); +#line 1 ""a"" + B(); +#line 3 ""a"" + C(); + } + + + int x = 1; +}"; + var src2 = @" +class C +{ + static void F() + { + A(); +#line 1 ""b"" + B(); +#line 2 ""a"" + C(); + } + + int x = 1; +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyLineEdits( + new SequencePointUpdates[] + { + new("a", ImmutableArray.Create(new SourceLineUpdate(6, 4))), + }, + expectedNodeUpdates: new[] { "static void F()" }); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")) + }); + } + + [Fact] + public void LineMapping_FileChange_RudeEdit() + { + var src1 = @" +#line 1 ""a"" +class C { static void Bar() { } } +"; + var src2 = @" +#line 1 ""b"" +class C { static void Bar() { } }"; + + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + Array.Empty(), + new string[] { "static void Bar() { }" }, + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "{", FeaturesResources.method)); + } + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index e863592fd1c4e..5658d7becc583 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -236,7 +236,7 @@ public void ParenthesizedVariableDeclaration_ComplexReorder() #endregion - #region Switch + #region Switch Statement [Fact] public void Switch1() @@ -309,6 +309,70 @@ public void CasePatternLabel_UpdateDelete() #endregion + #region Switch Expression + + [Fact] + public void MethodUpdate_UpdateSwitchExpression1() + { + var src1 = @" +class C +{ + static int F(int a) => a switch { 0 => 0, _ => 1 }; +}"; + var src2 = @" +class C +{ + static int F(int a) => a switch { 0 => 0, _ => 2 }; +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [static int F(int a) => a switch { 0 => 0, _ => 1 };]@18 -> [static int F(int a) => a switch { 0 => 0, _ => 2 };]@18"); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void MethodUpdate_UpdateSwitchExpression2() + { + var src1 = @" +class C +{ + static int F(int a) => a switch { 0 => 0, _ => 1 }; +}"; + var src2 = @" +class C +{ + static int F(int a) => a switch { 1 => 0, _ => 2 }; +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [static int F(int a) => a switch { 0 => 0, _ => 1 };]@18 -> [static int F(int a) => a switch { 1 => 0, _ => 2 };]@18"); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void MethodUpdate_UpdateSwitchExpression3() + { + var src1 = @" +class C +{ + static int F(int a) => a switch { 0 => 0, _ => 1 }; +}"; + var src2 = @" +class C +{ + static int F(int a) => a switch { 0 => 0, 1 => 1, _ => 2 }; +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [static int F(int a) => a switch { 0 => 0, _ => 1 };]@18 -> [static int F(int a) => a switch { 0 => 0, 1 => 1, _ => 2 };]@18"); + + edits.VerifyRudeDiagnostics(); + } + + #endregion + #region Try Catch Finally [Fact] @@ -2626,6 +2690,110 @@ static void F() ); } + [Fact] + public void Lambdas_Insert_NotSupported() + { + var src1 = @" +using System; + +class C +{ + void F() + { + } +} +"; + var src2 = @" +using System; + +class C +{ + void F() + { + var f = new Func(a => a); + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + activeStatements: ActiveStatementsDescription.Empty, + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "a", CSharpFeaturesResources.lambda)); + } + + [Fact] + public void Lambdas_Insert_Second_NotSupported() + { + var src1 = @" +using System; + +class C +{ + void F() + { + var f = new Func(a => a); + } +} +"; + var src2 = @" +using System; + +class C +{ + void F() + { + var f = new Func(a => a); + var g = new Func(b => b); + } +} +"; + var capabilities = EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.NewTypeDefinition; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + activeStatements: ActiveStatementsDescription.Empty, + capabilities, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "b", CSharpFeaturesResources.lambda)); + } + + [Fact] + public void Lambdas_Insert_FirstInClass_NotSupported() + { + var src1 = @" +using System; + +class C +{ + void F() + { + } +} +"; + var src2 = @" +using System; + +class C +{ + void F() + { + var g = new Func(b => b); + } +} +"; + var capabilities = EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.NewTypeDefinition; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + activeStatements: ActiveStatementsDescription.Empty, + capabilities, + // TODO: https://github.com/dotnet/roslyn/issues/52759 + // This is incorrect, there should be no rude edit reported + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "b", CSharpFeaturesResources.lambda)); + } + [Fact] public void Lambdas_Update_CeaseCapture_This() { @@ -5484,6 +5652,40 @@ static void F() Diagnostic(RudeEditKind.InsertLambdaWithMultiScopeCapture, "x1", CSharpFeaturesResources.local_function, "x0", "x1")); } + [Fact] + public void LocalFunctions_Insert_NotSupported() + { + var src1 = @" +using System; + +class C +{ + void F() + { + } +} +"; + var src2 = @" +using System; + +class C +{ + void F() + { + void M() + { + } + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + activeStatements: ActiveStatementsDescription.Empty, + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "M", FeaturesResources.local_function)); + } + [Fact, WorkItem(21499, "https://github.com/dotnet/roslyn/issues/21499")] public void LocalFunctions_Update_CeaseCapture_This() { @@ -10296,6 +10498,60 @@ public void TupleInDelegate() "Update [(int, int) x]@35 -> [(int, int) y]@35"); } - #endregion + #endregion + + #region With Expressions + + [Fact] + public void WithExpression_PropertyAdd() + { + var src1 = @"var x = y with { X = 1 };"; + var src2 = @"var x = y with { X = 1, Y = 2 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1 }]@6 -> [x = y with { X = 1, Y = 2 }]@6"); + } + + [Fact] + public void WithExpression_PropertyDelete() + { + var src1 = @"var x = y with { X = 1, Y = 2 };"; + var src2 = @"var x = y with { X = 1 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1, Y = 2 }]@6 -> [x = y with { X = 1 }]@6"); + } + + [Fact] + public void WithExpression_PropertyChange() + { + var src1 = @"var x = y with { X = 1 };"; + var src2 = @"var x = y with { Y = 1 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1 }]@6 -> [x = y with { Y = 1 }]@6"); + } + + [Fact] + public void WithExpression_PropertyValueChange() + { + var src1 = @"var x = y with { X = 1, Y = 1 };"; + var src2 = @"var x = y with { X = 1, Y = 2 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1, Y = 1 }]@6 -> [x = y with { X = 1, Y = 2 }]@6"); + } + + [Fact] + public void WithExpression_PropertyValueReorder() + { + var src1 = @"var x = y with { X = 1, Y = 1 };"; + var src2 = @"var x = y with { Y = 1, X = 1 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1, Y = 1 }]@6 -> [x = y with { Y = 1, X = 1 }]@6"); + } + + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs index 7866c6b2824b6..ca139c32c0195 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs @@ -2056,6 +2056,27 @@ public void WhenCondition() #region Switch Expression + [Fact] + public void SwitchExpressionArms_Lambda() + { + var src1 = @"F1() switch { 1 => new Func(() => 1)(), _ => 2 };"; + var src2 = @"F1() switch { 1 => new Func(() => 3)(), _ => 2 };"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs { + { "F1() switch { 1 => new Func(() => 1)(), _ => 2 };", "F1() switch { 1 => new Func(() => 3)(), _ => 2 };" }, + { "F1() switch { 1 => new Func(() => 1)(), _ => 2 }", "F1() switch { 1 => new Func(() => 3)(), _ => 2 }" }, + { "1 => new Func(() => 1)()", "1 => new Func(() => 3)()" }, + { "() => 1", "() => 3" }, + { "()", "()" }, + { "_ => 2", "_ => 2" } + }; + + expected.AssertEqual(actual); + } + [Fact] public void SwitchExpressionArms_NestedSimilar() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1f189a7c0f65d..dd495ff5fd626 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -571,6 +571,37 @@ public void TypeKindUpdate() Diagnostic(RudeEditKind.TypeKindUpdate, "struct C", CSharpFeaturesResources.struct_)); } + [Fact] + public void TypeKindUpdate2() + { + // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 + var src1 = "class C { }"; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [class C { }]@0 -> [record C { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeKindUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void TypeKindUpdate3() + { + var src1 = "record C { }"; + var src2 = "record struct C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record struct C { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeKindUpdate, "record struct C", CSharpFeaturesResources.record_struct)); + } + [Fact] public void Class_Modifiers_Update() { @@ -1018,6 +1049,37 @@ public override void H() {} edits.VerifyRudeDiagnostics(); } + [Fact] + public void ClassInsert_NotSupportedByRuntime() + { + var src1 = @" +public class C +{ + void F() + { + } +}"; + var src2 = @" +public class C +{ + void F() + { + } +} + +public class D +{ + void M() + { + } +}"; + + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "public class D", FeaturesResources.class_)); + } + [Fact] public void InterfaceInsert() { @@ -1225,6 +1287,7 @@ enum E { } 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.InsertIntoInterface, "static int StaticProperty2", CSharpFeaturesResources.property_getter), 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), @@ -1232,7 +1295,9 @@ enum E { } 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.InsertIntoInterface, "sealed int NonVirtualProperty", CSharpFeaturesResources.property_getter), Diagnostic(RudeEditKind.InsertVirtual, "int this[byte virtualIndexer]", FeaturesResources.indexer_), + Diagnostic(RudeEditKind.InsertVirtual, "int this[byte virtualIndexer]", CSharpFeaturesResources.indexer_getter), 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_), @@ -1504,112 +1569,1139 @@ class C "; 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); -} -"; + 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(), partialType: "C", 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(), partialType: "C", 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(), partialType: "C", preserveLocalVariables: true), + }), + + DocumentResults() + }); + } + + #endregion + + #region Records + + [Fact] + public void Record_Partial_MovePrimaryConstructor() + { + var src1 = @" +partial record C { } +partial record C(int X);"; + var src2 = @" +partial record C(int X); +partial record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics(); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_Name_Update() + { + var src1 = "record C { }"; + var src2 = "record D { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record D { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Renamed, "record D", CSharpFeaturesResources.record_)); + } + + [Fact] + public void RecordStruct_NoModifiers_Insert() + { + var src1 = ""; + var src2 = "record struct C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void RecordStruct_AddField() + { + var src1 = @" +record struct C(int X) +{ +}"; + var src2 = @" +record struct C(int X) +{ + private int _y = 0; +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoStruct, "_y = 0", FeaturesResources.field, CSharpFeaturesResources.record_struct)); + } + + [Fact] + public void RecordStruct_AddProperty() + { + var src1 = @" +record struct C(int X) +{ +}"; + var src2 = @" +record struct C(int X) +{ + public int Y { get; set; } = 0; +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoStruct, "public int Y { get; set; } = 0;", FeaturesResources.auto_property, CSharpFeaturesResources.record_struct)); + } + + [Fact] + public void Record_NoModifiers_Insert() + { + var src1 = ""; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_NoModifiers_IntoNamespace_Insert() + { + var src1 = "namespace N { }"; + var src2 = "namespace N { record C { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_NoModifiers_IntoType_Insert() + { + var src1 = "struct N { }"; + var src2 = "struct N { record C { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_BaseTypeUpdate1() + { + var src1 = "record C { }"; + var src2 = "record C : D { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record C : D { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseTypeUpdate2() + { + var src1 = "record C : D1 { }"; + var src2 = "record C : D2 { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C : D1 { }]@0 -> [record C : D2 { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseInterfaceUpdate1() + { + var src1 = "record C { }"; + var src2 = "record C : IDisposable { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record C : IDisposable { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseInterfaceUpdate2() + { + var src1 = "record C : IGoo, IBar { }"; + var src2 = "record C : IGoo { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C : IGoo, IBar { }]@0 -> [record C : IGoo { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseInterfaceUpdate3() + { + var src1 = "record C : IGoo, IBar { }"; + var src2 = "record C : IBar, IGoo { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C : IGoo, IBar { }]@0 -> [record C : IBar, IGoo { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void RecordInsert_AbstractVirtualOverride() + { + var src1 = ""; + var src2 = @" +public abstract record C +{ + public abstract void F(); + public virtual void G() {} + public override void H() {} +}"; + + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_PrintMembers() + { + var src1 = "record C { }"; + var src2 = @" +record C +{ + protected bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void RecordStruct_ImplementSynthesized_PrintMembers() + { + var src1 = "record struct C { }"; + var src2 = @" +record struct C +{ + private bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_WrongParameterName() + { + // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 + + var src1 = "record C { }"; + var src2 = @" +record C +{ + protected virtual bool PrintMembers(System.Text.StringBuilder sb) + { + return false; + } + + public virtual bool Equals(C rhs) + { + return false; + } + + protected C(C other) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "protected virtual bool PrintMembers(System.Text.StringBuilder sb)", "PrintMembers(System.Text.StringBuilder builder)"), + Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "public virtual bool Equals(C rhs)", "Equals(C other)"), + Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "protected C(C other)", "C(C original)")); + } + + [Fact] + public void Record_ImplementSynthesized_ToString() + { + var src1 = "record C { }"; + var src2 = @" +record C +{ + public override string ToString() + { + return ""R""; + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.ToString"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_ToString() + { + var src1 = @" +record C +{ + public override string ToString() + { + return ""R""; + } +}"; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.ToString"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_Primary() + { + var src1 = "record C(int X);"; + var src2 = "record C(int X, int Y);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.AddRecordPositionalParameter, "int Y", FeaturesResources.parameter)); + } + + [Fact] + public void Record_AddProperty_NotPrimary() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_NotPrimary_WithConstructor() + { + var src1 = @" +record C(int X) +{ + public C(string fromAString) + { + } +}"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } + + public C(string fromAString) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_NotPrimary_WithExplicitMembers() + { + var src1 = @" +record C(int X) +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public virtual bool Equals(C other) + { + return false; + } + + public C(C original) + { + } +}"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } + + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public virtual bool Equals(C other) + { + return false; + } + + public C(C original) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_NotPrimary_WithInitializer() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } = 1; +}"; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField() + { + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { private int _y; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithExplicitMembers() + { + var src1 = @" +record C(int X) +{ + public C(C other) + { + } +}"; + var src2 = @" +record C(int X) +{ + private int _y; + + public C(C other) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithInitializer() + { + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { private int _y = 1; }"; + var syntaxMap = GetSyntaxMap(src1, src2); + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithExistingInitializer() + { + var src1 = "record C(int X) { private int _y = 1; }"; + var src2 = "record C(int X) { private int _y = 1; private int _z; }"; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), syntaxMap[0]), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithInitializerAndExistingInitializer() + { + var src1 = "record C(int X) { private int _y = 1; }"; + var src2 = "record C(int X) { private int _y = 1; private int _z = 1; }"; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), syntaxMap[0]), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_DeleteField() + { + var src1 = "record C(int X) { private int _y; }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.field, "_y"))); + } + + [Fact] + public void Record_DeleteProperty_Primary() + { + var src1 = "record C(int X, int Y) { }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.DeleteRecordPositionalParameter, "record C", FeaturesResources.parameter)); + } + + [Fact] + public void Record_DeleteProperty_NotPrimary() + { + var src1 = "record C(int X) { public int P { get; set; } }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.auto_property, "P"))); + } + + [Fact] + public void Record_ImplementSynthesized_Property() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get; init; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_WithBody() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X + { + get + { + return 4; + } + init + { + throw null; + } + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_WithExpressionBody() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get => 4; init => throw null; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_InitToSet() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get; set; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ImplementRecordParameterWithSet, "public int X", "X")); + } + + [Fact] + public void Record_ImplementSynthesized_Property_MakeReadOnly() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ImplementRecordParameterAsReadOnly, "public int X", "X")); + } + + [Fact] + public void Record_UnImplementSynthesized_Property() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_WithExpressionBody() + { + var src1 = @" +record C(int X) +{ + public int X { get => 4; init => throw null; } +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_WithBody() + { + var src1 = @" +record C(int X) +{ + public int X { get { return 4; } init { } } +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_Partial() + { + var srcA1 = @"partial record C(int X);"; + var srcB1 = @"partial record C;"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @" +partial record C +{ + public int X { get; init; } +}"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType: "C", preserveLocalVariables: true) + }) + }); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_Partial() + { + var srcA1 = @"partial record C(int X);"; + var srcB1 = @" +partial record C +{ + public int X { get; init; } +}"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @"partial record C;"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType: "C", preserveLocalVariables: true) + }) + }); + } + + [Fact] + public void Record_ImplementSynthesized_Property_Partial_WithBody() + { + var srcA1 = @"partial record C(int X);"; + var srcB1 = @"partial record C;"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @" +partial record C +{ + public int X + { + get + { + return 4; + } + init + { + throw null; + } + } +}"; + EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType : "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) }) }); } [Fact] - public void Type_DeleteInsert_DataMembers_PartialSplit() + public void Record_UnImplementSynthesized_Property_Partial_WithBody() { - var srcA1 = @" -class C + var srcA1 = @"partial record C(int X);"; + var srcB1 = @" +partial record C { - public int x = 1; - public int y = 2; - public int P { get; set; } = 3; -} -"; - var srcB1 = ""; + public int X + { + get + { + return 4; + } + init + { + throw null; + } + } +}"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @"partial record C;"; - 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(), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType : "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) }) }); } [Fact] - public void Type_DeleteInsert_DataMembers_PartialMerge() + public void Record_MoveProperty_Partial() { - var srcA1 = @" -partial class C -{ - public int x = 1; - public int y = 2; -} -"; - var srcB1 = @" -partial class C + var srcA1 = @"partial record C(int X) { - public int P { get; set; } = 3; + public int Y { get; init; } }"; - - var srcA2 = @" -class C + var srcB1 = @"partial record C;"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @"partial record C { - public int x = 1; - public int y = 2; - public int P { get; set; } = 3; -} -"; + public int Y { get; init; } +}"; - var srcB2 = @" -"; - // note that accessors are not updated since they do not have bodies EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + 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(), - DocumentResults() + DocumentResults(), }); } + [Fact] + public void Record_UnImplementSynthesized_Property_WithInitializer() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } = 1; +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_WithInitializerMatchingCompilerGenerated() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } = X; +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_Property_Delete_NotPrimary() + { + var src1 = @" +record C(int X) +{ + public int Y { get; init; } +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.auto_property, "Y"))); + } + + [Fact] + public void Record_PropertyInitializer_Update_NotPrimary() + { + var src1 = "record C { int X { get; } = 0; }"; + var src2 = "record C { int X { get; } = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Length == 0), preserveLocalVariables: true)); + } + + [Fact] + public void Record_PropertyInitializer_Update_Primary() + { + var src1 = "record C(int X) { int X { get; } = 0; }"; + var src2 = "record C(int X) { int X { get; } = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + } + #endregion #region Enums @@ -4118,6 +5210,19 @@ class C Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "puts(string)"))); } + [Fact] + public void MethodInsert_NotSupportedByRuntime() + { + var src1 = "class C { }"; + var src2 = "class C { void goo() { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "void goo()", FeaturesResources.method)); + } + [Fact] public void PrivateMethodInsert() { @@ -4632,6 +5737,32 @@ public async Task WaitAsync() VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } + [Fact] + public void MethodUpdate_Modifier_Async_Add_NotSupported() + { + var src1 = @" +class Test +{ + public Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public async Task WaitAsync() + { + await Task.Delay(1000); + return 1; + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.MakeMethodAsync, "public async Task WaitAsync()")); + } + [Fact] public void MethodUpdate_AsyncMethod0() { @@ -5163,27 +6294,6 @@ public void MethodUpdate_UpdateStackAlloc2(string stackallocDecl) Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.method)); } - [Fact] - [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] - [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] - public void MethodUpdate_UpdateSwitchExpression() - { - var src1 = @" -class C -{ - static int F(int a) => a switch { 0 => 0, _ => 1 }; -}"; - var src2 = @" -class C -{ - static int F(int a) => a switch { 0 => 0, _ => 2 }; -}"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); - } - [Fact] public void MethodUpdate_UpdateStackAllocInLambda1() { @@ -5375,6 +6485,19 @@ public void MethodUpdate_AddYieldReturn() VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } + [Fact] + public void MethodUpdate_AddYieldReturn_NotSupported() + { + var src1 = "class C { IEnumerable M() { return new[] { 1, 2, 3}; } }"; + var src2 = "class C { IEnumerable M() { yield return 2; } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.MakeMethodIterator, "IEnumerable M()")); + } + [Fact] public void MethodUpdate_Iterator_YieldBreak() { @@ -7901,8 +9024,7 @@ public void FieldInitializerUpdate_SwitchExpressionInConstructor() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.constructor)); + edits.VerifySemanticDiagnostics(); } [Fact] @@ -7952,8 +9074,7 @@ public void PropertyInitializerUpdate_SwitchExpressionInConstructor1() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.constructor)); + edits.VerifySemanticDiagnostics(); } [Fact] @@ -7979,8 +9100,7 @@ public void PropertyInitializerUpdate_SwitchExpressionInConstructor3() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.constructor)); + edits.VerifySemanticDiagnostics(); } [Fact] @@ -8935,6 +10055,28 @@ public void Field_Partial_DeleteInsert_InitializerRemoval() }); } + [Fact] + public void Field_Partial_DeleteInsert_InitializerUpdate() + { + 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 = 2; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + }); + } + #endregion #region Fields @@ -9429,6 +10571,32 @@ class C }); } + [Fact] + public void FieldInsert_NotSupportedByRuntime() + { + var src1 = "class C { }"; + var src2 = "class C { public int a = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities | EditAndContinueCapabilities.AddStaticFieldToExistingType, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "a = 1", FeaturesResources.field)); + } + + [Fact] + public void FieldInsert_Static_NotSupportedByRuntime() + { + var src1 = "class C { }"; + var src2 = "class C { public static int a = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities | EditAndContinueCapabilities.AddInstanceFieldToExistingType, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "a = 1", FeaturesResources.field)); + } + [Fact] public void FieldDelete1() { @@ -9808,6 +10976,18 @@ public void PropertyRename2() Diagnostic(RudeEditKind.Renamed, "int J.P", FeaturesResources.property_)); } + [Fact] + public void PropertyDelete() + { + var src1 = "class C { int P { get { return 1; } } }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.property_, "P"))); + } + [Fact] public void PropertyReorder1() { @@ -9894,6 +11074,19 @@ public void PropertyInsert() SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("P"))); } + [Fact] + public void PropertyInsert_NotSupportedByRuntime() + { + var src1 = "class C { }"; + var src2 = "class C { int P { get => 1; set { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int P", FeaturesResources.auto_property)); + } + [WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] [Fact] public void PropertyInsert_PInvoke() @@ -11190,9 +12383,6 @@ public void AutoIndexer_Partial_InsertDelete() [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 { @@ -11200,13 +12390,13 @@ partial class C var srcB1 = @" partial class C { - int this[int a] => new System.Func(() => a + 1) + 10; + int this[int a] => new System.Func(() => a + 1); }"; var srcA2 = @" partial class C { - int this[int a] => new System.Func(() => 2) + 11; // no capture + int this[int a] => new System.Func(() => 2); // no capture }"; var srcB2 = @" partial class C diff --git a/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.TestAnalyzerConfigOptions.cs b/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.TestAnalyzerConfigOptions.cs index d2b3c7460dd33..9346e9d6eb5d5 100644 --- a/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.TestAnalyzerConfigOptions.cs +++ b/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.TestAnalyzerConfigOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -11,12 +12,22 @@ public partial class SettingsUpdaterTests { private class TestAnalyzerConfigOptions : AnalyzerConfigOptions { - public static TestAnalyzerConfigOptions Instance = new TestAnalyzerConfigOptions(); + public static TestAnalyzerConfigOptions Instance = new(); + private readonly Func? _lookup = null; + + public TestAnalyzerConfigOptions() + { + } + + public TestAnalyzerConfigOptions(Func lookup) + { + _lookup = lookup; + } public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { - value = null; - return false; + value = _lookup?.Invoke(key); + return value != null; } } } diff --git a/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs b/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs index 918323c59e58f..de0a4df83da3f 100644 --- a/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs +++ b/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.CodeStyle; @@ -24,6 +26,8 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests [UseExportProvider] public partial class SettingsUpdaterTests : TestBase { + private const string EditorconfigPath = "/a/b/config"; + private static Workspace CreateWorkspaceWithProjectAndDocuments() { var projectId = ProjectId.CreateNewId(); @@ -34,7 +38,7 @@ private static Workspace CreateWorkspaceWithProjectAndDocuments() .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp) .AddDocument(DocumentId.CreateNewId(projectId), "goo.cs", "public class Goo { }") .AddAdditionalDocument(DocumentId.CreateNewId(projectId), "add.txt", "text") - .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId), "editorcfg", SourceText.From(""), filePath: "/a/b/config"))); + .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId), "editorcfg", SourceText.From(""), filePath: EditorconfigPath))); return workspace; } @@ -81,9 +85,8 @@ await TestAsync( [Fact, Trait(Traits.Feature, Traits.Features.EditorConfigUI)] public async Task TestAddNewBoolCodeStyleOptionWithSeverityAsync() { - var option = CSharpCodeStyleOptions.PreferThrowExpression.DefaultValue; - option.Value = true; - option.Notification = CodeStyle.NotificationOption2.Suggestion; + ICodeStyleOption option = CSharpCodeStyleOptions.PreferThrowExpression.DefaultValue; + option = option.WithValue(true).WithNotification(NotificationOption2.Suggestion); await TestAsync( string.Empty, "[*.cs]\r\ncsharp_style_throw_expression=true:suggestion", @@ -93,9 +96,8 @@ await TestAsync( [Fact, Trait(Traits.Feature, Traits.Features.EditorConfigUI)] public async Task TestAddNewEnumCodeStyleOptionWithSeverityAsync() { - var option = CSharpCodeStyleOptions.PreferredUsingDirectivePlacement.DefaultValue; - option.Value = AddImportPlacement.InsideNamespace; - option.Notification = CodeStyle.NotificationOption2.Warning; + ICodeStyleOption option = CSharpCodeStyleOptions.PreferredUsingDirectivePlacement.DefaultValue; + option = option.WithValue(AddImportPlacement.InsideNamespace).WithNotification(NotificationOption2.Warning); await TestAsync( string.Empty, "[*.cs]\r\ncsharp_using_directive_placement=inside_namespace:warning", @@ -337,20 +339,27 @@ public async Task TestAnalyzerSettingsUpdaterService() public async Task TestCodeStyleSettingUpdaterService() { var workspace = CreateWorkspaceWithProjectAndDocuments(); - var updater = new OptionUpdater(workspace, "/a/b/config"); + var updater = new OptionUpdater(workspace, EditorconfigPath); + var value = "false:silent"; + var editorOptions = new TestAnalyzerConfigOptions(key => value); var setting = CodeStyleSetting.Create(CSharpCodeStyleOptions.AllowBlankLineAfterColonInConstructorInitializer, "", - TestAnalyzerConfigOptions.Instance, + editorOptions, workspace.Options, updater); setting.ChangeSeverity(DiagnosticSeverity.Error); var updates = await updater.GetChangedEditorConfigAsync(default); var update = Assert.Single(updates); - Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental=true:error", update.NewText); - setting.ChangeValue(1); + Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental=false:error", update.NewText); + value = "false:error"; + var editorconfig = workspace.CurrentSolution.Projects.SelectMany(p => p.AnalyzerConfigDocuments.Where(a => a.FilePath == EditorconfigPath)).Single(); + var text = await editorconfig.GetTextAsync(); + var newSolution = workspace.CurrentSolution.WithAnalyzerConfigDocumentText(editorconfig.Id, text); + Assert.True(workspace.TryApplyChanges(newSolution)); + setting.ChangeValue(0); updates = await updater.GetChangedEditorConfigAsync(default); update = Assert.Single(updates); - Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental=false:error", update.NewText); + Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental=true:error", update.NewText); } [Fact, Trait(Traits.Feature, Traits.Features.EditorConfigUI)] diff --git a/src/EditorFeatures/CSharpTest/Extensions/TelemetryExtensionTests.cs b/src/EditorFeatures/CSharpTest/Extensions/TelemetryExtensionTests.cs index 9189e507af02a..1225c64fd1c12 100644 --- a/src/EditorFeatures/CSharpTest/Extensions/TelemetryExtensionTests.cs +++ b/src/EditorFeatures/CSharpTest/Extensions/TelemetryExtensionTests.cs @@ -19,16 +19,8 @@ public void TestConstantTelemetryId() var actual = typeof(TelemetryExtensionTests).GetTelemetryId(); var actualBytes = actual.ToByteArray(); - // The first 4 bytes are using platform dependent hashcode and - // are not deterministic. This is a known limitation and corrected - // with the last 8 bytes of the GUID - for (var i = 0; i < 4; i++) - { - actualBytes[i] = 0; - } - // If the assertion fails then telemetry ids could be changing - // making them hard to track. It's important to not regress + // making them hard to track. It's important to not regress // the ability to track telemetry across versions of Roslyn. Assert.Equal(new Guid(actualBytes), expected); } diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index 74a16353d77fb..0d1219d77024b 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -88,6 +88,43 @@ private static void Main(string[] args) return AssertCodeCleanupResult(expected, code); } + [Fact] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public Task SortGlobalUsings() + { + var code = @"using System.Threading.Tasks; +using System.Threading; +global using System.Collections.Generic; +global using System; +class Program +{ + static async Task Main(string[] args) + { + Barrier b = new Barrier(0); + var list = new List(); + Console.WriteLine(list.Count); + } +} +"; + + var expected = @"global using System; +global using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +internal class Program +{ + private static async Task Main(string[] args) + { + Barrier b = new Barrier(0); + List list = new List(); + Console.WriteLine(list.Count); + } +} +"; + return AssertCodeCleanupResult(expected, code); + } + [Fact, WorkItem(36984, "https://github.com/dotnet/roslyn/issues/36984")] [Trait(Traits.Feature, Traits.Features.CodeCleanup)] public Task GroupUsings() diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs index 6d811d08c614d..1c5b1d09ef55e 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Editor.Implementation.Formatting; -using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; @@ -34,7 +33,7 @@ private static Dictionary SmartIndentButDoNotFormatWhileTypi return new Dictionary { { new OptionKey2(FormattingOptions2.SmartIndent, LanguageNames.CSharp), FormattingOptions.IndentStyle.Smart }, - { new OptionKey2(FeatureOnOffOptions.AutoFormattingOnTyping, LanguageNames.CSharp), false }, + { new OptionKey2(FormattingOptions2.AutoFormattingOnTyping, LanguageNames.CSharp), false }, { new OptionKey2(BraceCompletionOptions.AutoFormattingOnCloseBrace, LanguageNames.CSharp), false }, }; } @@ -1235,7 +1234,7 @@ class C var optionSet = new Dictionary { - { new OptionKey2(FeatureOnOffOptions.AutoFormattingOnTyping, LanguageNames.CSharp), false } + { new OptionKey2(FormattingOptions2.AutoFormattingOnTyping, LanguageNames.CSharp), false } }; AssertFormatAfterTypeChar(code, expected, optionSet); @@ -1267,7 +1266,7 @@ static void Main() var optionSet = new Dictionary { - { new OptionKey2(FeatureOnOffOptions.AutoFormattingOnTyping, LanguageNames.CSharp), false } + { new OptionKey2(FormattingOptions2.AutoFormattingOnTyping, LanguageNames.CSharp), false } }; AssertFormatAfterTypeChar(code, expected, optionSet); @@ -1325,7 +1324,7 @@ class C var optionSet = new Dictionary { - { new OptionKey2(FeatureOnOffOptions.AutoFormattingOnSemicolon, LanguageNames.CSharp), false } + { new OptionKey2(FormattingOptions2.AutoFormattingOnSemicolon, LanguageNames.CSharp), false } }; AssertFormatAfterTypeChar(code, expected, optionSet); @@ -1357,7 +1356,7 @@ class C var optionSet = new Dictionary { - { new OptionKey2(FeatureOnOffOptions.AutoFormattingOnTyping, LanguageNames.CSharp), false } + { new OptionKey2(FormattingOptions2.AutoFormattingOnTyping, LanguageNames.CSharp), false } }; AssertFormatAfterTypeChar(code, expected, optionSet); diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs index 42742ac7ca02e..4599073a45c36 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs @@ -569,32 +569,35 @@ int Prop await AutoFormatOnCloseBraceAsync(code, expected, SyntaxKind.OpenBraceToken); } - [WpfFact] + [WpfTheory] [WorkItem(16984, "https://github.com/dotnet/roslyn/issues/16984")] [Trait(Traits.Feature, Traits.Features.SmartTokenFormatting)] - public async Task AccessorList9() + [InlineData("get")] + [InlineData("set")] + [InlineData("init")] + public async Task AccessorList9(string accessor) { - var code = @"class C -{ + var code = $@"class C +{{ int Prop - { -set - { + {{ +{accessor} + {{ ; - }$$ - } -}"; + }}$$ + }} +}}"; - var expected = @"class C -{ + var expected = $@"class C +{{ int Prop - { - set - { + {{ + {accessor} + {{ ; - } - } -}"; + }} + }} +}}"; await AutoFormatOnCloseBraceAsync(code, expected, SyntaxKind.OpenBraceToken); } diff --git a/src/EditorFeatures/CSharpTest/GenerateConstructor/GenerateConstructorTests.cs b/src/EditorFeatures/CSharpTest/GenerateConstructor/GenerateConstructorTests.cs index 5442c1c7ba5e8..064307be06e10 100644 --- a/src/EditorFeatures/CSharpTest/GenerateConstructor/GenerateConstructorTests.cs +++ b/src/EditorFeatures/CSharpTest/GenerateConstructor/GenerateConstructorTests.cs @@ -2,13 +2,10 @@ // 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.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Diagnostics; using Microsoft.CodeAnalysis.CSharp.GenerateConstructor; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Diagnostics; @@ -28,7 +25,7 @@ public GenerateConstructorTests(ITestOutputHelper logger) { } - internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) + internal override (DiagnosticAnalyzer?, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) => (null, new GenerateConstructorCodeFixProvider()); private readonly NamingStylesTestOptionSets options = new NamingStylesTestOptionSets(LanguageNames.CSharp); @@ -2753,21 +2750,11 @@ static void Main(string[] args) }"); } - public partial class GenerateConstructorTestsWithFindMissingIdentifiersAnalyzer : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + [WorkItem(1241, @"https://github.com/dotnet/roslyn/issues/1241")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)] + public async Task TestGenerateConstructorInIncompleteLambda() { - public GenerateConstructorTestsWithFindMissingIdentifiersAnalyzer(ITestOutputHelper logger) - : base(logger) - { - } - - internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new CSharpUnboundIdentifiersDiagnosticAnalyzer(), new GenerateConstructorCodeFixProvider()); - - [WorkItem(1241, @"https://github.com/dotnet/roslyn/issues/1241")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)] - public async Task TestGenerateConstructorInIncompleteLambda() - { - await TestInRegularAndScriptAsync( + await TestInRegularAndScriptAsync( @"using System.Threading.Tasks; class C @@ -2795,7 +2782,6 @@ public C(int v) new C(0) }); } }"); - } } [WorkItem(5274, "https://github.com/dotnet/roslyn/issues/5274")] @@ -4741,5 +4727,32 @@ public C(int v1, int v2) } ", parseOptions: TestOptions.Regular); } + + [WorkItem(38822, "https://github.com/dotnet/roslyn/issues/38822")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)] + public async Task TestMissingInLambdaWithCallToExistingConstructor() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +public class InstanceType +{ + public InstanceType(object? a = null) { } +} + +public static class Example +{ + public static void Test() + { + Action lambda = () => + { + var _ = new [|InstanceType|](); + var _ = 0 + }; + } +} +"); + } } } diff --git a/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs b/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs index 15061953a2233..6fa2a987c3ce0 100644 --- a/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs @@ -1911,5 +1911,134 @@ public override void AbstractMethod() } }", parseOptions: TestOptions.RegularPreview); } + + [WorkItem(48742, "https://github.com/dotnet/roslyn/issues/48742")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementAbstractClass)] + public async Task TestUnconstrainedGenericNullable() + { + await TestAllOptionsOffAsync( +@"#nullable enable + +abstract class B +{ + public abstract T? M(); +} + +class [|D|] : B +{ +}", +@"#nullable enable + +abstract class B +{ + public abstract T? M(); +} + +class D : B +{ + public override int M() + { + throw new System.NotImplementedException(); + } +}"); + } + + [WorkItem(48742, "https://github.com/dotnet/roslyn/issues/48742")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementAbstractClass)] + public async Task TestUnconstrainedGenericNullable2() + { + await TestAllOptionsOffAsync( +@"#nullable enable + +abstract class B +{ + public abstract T? M(); +} + +class [|D|] : B where T : struct +{ +}", +@"#nullable enable + +abstract class B +{ + public abstract T? M(); +} + +class D : B where T : struct +{ + public override T M() + { + throw new System.NotImplementedException(); + } +}"); + } + + [WorkItem(48742, "https://github.com/dotnet/roslyn/issues/48742")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementAbstractClass)] + public async Task TestUnconstrainedGenericNullable_Tuple() + { + await TestAllOptionsOffAsync( +@"#nullable enable + +abstract class B +{ + public abstract T? M(); +} + +class [|D|] : B<(T, T)> +{ +}", +@"#nullable enable + +abstract class B +{ + public abstract T? M(); +} + +class D : B<(T, T)> +{ + public override (T, T) M() + { + throw new System.NotImplementedException(); + } +}"); + } + + [WorkItem(48742, "https://github.com/dotnet/roslyn/issues/48742")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsImplementAbstractClass)] + [InlineData("", "T")] + [InlineData(" where T : class", "T")] + [InlineData("", "T?")] + [InlineData(" where T : class", "T?")] + [InlineData(" where T : struct", "T?")] + public async Task TestUnconstrainedGenericNullable_NoRegression(string constraint, string passToBase) + { + await TestAllOptionsOffAsync( +$@"#nullable enable + +abstract class B +{{ + public abstract T? M(); +}} + +class [|D|] : B<{passToBase}>{constraint} +{{ +}}", +$@"#nullable enable + +abstract class B +{{ + public abstract T? M(); +}} + +class D : B<{passToBase}>{constraint} +{{ + public override T? M() + {{ + throw new System.NotImplementedException(); + }} +}}"); + } } } diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index 55566b6cc9e86..433012586fa24 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -8724,30 +8724,33 @@ public void M1() } [WorkItem(48295, "https://github.com/dotnet/roslyn/issues/48295")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] - public async Task TestImplementOnRecord_WithSemiColonAndTrivia() + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestImplementOnRecord_WithSemiColonAndTrivia(string record) { - await TestInRegularAndScriptAsync(@" + await TestInRegularAndScriptAsync($@" interface I -{ +{{ void M1(); -} +}} -record C : [|I|]; // hello +{record} C : [|I|]; // hello ", -@" +$@" interface I -{ +{{ void M1(); -} +}} -record C : [|I|] // hello -{ +{record} C : [|I|] // hello +{{ public void M1() - { + {{ throw new System.NotImplementedException(); - } -} + }} +}} "); } @@ -8938,5 +8941,48 @@ public void Bar(string? x) } "); } + + [WorkItem(51779, "https://github.com/dotnet/roslyn/issues/51779")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + public async Task TestImplementTwoPropertiesOfCSharp5() + { + await TestInRegularAndScriptAsync(@" +interface ITest +{ + int Bar { get; } + int Foo { get; } +} + +class Program : [|ITest|] +{ +} +", +@" +interface ITest +{ + int Bar { get; } + int Foo { get; } +} + +class Program : ITest +{ + public int Bar + { + get + { + throw new System.NotImplementedException(); + } + } + + public int Foo + { + get + { + throw new System.NotImplementedException(); + } + } +} +", parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5)); + } } } diff --git a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs index 9842299c4cd0c..a2ded4cb3e282 100644 --- a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs @@ -4,16 +4,10 @@ #nullable disable -using System.Globalization; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.AddParameter; -using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.InitializeParameter; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; -using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; @@ -166,6 +160,24 @@ interface I await VerifyCS.VerifyRefactoringAsync(code, code); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestNotOnNullableParameter() + { + var code = @" +#nullable enable + +using System; + +class C +{ + void M([||]string? s) + { + } +}"; + + await VerifyCS.VerifyRefactoringAsync(code, code); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] public async Task TestNotOnAbstractParameter() { @@ -476,6 +488,47 @@ public C(string a, string b, string c) }.RunAsync(); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestMultiNullableParametersSomeNullableReferenceTypes() + { + await new VerifyCS.Test + { + TestCode = @" +#nullable enable + +using System; + +class C +{ + public C([||]string a, string b, string? c) + { + } +}", + FixedCode = @$" +#nullable enable + +using System; + +class C +{{ + public C(string a, string b, string? c) + {{ + if (string.IsNullOrEmpty(a)) + {{ + throw new ArgumentException($""{string.Format(FeaturesResources._0_cannot_be_null_or_empty, "{nameof(a)}").Replace("\"", "\\\"")}"", nameof(a)); + }} + + if (string.IsNullOrEmpty(b)) + {{ + throw new ArgumentException($""{string.Format(FeaturesResources._0_cannot_be_null_or_empty, "{nameof(b)}").Replace("\"", "\\\"")}"", nameof(b)); + }} + }} +}}", + CodeActionIndex = 3, + CodeActionEquivalenceKey = nameof(FeaturesResources.Add_null_checks_for_all_parameters) + }.RunAsync(); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] public async Task TestCursorNotOnParameters() { diff --git a/src/EditorFeatures/CSharpTest/Intents/AddConstructorParameterIntentTests.cs b/src/EditorFeatures/CSharpTest/Intents/AddConstructorParameterIntentTests.cs new file mode 100644 index 0000000000000..348eb69da703b --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Intents/AddConstructorParameterIntentTests.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.ExternalAccess.IntelliCode.Api; +using Microsoft.CodeAnalysis.Features.Intents; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Text; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Intents +{ + [UseExportProvider] + public class AddConstructorParameterIntentTests : IntentTestsBase + { + [Fact] + public async Task AddConstructorParameterWithField() + { + var initialText = +@"class C +{ + private readonly int _someInt;{|priorSelection:|} + + public C({|typed:int som|}) + { + } +}"; + var expectedText = +@"class C +{ + private readonly int _someInt; + + public C(int someInt) + { + _someInt = someInt; + } +}"; + + await VerifyExpectedTextAsync(WellKnownIntents.AddConstructorParameter, initialText, expectedText).ConfigureAwait(false); + } + + [Fact] + public async Task AddConstructorParameterWithProperty() + { + var initialText = +@"class C +{ + public int SomeInt { get; }{|priorSelection:|} + + public C({|typed:int som|}) + { + } +}"; + var expectedText = +@"class C +{ + public int SomeInt { get; } + + public C(int someInt) + { + SomeInt = someInt; + } +}"; + + await VerifyExpectedTextAsync(WellKnownIntents.AddConstructorParameter, initialText, expectedText).ConfigureAwait(false); + } + + [Fact] + public async Task AddMultipleConstructorParameters() + { + var initialText = +@"class C +{ + {|priorSelection:private readonly int _someInt; + private readonly string _someString;|} + + public C({|typed:int som|}) + { + } +}"; + var expectedText = +@"class C +{ + private readonly int _someInt; + private readonly string _someString; + + public C(int someInt, string someString) + { + _someInt = someInt; + _someString = someString; + } +}"; + + await VerifyExpectedTextAsync(WellKnownIntents.AddConstructorParameter, initialText, expectedText).ConfigureAwait(false); + } + + [Fact] + public async Task AddConstructorParameterOnlyAddsSelected() + { + var initialText = +@"class C +{ + private readonly int _someInt;{|priorSelection:|} + private readonly string _someString; + + public C({|typed:int som|}) + { + } +}"; + var expectedText = +@"class C +{ + private readonly int _someInt; + private readonly string _someString; + + public C(int someInt) + { + _someInt = someInt; + } +}"; + + await VerifyExpectedTextAsync(WellKnownIntents.AddConstructorParameter, initialText, expectedText).ConfigureAwait(false); + } + + [Fact] + public async Task AddConstructorParameterUsesCodeStyleOption() + { + var initialText = +@"class C +{ + private readonly int _someInt;{|priorSelection:|} + + public C({|typed:int som|}) + { + } +}"; + var expectedText = +@"class C +{ + private readonly int _someInt; + + public C(int someInt) + { + this._someInt = someInt; + } +}"; + await VerifyExpectedTextAsync(WellKnownIntents.AddConstructorParameter, initialText, expectedText, + options: new OptionsCollection(LanguageNames.CSharp) + { + { CodeStyleOptions2.QualifyFieldAccess, true } + }).ConfigureAwait(false); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Intents/GenerateConstructorIntentTests.cs b/src/EditorFeatures/CSharpTest/Intents/GenerateConstructorIntentTests.cs index 72a336cf782bb..ac9403f582bbc 100644 --- a/src/EditorFeatures/CSharpTest/Intents/GenerateConstructorIntentTests.cs +++ b/src/EditorFeatures/CSharpTest/Intents/GenerateConstructorIntentTests.cs @@ -2,27 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; -using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.ExternalAccess.IntelliCode.Api; using Microsoft.CodeAnalysis.Features.Intents; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Text.Shared.Extensions; -using Microsoft.VisualStudio.Text; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Intents { [UseExportProvider] - public class GenerateConstructorIntentTests + public class GenerateConstructorIntentTests : IntentTestsBase { [Fact] public async Task GenerateConstructorSimpleResult() @@ -45,7 +35,7 @@ public C(int someInt) } }"; - await VerifyExpectedTextAsync(initialText, expectedText).ConfigureAwait(false); + await VerifyExpectedTextAsync(WellKnownIntents.GenerateConstructor, initialText, expectedText).ConfigureAwait(false); } [Fact] @@ -69,7 +59,7 @@ public C(int someInt) } }"; - await VerifyExpectedTextAsync(initialText, expectedText).ConfigureAwait(false); + await VerifyExpectedTextAsync(WellKnownIntents.GenerateConstructor, initialText, expectedText).ConfigureAwait(false); } [Fact] @@ -96,7 +86,7 @@ public C(int someInt) } }"; - await VerifyExpectedTextAsync(initialText, additionalDocuments, expectedText).ConfigureAwait(false); + await VerifyExpectedTextAsync(WellKnownIntents.GenerateConstructor, initialText, additionalDocuments, expectedText).ConfigureAwait(false); } [Fact] @@ -120,7 +110,7 @@ public C(object someObject) } }"; - await VerifyExpectedTextAsync(initialText, expectedText).ConfigureAwait(false); + await VerifyExpectedTextAsync(WellKnownIntents.GenerateConstructor, initialText, expectedText).ConfigureAwait(false); } [Fact] @@ -141,65 +131,11 @@ public async Task GenerateConstructorWithExpressionBodyOption() public C(int someInt) => _someInt = someInt; }"; - await VerifyExpectedTextAsync(initialText, expectedText, + await VerifyExpectedTextAsync(WellKnownIntents.GenerateConstructor, initialText, expectedText, options: new OptionsCollection(LanguageNames.CSharp) { { CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement } }).ConfigureAwait(false); } - - private static Task VerifyExpectedTextAsync(string markup, string expectedText, OptionsCollection? options = null) - { - return VerifyExpectedTextAsync(markup, new string[] { }, expectedText, options); - } - - private static async Task VerifyExpectedTextAsync(string activeDocument, string[] additionalDocuments, string expectedText, OptionsCollection? options = null) - { - var documentSet = additionalDocuments.Prepend(activeDocument).ToArray(); - using var workspace = TestWorkspace.CreateCSharp(documentSet, exportProvider: EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider()); - if (options != null) - { - workspace.ApplyOptions(options!); - } - - var intentSource = workspace.ExportProvider.GetExportedValue(); - - // The first document will be the active document. - var document = workspace.Documents.Single(d => d.Name == "test1.cs"); - var textBuffer = document.GetTextBuffer(); - var annotatedSpan = document.AnnotatedSpans["typed"].Single(); - - // Get the current snapshot span and selection. - var currentSelectedSpan = document.SelectedSpans.FirstOrDefault(); - if (currentSelectedSpan.IsEmpty) - { - currentSelectedSpan = TextSpan.FromBounds(annotatedSpan.End, annotatedSpan.End); - } - - var currentSnapshotSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, currentSelectedSpan.ToSpan()); - - // Determine the edits to rewind to the prior snapshot by removing the changes in the annotated span. - var rewindTextChange = new TextChange(annotatedSpan, ""); - - var intentContext = new IntentRequestContext( - WellKnownIntents.GenerateConstructor, - currentSnapshotSpan, - ImmutableArray.Create(rewindTextChange), - TextSpan.FromBounds(rewindTextChange.Span.Start, rewindTextChange.Span.Start), - intentData: null); - var results = await intentSource.ComputeIntentsAsync(intentContext, CancellationToken.None).ConfigureAwait(false); - - // For now, we're just taking the first result to match intellicode behavior. - var result = results.First(); - - using var edit = textBuffer.CreateEdit(); - foreach (var change in result.TextChanges) - { - edit.Replace(change.Span.ToSpan(), change.NewText); - } - edit.Apply(); - - Assert.Equal(expectedText, textBuffer.CurrentSnapshot.GetText()); - } } } diff --git a/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs b/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs new file mode 100644 index 0000000000000..e14a8ea6bf880 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Intents/IntentTestsBase.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.ExternalAccess.IntelliCode.Api; +using Microsoft.CodeAnalysis.Features.Intents; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Text; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Intents +{ + public class IntentTestsBase + { + internal static Task VerifyExpectedTextAsync(string intentName, string markup, string expectedText, OptionsCollection? options = null) + { + return VerifyExpectedTextAsync(intentName, markup, new string[] { }, expectedText, options); + } + + internal static async Task VerifyExpectedTextAsync(string intentName, string activeDocument, string[] additionalDocuments, string expectedText, OptionsCollection? options = null) + { + var documentSet = additionalDocuments.Prepend(activeDocument).ToArray(); + using var workspace = TestWorkspace.CreateCSharp(documentSet, exportProvider: EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider()); + if (options != null) + { + workspace.ApplyOptions(options!); + } + + var intentSource = workspace.ExportProvider.GetExportedValue(); + + // The first document will be the active document. + var document = workspace.Documents.Single(d => d.Name == "test1.cs"); + var textBuffer = document.GetTextBuffer(); + var typedSpan = document.AnnotatedSpans["typed"].Single(); + + // Get the current snapshot span and selection. + var currentSelectedSpan = document.SelectedSpans.FirstOrDefault(); + if (currentSelectedSpan.IsEmpty) + { + currentSelectedSpan = TextSpan.FromBounds(typedSpan.End, typedSpan.End); + } + + var currentSnapshotSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, currentSelectedSpan.ToSpan()); + + // Determine the edits to rewind to the prior snapshot by removing the changes in the annotated span. + var rewindTextChange = new TextChange(typedSpan, ""); + var priorSelection = TextSpan.FromBounds(rewindTextChange.Span.Start, rewindTextChange.Span.Start); + if (document.AnnotatedSpans.ContainsKey("priorSelection")) + { + priorSelection = document.AnnotatedSpans["priorSelection"].Single(); + } + + var intentContext = new IntentRequestContext( + intentName, + currentSnapshotSpan, + ImmutableArray.Create(rewindTextChange), + priorSelection, + intentData: null); + var results = await intentSource.ComputeIntentsAsync(intentContext, CancellationToken.None).ConfigureAwait(false); + + // For now, we're just taking the first result to match intellicode behavior. + var result = results.First(); + + using var edit = textBuffer.CreateEdit(); + foreach (var change in result.TextChanges) + { + edit.Replace(change.Span.ToSpan(), change.NewText); + } + edit.Apply(); + + Assert.Equal(expectedText, textBuffer.CurrentSnapshot.GetText()); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs b/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs index 8962cc97294df..9a62468cb5b7f 100644 --- a/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs +++ b/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs @@ -38,7 +38,6 @@ private static async Task>> ProduceTagsA var producer = new BraceHighlightingViewTaggerProvider( workspace.ExportProvider.GetExportedValue(), workspace.GetService(), - workspace.GetService(), AsynchronousOperationListenerProvider.NullProvider); var context = new TaggerContext( diff --git a/src/EditorFeatures/CSharpTest/Interactive/CommandArgumentsParsingTest.cs b/src/EditorFeatures/CSharpTest/Interactive/CommandArgumentsParsingTest.cs deleted file mode 100644 index 66b6a9134f2d0..0000000000000 --- a/src/EditorFeatures/CSharpTest/Interactive/CommandArgumentsParsingTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Editor.Implementation.Interactive; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Roslyn.VisualStudio.CSharp.UnitTests.Interactive -{ - public class CommandArgumentsParsingTest - { - [WpfFact] - public void Path() - { - TestPath("", expected: null, validArg: true, readToEnd: true); - TestPath("blah", expected: null, validArg: true, readToEnd: false); - TestPath(@"""blah""", expected: "blah", validArg: true, readToEnd: true); - TestPath(@"""b\l\a\h""", expected: @"b\l\a\h", validArg: true, readToEnd: true); - TestPath(@"""b\l\a\h"" // comment", expected: @"b\l\a\h", validArg: true, readToEnd: true); - - TestPath("\t \"x\tx\" \t \r\n \t ", expected: "x\tx", validArg: true, readToEnd: true); - TestPath(@"""b\l\a\h", expected: @"b\l\a\h", validArg: false); - TestPath(@"""blah" + "\r\n" + @"""", expected: "blah", validArg: false); - TestPath(@"""blah""" + "\r\n" + @"""", expected: "blah", validArg: true, readToEnd: false); - - TestPath(@"""blah//goo", expected: "blah//goo", validArg: false); - TestPath(@"""blah//goo""", expected: "blah//goo", validArg: true); - } - - private void TestPath(string args, string expected, bool validArg, bool? readToEnd = null) - { - int i = 0; - string actual; - - Assert.Equal(validArg, CommandArgumentsParser.ParsePath(args, ref i, out actual)); - Assert.Equal(expected, actual); - - if (readToEnd != null) - { - Assert.Equal(readToEnd, CommandArgumentsParser.ParseTrailingTrivia(args, ref i)); - } - } - } -} diff --git a/src/EditorFeatures/CSharpTest/Interactive/NavigateTo/InteractiveNavigateToTests.cs b/src/EditorFeatures/CSharpTest/Interactive/NavigateTo/InteractiveNavigateToTests.cs index 0dbf5f7d7e289..68a107bfef5c1 100644 --- a/src/EditorFeatures/CSharpTest/Interactive/NavigateTo/InteractiveNavigateToTests.cs +++ b/src/EditorFeatures/CSharpTest/Interactive/NavigateTo/InteractiveNavigateToTests.cs @@ -29,9 +29,9 @@ protected override TestWorkspace CreateWorkspace(string content, ExportProvider [WpfTheory] [CombinatorialData] - public async Task NoItemsForEmptyFile(TestHost testHost) + public async Task NoItemsForEmptyFile(TestHost testHost, Composition composition) { - await TestAsync(testHost, "", async w => + await TestAsync(testHost, composition, "", async w => { Assert.Empty(await _aggregator.GetItemsAsync("Hello")); }); @@ -39,9 +39,9 @@ await TestAsync(testHost, "", async w => [WpfTheory] [CombinatorialData] - public async Task FindClass(TestHost testHost) + public async Task FindClass(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { }", async w => @@ -53,9 +53,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindNestedClass(TestHost testHost) + public async Task FindNestedClass(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { class Bar @@ -73,9 +73,9 @@ internal class DogBed [WpfTheory] [CombinatorialData] - public async Task FindMemberInANestedClass(TestHost testHost) + public async Task FindMemberInANestedClass(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { class Bar @@ -96,9 +96,9 @@ public void Method() [WpfTheory] [CombinatorialData] - public async Task FindGenericClassWithConstraints(TestHost testHost) + public async Task FindGenericClassWithConstraints(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"using System.Collections; class Goo where T : IEnumerable @@ -112,9 +112,9 @@ class Goo where T : IEnumerable [WpfTheory] [CombinatorialData] - public async Task FindGenericMethodWithConstraints(TestHost testHost) + public async Task FindGenericMethodWithConstraints(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"using System; class Goo @@ -131,9 +131,9 @@ public void Bar(T item) where T : IComparable [WpfTheory] [CombinatorialData] - public async Task FindPartialClass(TestHost testHost) + public async Task FindPartialClass(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"public partial class Goo { int a; @@ -155,9 +155,9 @@ partial class Goo [WpfTheory] [CombinatorialData] - public async Task FindTypesInMetadata(TestHost testHost) + public async Task FindTypesInMetadata(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"using System; Class Program { FileStyleUriParser f; }", async w => @@ -169,9 +169,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindClassInNamespace(TestHost testHost) + public async Task FindClassInNamespace(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"namespace Bar { class Goo @@ -186,9 +186,9 @@ class Goo [WpfTheory] [CombinatorialData] - public async Task FindStruct(TestHost testHost) + public async Task FindStruct(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"struct Bar { }", async w => @@ -200,9 +200,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindEnum(TestHost testHost) + public async Task FindEnum(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"enum Colors { Red, @@ -217,9 +217,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindEnumMember(TestHost testHost) + public async Task FindEnumMember(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"enum Colors { Red, @@ -234,9 +234,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindConstField(TestHost testHost) + public async Task FindConstField(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { const int bar = 7; @@ -249,9 +249,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindVerbatimIdentifier(TestHost testHost) + public async Task FindVerbatimIdentifier(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { string @string; @@ -264,10 +264,10 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindIndexer(TestHost testHost) + public async Task FindIndexer(TestHost testHost, Composition composition) { var program = @"class Goo { int[] arr; public int this[int i] { get { return arr[i]; } set { arr[i] = value; } } }"; - await TestAsync(testHost, program, async w => + await TestAsync(testHost, composition, program, async w => { var item = (await _aggregator.GetItemsAsync("this")).Single(); VerifyNavigateToResultItem(item, "this", "[|this|][int]", PatternMatchKind.Exact, NavigateToItemKind.Property, Glyph.PropertyPublic, additionalInfo: string.Format(FeaturesResources.in_0_project_1, "Goo", "Test")); @@ -276,10 +276,10 @@ await TestAsync(testHost, program, async w => [WpfTheory] [CombinatorialData] - public async Task FindEvent(TestHost testHost) + public async Task FindEvent(TestHost testHost, Composition composition) { var program = "class Goo { public event EventHandler ChangedEventHandler; }"; - await TestAsync(testHost, program, async w => + await TestAsync(testHost, composition, program, async w => { var item = (await _aggregator.GetItemsAsync("CEH")).Single(); VerifyNavigateToResultItem(item, "ChangedEventHandler", "[|C|]hanged[|E|]vent[|H|]andler", PatternMatchKind.CamelCaseExact, NavigateToItemKind.Event, Glyph.EventPublic, additionalInfo: string.Format(FeaturesResources.in_0_project_1, "Goo", "Test")); @@ -288,9 +288,9 @@ await TestAsync(testHost, program, async w => [WpfTheory] [CombinatorialData] - public async Task FindAutoProperty(TestHost testHost) + public async Task FindAutoProperty(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { int Bar { get; set; } @@ -303,9 +303,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindMethod(TestHost testHost) + public async Task FindMethod(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { void DoSomething(); @@ -318,9 +318,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindParameterizedMethod(TestHost testHost) + public async Task FindParameterizedMethod(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { void DoSomething(int a, string b) @@ -335,9 +335,9 @@ void DoSomething(int a, string b) [WpfTheory] [CombinatorialData] - public async Task FindConstructor(TestHost testHost) + public async Task FindConstructor(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { public Goo() @@ -352,9 +352,9 @@ public Goo() [WpfTheory] [CombinatorialData] - public async Task FindParameterizedConstructor(TestHost testHost) + public async Task FindParameterizedConstructor(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { public Goo(int i) @@ -369,9 +369,9 @@ public Goo(int i) [WpfTheory] [CombinatorialData] - public async Task FindStaticConstructor(TestHost testHost) + public async Task FindStaticConstructor(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class Goo { static Goo() @@ -386,9 +386,9 @@ static Goo() [WpfTheory] [CombinatorialData] - public async Task FindPartialMethods(TestHost testHost) + public async Task FindPartialMethods(TestHost testHost, Composition composition) { - await TestAsync(testHost, "partial class Goo { partial void Bar(); } partial class Goo { partial void Bar() { Console.Write(\"hello\"); } }", async w => + await TestAsync(testHost, composition, "partial class Goo { partial void Bar(); } partial class Goo { partial void Bar() { Console.Write(\"hello\"); } }", async w => { var expecteditem1 = new NavigateToItem("Bar", NavigateToItemKind.Method, "csharp", null, null, s_emptyExactPatternMatch, null); var expecteditems = new List { expecteditem1, expecteditem1 }; @@ -401,9 +401,9 @@ public async Task FindPartialMethods(TestHost testHost) [WpfTheory] [CombinatorialData] - public async Task FindPartialMethodDefinitionOnly(TestHost testHost) + public async Task FindPartialMethodDefinitionOnly(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"partial class Goo { partial void Bar(); @@ -416,10 +416,10 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindOverriddenMembers(TestHost testHost) + public async Task FindOverriddenMembers(TestHost testHost, Composition composition) { var program = "class Goo { public virtual string Name { get; set; } } class DogBed : Goo { public override string Name { get { return base.Name; } set {} } }"; - await TestAsync(testHost, program, async w => + await TestAsync(testHost, composition, program, async w => { var expecteditem1 = new NavigateToItem("Name", NavigateToItemKind.Property, "csharp", null, null, s_emptyExactPatternMatch, null); var expecteditems = new List { expecteditem1, expecteditem1 }; @@ -446,9 +446,9 @@ await TestAsync(testHost, program, async w => [WpfTheory] [CombinatorialData] - public async Task FindInterface(TestHost testHost) + public async Task FindInterface(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"public interface IGoo { }", async w => @@ -460,9 +460,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindDelegateInNamespace(TestHost testHost) + public async Task FindDelegateInNamespace(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"namespace Goo { delegate void DoStuff(); @@ -475,9 +475,9 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task FindLambdaExpression(TestHost testHost) + public async Task FindLambdaExpression(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"using System; class Goo @@ -492,9 +492,9 @@ class Goo [WpfTheory] [CombinatorialData] - public async Task OrderingOfConstructorsAndTypes(TestHost testHost) + public async Task OrderingOfConstructorsAndTypes(TestHost testHost, Composition composition) { - await TestAsync(testHost, + await TestAsync(testHost, composition, @"class C1 { C1(int i) @@ -529,10 +529,10 @@ static C2() [WpfTheory] [CombinatorialData] - public async Task StartStopSanity(TestHost testHost) + public async Task StartStopSanity(TestHost testHost, Composition composition) { // Verify that multiple calls to start/stop and dispose don't blow up - await TestAsync(testHost, + await TestAsync(testHost, composition, @"public class Goo { }", async w => @@ -552,10 +552,10 @@ await TestAsync(testHost, [WpfTheory] [CombinatorialData] - public async Task DescriptionItems(TestHost testHost) + public async Task DescriptionItems(TestHost testHost, Composition composition) { var code = "public\r\nclass\r\nGoo\r\n{ }"; - await TestAsync(testHost, code, async w => + await TestAsync(testHost, composition, code, async w => { var item = (await _aggregator.GetItemsAsync("G")).Single(x => x.Kind != "Method"); var itemDisplay = item.DisplayFactory.CreateItemDisplay(item); @@ -576,10 +576,10 @@ void assertDescription(string label, string value) [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest1(TestHost testHost) + public async Task TermSplittingTest1(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_keyword", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive, null); @@ -596,10 +596,10 @@ await TestAsync(testHost, source, async w => [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest2(TestHost testHost) + public async Task TermSplittingTest2(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("GetKeyWord", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseExactPatternMatch, null); @@ -613,10 +613,10 @@ await TestAsync(testHost, source, async w => [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest3(TestHost testHost) + public async Task TermSplittingTest3(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseSubstringPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("GetKeyWord", NavigateToItemKind.Field, "csharp", null, null, s_emptySubstringPatternMatch, null); @@ -630,10 +630,10 @@ await TestAsync(testHost, source, async w => [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest4(TestHost testHost) + public async Task TermSplittingTest4(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var items = await _aggregator.GetItemsAsync("WKG"); Assert.Empty(items); @@ -642,10 +642,10 @@ await TestAsync(testHost, source, async w => [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest5(TestHost testHost) + public async Task TermSplittingTest5(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var item = (await _aggregator.GetItemsAsync("G_K_W")).Single(); VerifyNavigateToResultItem(item, "get_key_word", "[|g|]et[|_k|]ey[|_w|]ord", PatternMatchKind.CamelCaseExact, NavigateToItemKind.Field, Glyph.FieldPrivate); @@ -654,11 +654,11 @@ await TestAsync(testHost, source, async w => [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest7(TestHost testHost) + public async Task TermSplittingTest7(TestHost testHost, Composition composition) { ////Diff from dev10 var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseSubstringPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("GetKeyWord", NavigateToItemKind.Field, "csharp", null, null, s_emptySubstringPatternMatch, null); @@ -672,11 +672,11 @@ await TestAsync(testHost, source, async w => [WpfTheory] [CombinatorialData] - public async Task TermSplittingTest8(TestHost testHost) + public async Task TermSplittingTest8(TestHost testHost, Composition composition) { ////Diff from dev10 var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var items = await _aggregator.GetItemsAsync("GTW"); Assert.Empty(items); diff --git a/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs b/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs index d9c1ae3943071..4ab058e0cb278 100644 --- a/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs +++ b/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs @@ -61,6 +61,18 @@ ref struct S await TestInRegularAndScriptAsync(text, expected, parseOptions: s_parseOptions); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeRefStruct)] + public async Task FieldInRecordStruct() + { + var text = CreateTestSource(@" +record struct S +{ + Span[||] m; +} +"); + await TestMissingInRegularAndScriptAsync(text, new TestParameters(CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview))); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeRefStruct)] public async Task FieldInNestedClassInsideNotRefStruct() { diff --git a/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs b/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs index 782141acc492a..f96a5079a7d62 100644 --- a/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs +++ b/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs @@ -30,12 +30,12 @@ public async Task TestMethod() { await TestInRegularAndScript1Async( @" -public class Foo +public class Goo { public abstract void [|M|](); }", @" -public abstract class Foo +public abstract class Goo { public abstract void M(); }"); @@ -46,12 +46,12 @@ public async Task TestMethodEnclosingClassWithoutAccessibility() { await TestInRegularAndScript1Async( @" -class Foo +class Goo { public abstract void [|M|](); }", @" -abstract class Foo +abstract class Goo { public abstract void M(); }"); @@ -65,7 +65,7 @@ await TestInRegularAndScript1Async( /// /// Some class comment. /// -public class Foo +public class Goo { public abstract void [|M|](); }", @@ -73,7 +73,7 @@ public class Foo /// /// Some class comment. /// -public abstract class Foo +public abstract class Goo { public abstract void M(); }"); @@ -84,12 +84,12 @@ public async Task TestPropertyGetter() { await TestInRegularAndScript1Async( @" -public class Foo +public class Goo { public abstract object P { [|get|]; } }", @" -public abstract class Foo +public abstract class Goo { public abstract object P { get; } }"); @@ -100,12 +100,12 @@ public async Task TestPropertySetter() { await TestInRegularAndScript1Async( @" -public class Foo +public class Goo { public abstract object P { [|set|]; } }", @" -public abstract class Foo +public abstract class Goo { public abstract object P { set; } }"); @@ -116,12 +116,12 @@ public async Task TestIndexerGetter() { await TestInRegularAndScript1Async( @" -public class Foo +public class Goo { public abstract object this[object o] { [|get|]; } }", @" -public abstract class Foo +public abstract class Goo { public abstract object this[object o] { get; } }"); @@ -132,12 +132,12 @@ public async Task TestIndexerSetter() { await TestInRegularAndScript1Async( @" -public class Foo +public class Goo { public abstract object this[object o] { [|set|]; } }", @" -public abstract class Foo +public abstract class Goo { public abstract object this[object o] { set; } }"); @@ -148,21 +148,21 @@ public async Task TestPartialClass() { await TestInRegularAndScript1Async( @" -public partial class Foo +public partial class Goo { public abstract void [|M|](); } -public partial class Foo +public partial class Goo { }", @" -public partial abstract class Foo +public partial abstract class Goo { public abstract void M(); } -public partial class Foo +public partial class Goo { }"); } @@ -172,7 +172,7 @@ public async Task TestEventAdd() { await TestMissingInRegularAndScriptAsync( @" -public class Foo +public class Goo { public abstract event System.EventHandler E { [|add|]; } }"); @@ -183,7 +183,7 @@ public async Task TestEventRemove() { await TestMissingInRegularAndScriptAsync( @" -public class Foo +public class Goo { public abstract event System.EventHandler E { [|remove|]; } }"); @@ -194,7 +194,7 @@ public async Task TestMethodWithBody() { await TestMissingInRegularAndScriptAsync( @" -public class Foo +public class Goo { public abstract int [|M|]() => 3; }"); @@ -205,7 +205,7 @@ public async Task TestPropertyGetterWithArrowBody() { await TestMissingInRegularAndScriptAsync( @" -public class Foo +public class Goo { public abstract int [|P|] => 3; }"); @@ -216,7 +216,7 @@ public async Task TestPropertyGetterWithBody() { await TestMissingInRegularAndScriptAsync( @" -public class Foo +public class Goo { public abstract int P { @@ -234,7 +234,7 @@ public class C { public struct S { - public abstract void [|Foo|](); + public abstract void [|Goo|](); } }"); } @@ -244,7 +244,7 @@ public async Task TestMethodEnclosingClassStatic() { await TestMissingInRegularAndScriptAsync( @" -public static class Foo +public static class Goo { public abstract void [|M|](); }"); @@ -255,17 +255,43 @@ public async Task TestRecord() { await TestInRegularAndScript1Async( @" -public record Foo +public record Goo { public abstract void [|M|](); }", @" -public abstract record Foo +public abstract record Goo { public abstract void M(); }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeTypeAbstract)] + public async Task TestRecordClass() + { + await TestInRegularAndScript1Async( +@" +public record class Goo +{ + public abstract void [|M|](); +}", +@" +public abstract record class Goo +{ + public abstract void M(); +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeTypeAbstract)] + public async Task TestRecordStruct() + { + await TestMissingInRegularAndScriptAsync(@" +public record struct Goo +{ + public abstract void [|M|](); +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeTypeAbstract)] public async Task FixAll() { diff --git a/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj b/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj index 9d45d519b36c2..5776822c7db8b 100644 --- a/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj +++ b/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj @@ -62,14 +62,6 @@ - - - - - - - - diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs index 62bc273717f04..af3a2f82a80f5 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Xml.Linq; using Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests; @@ -34,9 +35,9 @@ protected override TestWorkspace CreateWorkspace(string content, ExportProvider [Theory] [CombinatorialData] - public async Task NoItemsForEmptyFile(TestHost testHost) + public async Task NoItemsForEmptyFile(TestHost testHost, Composition composition) { - await TestAsync(testHost, "", async w => + await TestAsync(testHost, composition, "", async w => { Assert.Empty(await _aggregator.GetItemsAsync("Hello")); }); @@ -44,10 +45,10 @@ await TestAsync(testHost, "", async w => [Theory] [CombinatorialData] - public async Task FindClass(TestHost testHost) + public async Task FindClass(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { }", async w => { @@ -58,10 +59,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindRecord(TestHost testHost) + public async Task FindRecord(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"record Goo +testHost, composition, @"record Goo { }", async w => { @@ -72,10 +73,46 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindVerbatimClass(TestHost testHost) + public async Task FindRecordClass(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class @static +testHost, composition, @"record class Goo +{ +}", async w => + { + var item = (await _aggregator.GetItemsAsync("Goo")).Single(x => x.Kind != "Method"); + VerifyNavigateToResultItem(item, "Goo", "[|Goo|]", PatternMatchKind.Exact, NavigateToItemKind.Class, Glyph.ClassInternal); + }); + } + + [Theory] + [CombinatorialData] + public async Task FindRecordStruct(TestHost testHost, Composition composition) + { + var content = XElement.Parse(@" + + + +record struct Goo +{ +} + + + +"); + await TestAsync(testHost, composition, content, async w => + { + var item = (await _aggregator.GetItemsAsync("Goo")).Single(x => x.Kind != "Method"); + VerifyNavigateToResultItem(item, "Goo", "[|Goo|]", PatternMatchKind.Exact, NavigateToItemKind.Structure, Glyph.StructureInternal); + }); + } + + [Theory] + [CombinatorialData] + public async Task FindVerbatimClass(TestHost testHost, Composition composition) + { + await TestAsync( +testHost, composition, @"class @static { }", async w => { @@ -90,10 +127,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindNestedClass(TestHost testHost) + public async Task FindNestedClass(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { class Bar { @@ -110,10 +147,10 @@ internal class DogBed [Theory] [CombinatorialData] - public async Task FindMemberInANestedClass(TestHost testHost) + public async Task FindMemberInANestedClass(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { class Bar { @@ -133,10 +170,10 @@ public void Method() [Theory] [CombinatorialData] - public async Task FindGenericClassWithConstraints(TestHost testHost) + public async Task FindGenericClassWithConstraints(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"using System.Collections; +testHost, composition, @"using System.Collections; class Goo where T : IEnumerable { @@ -149,10 +186,10 @@ class Goo where T : IEnumerable [Theory] [CombinatorialData] - public async Task FindGenericMethodWithConstraints(TestHost testHost) + public async Task FindGenericMethodWithConstraints(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"using System; +testHost, composition, @"using System; class Goo { @@ -168,10 +205,10 @@ public void Bar(T item) where T : IComparable [Theory] [CombinatorialData] - public async Task FindPartialClass(TestHost testHost) + public async Task FindPartialClass(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"public partial class Goo +testHost, composition, @"public partial class Goo { int a; } @@ -192,10 +229,10 @@ partial class Goo [Theory] [CombinatorialData] - public async Task FindTypesInMetadata(TestHost testHost) + public async Task FindTypesInMetadata(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"using System; +testHost, composition, @"using System; Class Program { FileStyleUriParser f; }", async w => { @@ -206,10 +243,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindClassInNamespace(TestHost testHost) + public async Task FindClassInNamespace(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"namespace Bar +testHost, composition, @"namespace Bar { class Goo { @@ -223,10 +260,10 @@ class Goo [Theory] [CombinatorialData] - public async Task FindStruct(TestHost testHost) + public async Task FindStruct(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"struct Bar +testHost, composition, @"struct Bar { }", async w => { @@ -237,10 +274,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindEnum(TestHost testHost) + public async Task FindEnum(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"enum Colors +testHost, composition, @"enum Colors { Red, Green, @@ -254,10 +291,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindEnumMember(TestHost testHost) + public async Task FindEnumMember(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"enum Colors +testHost, composition, @"enum Colors { Red, Green, @@ -271,10 +308,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindField1(TestHost testHost) + public async Task FindField1(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int bar; }", async w => @@ -286,10 +323,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindField2(TestHost testHost) + public async Task FindField2(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int bar; }", async w => @@ -301,10 +338,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindField3(TestHost testHost) + public async Task FindField3(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int bar; }", async w => @@ -315,10 +352,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindVerbatimField(TestHost testHost) + public async Task FindVerbatimField(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int @string; }", async w => @@ -334,10 +371,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindPtrField1(TestHost testHost) + public async Task FindPtrField1(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int* bar; }", async w => @@ -348,10 +385,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindPtrField2(TestHost testHost) + public async Task FindPtrField2(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int* bar; }", async w => @@ -363,10 +400,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindConstField(TestHost testHost) + public async Task FindConstField(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { const int bar = 7; }", async w => @@ -378,10 +415,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindIndexer(TestHost testHost) + public async Task FindIndexer(TestHost testHost, Composition composition) { var program = @"class Goo { int[] arr; public int this[int i] { get { return arr[i]; } set { arr[i] = value; } } }"; - await TestAsync(testHost, program, async w => + await TestAsync(testHost, composition, program, async w => { var item = (await _aggregator.GetItemsAsync("this")).Single(); VerifyNavigateToResultItem(item, "this", "[|this|][int]", PatternMatchKind.Exact, NavigateToItemKind.Property, Glyph.PropertyPublic, additionalInfo: string.Format(FeaturesResources.in_0_project_1, "Goo", "Test")); @@ -390,10 +427,10 @@ await TestAsync(testHost, program, async w => [Theory] [CombinatorialData] - public async Task FindEvent(TestHost testHost) + public async Task FindEvent(TestHost testHost, Composition composition) { var program = "class Goo { public event EventHandler ChangedEventHandler; }"; - await TestAsync(testHost, program, async w => + await TestAsync(testHost, composition, program, async w => { var item = (await _aggregator.GetItemsAsync("CEH")).Single(); VerifyNavigateToResultItem(item, "ChangedEventHandler", "[|C|]hanged[|E|]vent[|H|]andler", PatternMatchKind.CamelCaseExact, NavigateToItemKind.Event, Glyph.EventPublic, additionalInfo: string.Format(FeaturesResources.in_0_project_1, "Goo", "Test")); @@ -402,10 +439,10 @@ await TestAsync(testHost, program, async w => [Theory] [CombinatorialData] - public async Task FindAutoProperty(TestHost testHost) + public async Task FindAutoProperty(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { int Bar { get; set; } }", async w => @@ -417,10 +454,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindMethod(TestHost testHost) + public async Task FindMethod(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { void DoSomething(); }", async w => @@ -432,10 +469,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindVerbatimMethod(TestHost testHost) + public async Task FindVerbatimMethod(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { void @static(); }", async w => @@ -451,10 +488,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindParameterizedMethod(TestHost testHost) + public async Task FindParameterizedMethod(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { void DoSomething(int a, string b) { @@ -468,10 +505,10 @@ void DoSomething(int a, string b) [Theory] [CombinatorialData] - public async Task FindConstructor(TestHost testHost) + public async Task FindConstructor(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { public Goo() { @@ -485,10 +522,10 @@ public Goo() [Theory] [CombinatorialData] - public async Task FindParameterizedConstructor(TestHost testHost) + public async Task FindParameterizedConstructor(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { public Goo(int i) { @@ -502,10 +539,10 @@ public Goo(int i) [Theory] [CombinatorialData] - public async Task FindStaticConstructor(TestHost testHost) + public async Task FindStaticConstructor(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { static Goo() { @@ -519,9 +556,9 @@ static Goo() [Theory] [CombinatorialData] - public async Task FindPartialMethods(TestHost testHost) + public async Task FindPartialMethods(TestHost testHost, Composition composition) { - await TestAsync(testHost, "partial class Goo { partial void Bar(); } partial class Goo { partial void Bar() { Console.Write(\"hello\"); } }", async w => + await TestAsync(testHost, composition, "partial class Goo { partial void Bar(); } partial class Goo { partial void Bar() { Console.Write(\"hello\"); } }", async w => { var expecteditem1 = new NavigateToItem("Bar", NavigateToItemKind.Method, "csharp", null, null, s_emptyExactPatternMatch, null); var expecteditems = new List { expecteditem1, expecteditem1 }; @@ -534,10 +571,10 @@ public async Task FindPartialMethods(TestHost testHost) [Theory] [CombinatorialData] - public async Task FindPartialMethodDefinitionOnly(TestHost testHost) + public async Task FindPartialMethodDefinitionOnly(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"partial class Goo +testHost, composition, @"partial class Goo { partial void Bar(); }", async w => @@ -549,10 +586,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindPartialMethodImplementationOnly(TestHost testHost) + public async Task FindPartialMethodImplementationOnly(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"partial class Goo +testHost, composition, @"partial class Goo { partial void Bar() { @@ -566,10 +603,10 @@ partial void Bar() [Theory] [CombinatorialData] - public async Task FindOverriddenMembers(TestHost testHost) + public async Task FindOverriddenMembers(TestHost testHost, Composition composition) { var program = "class Goo { public virtual string Name { get; set; } } class DogBed : Goo { public override string Name { get { return base.Name; } set {} } }"; - await TestAsync(testHost, program, async w => + await TestAsync(testHost, composition, program, async w => { var expecteditem1 = new NavigateToItem("Name", NavigateToItemKind.Property, "csharp", null, null, s_emptyExactPatternMatch, null); var expecteditems = new List { expecteditem1, expecteditem1 }; @@ -596,10 +633,10 @@ await TestAsync(testHost, program, async w => [Theory] [CombinatorialData] - public async Task FindInterface(TestHost testHost) + public async Task FindInterface(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"public interface IGoo +testHost, composition, @"public interface IGoo { }", async w => { @@ -610,10 +647,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindDelegateInNamespace(TestHost testHost) + public async Task FindDelegateInNamespace(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"namespace Goo +testHost, composition, @"namespace Goo { delegate void DoStuff(); }", async w => @@ -625,10 +662,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindLambdaExpression(TestHost testHost) + public async Task FindLambdaExpression(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"using System; +testHost, composition, @"using System; class Goo { @@ -642,10 +679,10 @@ class Goo [Theory] [CombinatorialData] - public async Task FindArray(TestHost testHost) + public async Task FindArray(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { object[] itemArray; }", async w => @@ -657,10 +694,10 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task FindClassAndMethodWithSameName(TestHost testHost) + public async Task FindClassAndMethodWithSameName(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class Goo +testHost, composition, @"class Goo { } @@ -683,10 +720,10 @@ void Goo() [Theory] [CombinatorialData] - public async Task FindMethodNestedInGenericTypes(TestHost testHost) + public async Task FindMethodNestedInGenericTypes(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class A +testHost, composition, @"class A { class B { @@ -706,10 +743,10 @@ void M() [Theory] [CombinatorialData] - public async Task OrderingOfConstructorsAndTypes(TestHost testHost) + public async Task OrderingOfConstructorsAndTypes(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class C1 +testHost, composition, @"class C1 { C1(int i) { @@ -743,10 +780,10 @@ static C2() [Theory] [CombinatorialData] - public async Task NavigateToMethodWithNullableParameter(TestHost testHost) + public async Task NavigateToMethodWithNullableParameter(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class C +testHost, composition, @"class C { void M(object? o) { @@ -760,11 +797,11 @@ void M(object? o) [Theory] [CombinatorialData] - public async Task StartStopSanity(TestHost testHost) + public async Task StartStopSanity(TestHost testHost, Composition composition) { // Verify that multiple calls to start/stop and dispose don't blow up await TestAsync( -testHost, @"public class Goo +testHost, composition, @"public class Goo { }", async w => { @@ -783,9 +820,9 @@ await TestAsync( [Theory] [CombinatorialData] - public async Task DescriptionItems(TestHost testHost) + public async Task DescriptionItems(TestHost testHost, Composition composition) { - await TestAsync(testHost, "public\r\nclass\r\nGoo\r\n{ }", async w => + await TestAsync(testHost, composition, "public\r\nclass\r\nGoo\r\n{ }", async w => { var item = (await _aggregator.GetItemsAsync("G")).Single(x => x.Kind != "Method"); var itemDisplay = item.DisplayFactory.CreateItemDisplay(item); @@ -806,10 +843,10 @@ void assertDescription(string label, string value) [Theory] [CombinatorialData] - public async Task TermSplittingTest1(TestHost testHost) + public async Task TermSplittingTest1(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_keyword", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive, null); @@ -826,10 +863,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TermSplittingTest2(TestHost testHost) + public async Task TermSplittingTest2(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("GetKeyWord", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseExactPatternMatch, null); @@ -843,10 +880,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TermSplittingTest3(TestHost testHost) + public async Task TermSplittingTest3(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditem1 = new NavigateToItem("get_key_word", NavigateToItemKind.Field, "csharp", null, null, s_emptyCamelCaseSubstringPatternMatch_NotCaseSensitive, null); var expecteditem2 = new NavigateToItem("GetKeyWord", NavigateToItemKind.Field, "csharp", null, null, s_emptySubstringPatternMatch, null); @@ -860,10 +897,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TermSplittingTest4(TestHost testHost) + public async Task TermSplittingTest4(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var items = await _aggregator.GetItemsAsync("WKG"); Assert.Empty(items); @@ -872,10 +909,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TermSplittingTest5(TestHost testHost) + public async Task TermSplittingTest5(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var item = (await _aggregator.GetItemsAsync("G_K_W")).Single(); VerifyNavigateToResultItem(item, "get_key_word", "[|g|]et[|_k|]ey[|_w|]ord", PatternMatchKind.CamelCaseExact, NavigateToItemKind.Field, Glyph.FieldPrivate); @@ -884,10 +921,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TermSplittingTest6(TestHost testHost) + public async Task TermSplittingTest6(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -905,10 +942,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TermSplittingTest7(TestHost testHost) + public async Task TermSplittingTest7(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var items = await _aggregator.GetItemsAsync("GTW"); Assert.Empty(items); @@ -917,7 +954,7 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task TestIndexer1(TestHost testHost) + public async Task TestIndexer1(TestHost testHost, Composition composition) { var source = @"class C @@ -933,7 +970,7 @@ void Goo() var b = q[4]; } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -948,10 +985,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task DottedPattern1(TestHost testHost) + public async Task DottedPattern1(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -966,10 +1003,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task DottedPattern2(TestHost testHost) + public async Task DottedPattern2(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -983,10 +1020,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task DottedPattern3(TestHost testHost) + public async Task DottedPattern3(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -1001,10 +1038,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task DottedPattern4(TestHost testHost) + public async Task DottedPattern4(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -1019,10 +1056,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task DottedPattern5(TestHost testHost) + public async Task DottedPattern5(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -1037,10 +1074,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] - public async Task DottedPattern6(TestHost testHost) + public async Task DottedPattern6(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -1055,10 +1092,10 @@ await TestAsync(testHost, source, async w => [Theory] [CombinatorialData] [WorkItem(7855, "https://github.com/dotnet/Roslyn/issues/7855")] - public async Task DottedPattern7(TestHost testHost) + public async Task DottedPattern7(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -1073,10 +1110,10 @@ await TestAsync(testHost, source, async w => [Theory, WorkItem(46267, "https://github.com/dotnet/roslyn/issues/46267")] [CombinatorialData] - public async Task DottedPatternMatchKind(TestHost testHost) + public async Task DottedPatternMatchKind(TestHost testHost, Composition composition) { var source = "namespace System { class Console { void Write(string s) { } void WriteLine(string s) { } } }"; - await TestAsync(testHost, source, async w => + await TestAsync(testHost, composition, source, async w => { var expecteditems = new List { @@ -1138,10 +1175,10 @@ public void VisibleMethod_Generated() { } [WorkItem(11474, "https://github.com/dotnet/roslyn/pull/11474")] [Theory] [CombinatorialData] - public async Task FindFuzzy1(TestHost testHost) + public async Task FindFuzzy1(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class C +testHost, composition, @"class C { public void ToError() { @@ -1156,10 +1193,10 @@ public void ToError() [WorkItem(18843, "https://github.com/dotnet/roslyn/issues/18843")] [Theory] [CombinatorialData] - public async Task Test__arglist(TestHost testHost) + public async Task Test__arglist(TestHost testHost, Composition composition) { await TestAsync( -testHost, @"class C +testHost, composition, @"class C { public void ToError(__arglist) { diff --git a/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs b/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs index 6a382abadd4b5..99ca6817b62fd 100644 --- a/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs +++ b/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs @@ -24,6 +24,8 @@ public class OrganizeTypeDeclarationTests : AbstractOrganizerTests [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] public async Task TestFieldsWithoutInitializers1(string typeKind) { var initial = @@ -42,6 +44,30 @@ public async Task TestFieldsWithoutInitializers1(string typeKind) await CheckAsync(initial, final); } + [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestNestedTypes(string typeKind) + { + var initial = +$@"class C {{ + {typeKind} Nested1 {{ }} + {typeKind} Nested2 {{ }} + int A; +}}"; + + var final = +$@"class C {{ + int A; + {typeKind} Nested1 {{ }} + {typeKind} Nested2 {{ }} +}}"; + await CheckAsync(initial, final); + } + [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] @@ -66,6 +92,7 @@ public async Task TestFieldsWithoutInitializers2(string typeKind) [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] + [InlineData("record struct")] public async Task TestFieldsWithInitializers1(string typeKind) { var initial = @@ -287,6 +314,7 @@ public async Task TestStaticInstance(string typeKind) [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] + [InlineData("record struct")] public async Task TestAccessibility(string typeKind) { var initial = diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index 5cf608e1aa700..4973fdf7d92fe 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -1651,6 +1651,147 @@ await TestInMethodAsync(@"Consol$$eColor c", MainDescription("enum System.ConsoleColor")); } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_Definition() + { + await TestInClassAsync(@"enum E$$ : byte { A, B }", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsField() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private E$$ _E; +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsProperty() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private E$$ E{ get; set; }; +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsParameter() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M(E$$ e) { } +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsReturnType() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private E$$ M() { } +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsLocal() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M() +{ + E$$ e = default; +} +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_OnMemberAccessOnType() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M() +{ + var ea = E$$.A; +} +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_NotOnMemberAccessOnMember() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M() +{ + var ea = E.A$$; +} +", + MainDescription("E.A = 0")); + } + + [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + [InlineData("byte", "byte")] + [InlineData("byte", "System.Byte")] + [InlineData("sbyte", "sbyte")] + [InlineData("sbyte", "System.SByte")] + [InlineData("short", "short")] + [InlineData("short", "System.Int16")] + [InlineData("ushort", "ushort")] + [InlineData("ushort", "System.UInt16")] + // int is the default type and is not shown + [InlineData("uint", "uint")] + [InlineData("uint", "System.UInt32")] + [InlineData("long", "long")] + [InlineData("long", "System.Int64")] + [InlineData("ulong", "ulong")] + [InlineData("ulong", "System.UInt64")] + public async Task EnumNonDefaultUnderlyingType_ShowForNonDefaultTypes(string displayTypeName, string underlyingTypeName) + { + await TestInClassAsync(@$" +enum E$$ : {underlyingTypeName} +{{ + A, B +}}", + MainDescription($"enum C.E : {displayTypeName}")); + } + + [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + [InlineData("")] + [InlineData(": int")] + [InlineData(": System.Int32")] + public async Task EnumNonDefaultUnderlyingType_DontShowForDefaultType(string defaultType) + { + await TestInClassAsync(@$" +enum E$$ {defaultType} +{{ + A, B +}}", + MainDescription("enum C.E")); + } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] public async Task EnumMemberNameFromMetadata() { @@ -7088,6 +7229,45 @@ public class Student : Person { public Student() : $$base(0) { } } ", MainDescription("Person.Person(int id)")); } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task QuickInfoRecordClass() + { + await TestWithOptionsAsync( + Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9), +@"record class Person(string First, string Last) +{ + void M($$Person p) + { + } +}", MainDescription("record Person")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task QuickInfoRecordStruct() + { + await TestWithOptionsAsync( + Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9), +@"record struct Person(string First, string Last) +{ + void M($$Person p) + { + } +}", MainDescription("record struct Person")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task QuickInfoReadOnlyRecordStruct() + { + await TestWithOptionsAsync( + Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9), +@"readonly record struct Person(string First, string Last) +{ + void M($$Person p) + { + } +}", MainDescription("readonly record struct Person")); + } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] [WorkItem(51615, "https://github.com/dotnet/roslyn/issues/51615")] public async Task TestVarPatternOnVarKeyword() diff --git a/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs b/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs new file mode 100644 index 0000000000000..4860b46b1673a --- /dev/null +++ b/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs @@ -0,0 +1,1000 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.ReassignedVariable; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReassignedVariable +{ + public class CSharpReassignedVariableTests : AbstractReassignedVariableTests + { + protected override TestWorkspace CreateWorkspace(string markup) + => TestWorkspace.CreateCSharp(markup); + + [Fact] + public async Task TestNoParameterReassignment() + { + await TestAsync( +@"class C +{ + void M(int p) + { + } +}"); + } + + [Fact] + public async Task TestParameterReassignment() + { + await TestAsync( +@"class C +{ + void M(int [|p|]) + { + [|p|] = 1; + } +}"); + } + + [Fact] + public async Task TestParameterReassignmentWhenReadAfter() + { + await TestAsync( +@" +using System; +class C +{ + void M(int [|p|]) + { + [|p|] = 1; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestParameterReassignmentWhenReadBefore() + { + await TestAsync( +@" +using System; +class C +{ + void M(int [|p|]) + { + Console.WriteLine([|p|]); + [|p|] = 1; + } +}"); + } + + [Fact] + public async Task TestParameterReassignmentWhenReadWithDefaultValue() + { + await TestAsync( +@" +using System; +class C +{ + void M(int [|p|] = 1) + { + Console.WriteLine([|p|]); + [|p|] = 1; + } +}"); + } + + [Fact] + public async Task TestParameterWithExprBodyWithReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M(int [|p|]) => Console.WriteLine([|p|]++); +}"); + } + + [Fact] + public async Task TestLocalFunctionWithExprBodyWithReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + void Local(int [|p|]) + => Console.WriteLine([|p|]++); +}"); + } + + [Fact] + public async Task TestIndexerWithWriteInExprBody() + { + await TestAsync( +@" +using System; +class C +{ + int this[int [|p|]] => [|p|]++; +}"); + } + + [Fact] + public async Task TestIndexerWithWriteInGetter1() + { + await TestAsync( +@" +using System; +class C +{ + int this[int [|p|]] { get => [|p|]++; } +}"); + } + + [Fact] + public async Task TestIndexerWithWriteInGetter2() + { + await TestAsync( +@" +using System; +class C +{ + int this[int [|p|]] { get { [|p|]++; } } +}"); + } + + [Fact] + public async Task TestIndexerWithWriteInSetter1() + { + await TestAsync( +@" +using System; +class C +{ + int this[int [|p|]] { set => [|p|]++; } +}"); + } + + [Fact] + public async Task TestIndexerWithWriteInSetter2() + { + await TestAsync( +@" +using System; +class C +{ + int this[int [|p|]] { set { [|p|]++; } } +}"); + } + + [Fact] + public async Task TestPropertyWithAssignmentToValue1() + { + await TestAsync( +@" +using System; +class C +{ + int Goo { set => [|value|] = [|value|] + 1; } +}"); + } + + [Fact] + public async Task TestPropertyWithAssignmentToValue2() + { + await TestAsync( +@" +using System; +class C +{ + int Goo { set { [|value|] = [|value|] + 1; } } +}"); + } + + [Fact] + public async Task TestEventAddWithAssignmentToValue() + { + await TestAsync( +@" +using System; +class C +{ + event Action Goo { add { [|value|] = null; } remove { } } +}"); + } + + [Fact] + public async Task TestEventRemoveWithAssignmentToValue() + { + await TestAsync( +@" +using System; +class C +{ + event Action Goo { add { } remove { [|value|] = null; } } +}"); + } + + [Fact] + public async Task TestLambdaParameterWithoutReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + Action a = x => Console.WriteLine(x); + } +}"); + } + + [Fact] + public async Task TestLambdaParameterWithReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + Action a = [|x|] => Console.WriteLine([|x|]++); + } +}"); + } + + [Fact] + public async Task TestLambdaParameterWithReassignment2() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + Action a = (int [|x|]) => Console.WriteLine([|x|]++); + } +}"); + } + + [Fact] + public async Task TestLocalWithoutInitializerWithoutReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M(bool b) + { + int p; + if (b) + p = 1; + else + p = 2; + + Console.WriteLine(p); + } +}"); + } + + [Fact] + public async Task TestLocalWithoutInitializerWithReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M(bool b) + { + int [|p|]; + if (b) + [|p|] = 1; + else + [|p|] = 2; + + [|p|] = 0; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestLocalDeclaredByPattern() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + if (0 is var [|p|]) [|p|] = 0; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestLocalDeclaredByPatternButAssignedInFalseBranch() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + if (0 is var [|p|]) + { + } + else + { + [|p|] = 0; + } + + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestLocalDeclaredByPositionalPattern() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + if ((0, 1) is var ([|p|], _)) [|p|] = 0; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestLocalDeclaredByOutVar() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + M2(out var [|p|]); + [|p|] = 0; + Console.WriteLine([|p|]); + } + + void M2(out int p) => p = 0; +}"); + } + + [Fact] + public async Task TestOutParameterCausingReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int [|p|] = 0; + M2(out [|p|]); + Console.WriteLine([|p|]); + } + + void M2(out int p) => p = 0; +}"); + } + + [Fact] + public async Task TestOutParameterWithoutReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int p; + M2(out p); + Console.WriteLine(p); + } + + void M2(out int p) => p = 0; +}"); + } + + [Fact] + public async Task AssignmentThroughOutParameter() + { + await TestAsync( +@" +using System; +class C +{ + void M(out int [|p|]) + { + [|p|] = 0; + [|p|] = 1; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestOutParameterReassignmentOneWrites() + { + await TestAsync( +@" +using System; +class C +{ + void M(out int p) + { + p = ref p; + Console.WriteLine(p); + } +}"); + } + + [Fact] + public async Task AssignmentThroughRefParameter() + { + await TestAsync( +@" +using System; +class C +{ + void M(ref int [|p|]) + { + [|p|] = 0; + [|p|] = 1; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestRefParameterReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M(ref int [|p|]) + { + [|p|] = ref [|p|]; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task AssignmentThroughRefLocal() + { + await TestAsync( +@" +using System; +class C +{ + void M(ref int [|p|]) + { + ref var [|local|] = ref [|p|]; + [|local|] = 0; + [|local|] = 1; + Console.WriteLine([|local|]); + } +}"); + } + + [Fact] + public async Task TestRefLocalReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M(ref int [|p|]) + { + // p is statically detected as overwritten (even though it is not written at runtime) + // due to a limitation in alias analysis. + ref var [|local|] = ref [|p|]; + [|local|] = ref [|p|]; + Console.WriteLine([|local|]); + } +}"); + } + + [Fact] + public async Task AssignmentThroughPointerIsNotAssignmentOfTheVariableItself() + { + await TestAsync( +@" +using System; +class C +{ + unsafe void M(int* p) + { + *p = 4; + Console.WriteLine((IntPtr)p); + } +}"); + } + + [Fact] + public async Task TestPointerVariableReassignment() + { + await TestAsync( +@" +using System; +class C +{ + unsafe void M(int* [|p|]) + { + [|p|] = null; + Console.WriteLine((IntPtr)[|p|]); + } +}"); + } + + [Fact] + public async Task TestRefParameterCausingPossibleReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int [|p|] = 0; + M2(ref [|p|]); + Console.WriteLine([|p|]); + } + + void M2(ref int p) { } +}"); + } + + [Fact] + public async Task TestVolatileRefReadParameterCausingPossibleReassignment() + { + await TestAsync( +@" +using System; +using System.Threading; +class C +{ + void M() + { + // p is statically detected as overwritten (even though it is not written at runtime) + // due to a limitation in ref analysis. + int [|p|] = 0; + Volatile.Read(ref [|p|]); + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestRefParameterWithoutReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int p; + M2(ref p); + Console.WriteLine(p); + } + + void M2(ref int p) { } +}"); + } + + [Fact] + public async Task TestRefLocalCausingPossibleReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int [|p|] = 0; + ref int refP = ref [|p|]; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestReadonlyRefLocalWithNoReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int p = 0; + ref readonly int refP = ref p; + Console.WriteLine(p); + } +}"); + } + + [Fact] + public async Task TestPointerCausingPossibleReassignment() + { + await TestAsync( +@" +using System; +class C +{ + unsafe void M() + { + int [|p|] = 0; + int* pointer = &[|p|]; + Console.WriteLine([|p|]); + } +}"); + } + + [Fact] + public async Task TestRefExtensionMethodCausingPossibleReassignment() + { + await TestAsync( +@" +using System; +static class C +{ + void M() + { + int [|p|] = 0; + [|p|].M2(); + Console.WriteLine([|p|]); + } + + static void M2(this ref int p) { } +}"); + } + + [Fact] + public async Task TestMutatingStructMethod() + { + await TestAsync( +@" +using System; +struct S +{ + int f; + + void M(S p) + { + p.MutatingMethod(); + Console.WriteLine(p); + } + + void MutatingMethod() => this = default; +}"); + } + + [Fact] + public async Task TestReassignmentWhenDeclaredWithDeconstruction() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + var ([|x|], y) = Goo(); + [|x|] = 0; + Console.WriteLine([|x|]); + } + + (int x, int y) Goo() => default; +}"); + } + + [Fact] + public async Task TestReassignmentThroughDeconstruction() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + var [|x|] = 0; + ([|x|], _) = Goo(); + Console.WriteLine([|x|]); + } + + (int x, int y) Goo() => default; +}"); + } + + [Fact] + public async Task TestTopLevelNotReassigned() + { + await TestAsync( +@" +int p; +p = 0; +Console.WriteLine(p); +"); + } + + [Fact] + public async Task TestTopLevelReassigned() + { + await TestAsync( +@" +int [|p|] = 1; +[|p|] = 0; +Console.WriteLine([|p|]); +"); + } + + [Fact] + public async Task TestTopLevelArgsParameterNotReassigned() + { + await TestAsync( +@" +Console.WriteLine(args); +"); + } + + [Fact] + public async Task TestTopLevelArgsParameterReassigned() + { + await TestAsync( +@" +[|args|] = null +Console.WriteLine([|args|]); +"); + } + + [Fact] + public async Task TestUsedInThisBase1() + { + await TestAsync( +@" +class C +{ + public C(int [|x|]) + : this([|x|]++, true) + { + } + + public C(int x, bool b) + { + } +} +"); + } + + [Fact] + public async Task TestUsedInThisBase2() + { + await TestAsync( +@" +class C +{ + public C(string s) + : this(int.TryParse(s, out var [|x|]) ? [|x|]++ : 0, true) + { + } + + public C(int x, bool b) + { + } +} +"); + } + + [Fact] + public async Task TestRecord1() + { + await TestAsync( +@" +record X(int [|x|]) : Y([|x|]++) +{ +} + +record Y(int x) +{ +} +"); + } + + [Fact] + public async Task TestExceptionVariableReassignment() + { + // Note: this is a bug. But the test currently tracks the current behavior. Fixing this + // is just not deemed worth it currently. + await TestAsync( +@" +using System; +class C +{ + void M() + { + try { } + catch (Exception ex) + { + [|ex|] = null; + } + } +}"); + } + + [Fact] + public async Task TestLocalReassignedInExceptionFilter() + { + // Note: this is a bug. But the test currently tracks the current behavior. Fixing this + // is just not deemed worth it currently. + await TestAsync( +@" +using System; +class C +{ + void M() + { + try { } + catch (Exception ex) when (([|ex|] = null) == null) { } + } +}"); + } + + [Fact] + public async Task TestLocalReassignedInCaseGuard() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + switch (1) + { + case var [|x|] when [|x|]++ == 2: break; + } + } +}"); + } + + [Fact] + public async Task TestLocalWithMultipleDeclarators() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int a, [|b|] = 1, c; + [|b|] = 2; + Console.WriteLine([|b|]); + } +}"); + } + + [Fact] + public async Task TestForLoop() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + for (int [|i|] = 0; [|i|] < 10; [|i|]++) + Console.WriteLine([|i|]); + } +}"); + } + + [Fact] + public async Task TestForeach() + { + await TestAsync( +@" +using System; +class C +{ + void M(string[] args) + { + foreach (var arg in args) + Console.WriteLine(arg); + } +}"); + } + + [Fact] + public async Task TestWriteThroughOneBranch() + { + await TestAsync( +@" +using System; +class C +{ + void M() + { + int p; + if (p) + p = 1; + + p = 0; + Console.WriteLine(p); + } +}"); + } + + [Fact] + public async Task TestDuplicateMethod() + { + await TestAsync( +@"class C +{ + void M(int [|p|]) + { + [|p|] = 1; + } + + void M(int [|p|]) + { + [|p|] = 1; + } +}"); + } + + [Fact] + public async Task TestDuplicateParameter() + { + await TestAsync( +@"class C +{ + void M(int p, int p) + { + p = 1; + } +}"); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.Cascading.cs b/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.Cascading.cs deleted file mode 100644 index da4f0d3012f7c..0000000000000 --- a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.Cascading.cs +++ /dev/null @@ -1,340 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReorderParameters -{ - public partial class ReorderParametersTests - { - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToImplementedMethod() - { - var markup = @" -interface I -{ - void M(int x, string y); -} - -class C : I -{ - $$public void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -interface I -{ - void M(string y, int x); -} - -class C : I -{ - public void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToImplementingMethod() - { - var markup = @" -interface I -{ - $$void M(int x, string y); -} - -class C : I -{ - public void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -interface I -{ - void M(string y, int x); -} - -class C : I -{ - public void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToOverriddenMethod() - { - var markup = @" -class B -{ - public virtual void M(int x, string y) - { } -} - -class D : B -{ - $$public override void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class B -{ - public virtual void M(string y, int x) - { } -} - -class D : B -{ - public override void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToOverridingMethod() - { - var markup = @" -class B -{ - $$public virtual void M(int x, string y) - { } -} - -class D : B -{ - public override void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class B -{ - public virtual void M(string y, int x) - { } -} - -class D : B -{ - public override void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToOverriddenMethod_Transitive() - { - var markup = @" -class B -{ - public virtual void M(int x, string y) - { } -} - -class D : B -{ - public override void M(int x, string y) - { } -} - -class D2 : D -{ - $$public override void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class B -{ - public virtual void M(string y, int x) - { } -} - -class D : B -{ - public override void M(string y, int x) - { } -} - -class D2 : D -{ - public override void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToOverridingMethod_Transitive() - { - var markup = @" -class B -{ - $$public virtual void M(int x, string y) - { } -} - -class D : B -{ - public override void M(int x, string y) - { } -} - -class D2 : D -{ - public override void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class B -{ - public virtual void M(string y, int x) - { } -} - -class D : B -{ - public override void M(string y, int x) - { } -} - -class D2 : D -{ - public override void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToMethods_Complex() - { - //// B I I2 - //// \ / \ / - //// D (I3) - //// / \ \ - //// $$D2 D3 C - - var markup = @" -class B { public virtual void M(int x, string y) { } } -class D : B, I { public override void M(int x, string y) { } } -class D2 : D { public override void $$M(int x, string y) { } } -class D3 : D { public override void M(int x, string y) { } } -interface I { void M(int x, string y); } -interface I2 { void M(int x, string y); } -interface I3 : I, I2 { } -class C : I3 { public void M(int x, string y) { } }"; - - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class B { public virtual void M(string y, int x) { } } -class D : B, I { public override void M(string y, int x) { } } -class D2 : D { public override void M(string y, int x) { } } -class D3 : D { public override void M(string y, int x) { } } -interface I { void M(string y, int x); } -interface I2 { void M(string y, int x); } -interface I3 : I, I2 { } -class C : I3 { public void M(string y, int x) { } }"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParameters_Cascade_ToMethods_WithDifferentParameterNames() - { - var markup = @" -public class B -{ - /// - /// - public virtual int M(int x, string y) - { - return 1; - } -} - -public class D : B -{ - /// - /// - public override int M(int a, string b) - { - return 1; - } -} - -public class D2 : D -{ - /// - /// - public override int $$M(int y, string x) - { - M(1, ""Two""); - ((D)this).M(1, ""Two""); - ((B)this).M(1, ""Two""); - - M(1, x: ""Two""); - ((D)this).M(1, b: ""Two""); - ((B)this).M(1, y: ""Two""); - - return 1; - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -public class B -{ - /// - /// - public virtual int M(string y, int x) - { - return 1; - } -} - -public class D : B -{ - /// - /// - public override int M(string b, int a) - { - return 1; - } -} - -public class D2 : D -{ - /// - /// - public override int M(string x, int y) - { - M(""Two"", 1); - ((D)this).M(""Two"", 1); - ((B)this).M(""Two"", 1); - - M(x: ""Two"", y: 1); - ((D)this).M(b: ""Two"", a: 1); - ((B)this).M(y: ""Two"", x: 1); - - return 1; - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.InvocationErrors.cs b/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.InvocationErrors.cs deleted file mode 100644 index bbc6bc164ba32..0000000000000 --- a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.InvocationErrors.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReorderParameters -{ - public partial class ReorderParametersTests - { - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnClassName_ShouldFail() - { - var markup = @" -using System; -class MyClass$$ -{ - public void Goo(int x, string y) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.You_can_only_change_the_signature_of_a_constructor_indexer_method_or_delegate); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnField_ShouldFail() - { - var markup = @" -using System; -class MyClass -{ - int t$$ = 2; - - public void Goo(int x, string y) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.You_can_only_change_the_signature_of_a_constructor_indexer_method_or_delegate); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InsufficientParameters_None() - { - var markup = @"class C { void $$M() { } }"; - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.InsufficientParametersToReorder); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InsufficientParameters_OneRegular() - { - var markup = @"class C { void $$M(int x) { } }"; - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.InsufficientParametersToReorder); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InsufficientParameters_OneDefault() - { - var markup = @"class C { void $$M(int x = 7) { } }"; - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.InsufficientParametersToReorder); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InsufficientParameters_OneRegularOneDefault() - { - var markup = @"class C { void $$M(int x, int y = 7) { } }"; - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.InsufficientParametersToReorder); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InsufficientParameters_OneRegularOneDefaultOneParams() - { - var markup = @"class C { void $$M(int x, int y = 7, params int[] z) { } }"; - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.InsufficientParametersToReorder); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InsufficientParameters_OneThisOneRegularOneDefaultOneParams() - { - var markup = @" -static class C -{ - static void $$M(this object o, int x, int y = 7, params int[] z) - { - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.InsufficientParametersToReorder); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.InvocationLocation.cs b/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.InvocationLocation.cs deleted file mode 100644 index b5b335e5927de..0000000000000 --- a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.InvocationLocation.cs +++ /dev/null @@ -1,691 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReorderParameters -{ - public partial class ReorderParametersTests - { - #region Methods - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeBeforeMethodName() - { - var markup = @" -using System; -class MyClass -{ - public void $$Goo(int x, string y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeInParameterList() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, $$string y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeAfterParameterList() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y)$$ - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeBeforeMethodDeclaration() - { - var markup = @" -using System; -class MyClass -{ - $$public void Goo(int x, string y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnMetadataReference_InIdentifier_ShouldFail() - { - var markup = @" -class C -{ - static void Main(string[] args) - { - ((System.IFormattable)null).ToSt$$ring(""test"", null); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.The_member_is_defined_in_metadata); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnMetadataReference_AtBeginningOfInvocation_ShouldFail() - { - var markup = @" -class C -{ - static void Main(string[] args) - { - $$((System.IFormattable)null).ToString(""test"", null); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.The_member_is_defined_in_metadata); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnMetadataReference_InArgumentsOfInvocation_ShouldFail() - { - var markup = @" -class C -{ - static void Main(string[] args) - { - ((System.IFormattable)null).ToString(""test"",$$ null); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.The_member_is_defined_in_metadata); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnMetadataReference_AfterInvocation_ShouldFail() - { - var markup = @" -class C -{ - string s = ((System.IFormattable)null).ToString(""test"", null)$$; -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, expectedSuccess: false, expectedErrorText: FeaturesResources.You_can_only_change_the_signature_of_a_constructor_indexer_method_or_delegate); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeInMethodBody() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - $$ - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_BeginningOfIdentifier() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - $$Bar(x, y); - } - - public void Bar(int x, string y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar(y, x); - } - - public void Bar(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_ArgumentList() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - $$Bar(x, y); - } - - public void Bar(int x, string y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar(y, x); - } - - public void Bar(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_NestedCalls1() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar($$Baz(x, y), y); - } - - public void Bar(int x, string y) - { - } - - public int Baz(int x, string y) - { - return 1; - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar(Baz(y, x), y); - } - - public void Bar(int x, string y) - { - } - - public int Baz(string y, int x) - { - return 1; - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_NestedCalls2() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar$$(Baz(x, y), y); - } - - public void Bar(int x, string y) - { - } - - public int Baz(int x, string y) - { - return 1; - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar(y, Baz(x, y)); - } - - public void Bar(string y, int x) - { - } - - public int Baz(int x, string y) - { - return 1; - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_NestedCalls3() - { - var markup = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar(Baz(x, y), $$y); - } - - public void Bar(int x, string y) - { - } - - public int Baz(int x, string y) - { - return 1; - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(int x, string y) - { - Bar(y, Baz(x, y)); - } - - public void Bar(string y, int x) - { - } - - public int Baz(int x, string y) - { - return 1; - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_Attribute() - { - var markup = @" -using System; - -[$$My(1, 2)] -class MyAttribute : Attribute -{ - public MyAttribute(int x, int y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; - -[My(2, 1)] -class MyAttribute : Attribute -{ - public MyAttribute(int y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_OnlyHasCandidateSymbols() - { - var markup = @" -class Test -{ - void M(int x, string y) { } - void M(int x, double y) { } - void M2() { $$M(""s"", 1); } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Test -{ - void M(string y, int x) { } - void M(int x, double y) { } - void M2() { M(1, ""s""); } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_CallToOtherConstructor() - { - var markup = @" -class Program -{ - public Program(int x, int y) : this(1, 2, 3)$$ - { - } - - public Program(int x, int y, int z) - { - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -class Program -{ - public Program(int x, int y) : this(3, 2, 1) - { - } - - public Program(int z, int y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters_InvokeOnReference_CallToBaseConstructor() - { - var markup = @" -class B -{ - public B(int a, int b) - { - } -} - -class D : B -{ - public D(int x, int y) : base(1, 2)$$ - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class B -{ - public B(int b, int a) - { - } -} - -class D : B -{ - public D(int x, int y) : base(2, 1) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - #endregion - - #region Indexers - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParameters_InvokeAtBeginningOfDeclaration() - { - var markup = @" -class Program -{ - $$int this[int x, string y] - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - int this[string y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParameters_InParameters() - { - var markup = @" -class Program -{ - int this[int x, $$string y] - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - int this[string y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParameters_InvokeAtEndOfDeclaration() - { - var markup = @" -class Program -{ - int this[int x, string y]$$ - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - int this[string y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParameters_InvokeInAccessor() - { - var markup = @" -class Program -{ - int this[int x, string y] - { - get { return $$5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - int this[string y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParameters_InvokeOnReference_BeforeTarget() - { - var markup = @" -class Program -{ - void M(Program p) - { - var t = $$p[5, ""test""]; - } - - int this[int x, string y] - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - void M(Program p) - { - var t = p[""test"", 5]; - } - - int this[string y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParameters_InvokeOnReference_InArgumentList() - { - var markup = @" -class Program -{ - void M(Program p) - { - var t = p[5, ""test""$$]; - } - - int this[int x, string y] - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - void M(Program p) - { - var t = p[""test"", 5]; - } - - int this[string y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - #endregion - } -} diff --git a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.cs b/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.cs deleted file mode 100644 index 951ccab267283..0000000000000 --- a/src/EditorFeatures/CSharpTest/ReorderParameters/ReorderParametersTests.cs +++ /dev/null @@ -1,887 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Editor.UnitTests.ReorderParameters; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReorderParameters -{ - public partial class ReorderParametersTests : AbstractReorderParametersTests - { - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParameters() - { - var markup = @" -using System; -class MyClass -{ - public void $$Goo(int x, string y) - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParametersAndArguments() - { - var markup = @" -using System; -class MyClass -{ - public void $$Goo(int x, string y) - { - Goo(3, ""hello""); - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public void Goo(string y, int x) - { - Goo(""hello"", 3); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderMethodParametersAndArgumentsOfNestedCalls() - { - var markup = @" -using System; -class MyClass -{ - public int $$Goo(int x, string y) - { - return Goo(Goo(4, ""inner""), ""outer""); - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -class MyClass -{ - public int Goo(string y, int x) - { - return Goo(""outer"", Goo(""inner"", 4)); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderConstructorParametersAndArguments() - { - var markup = @" -using System; - -class MyClass2 : MyClass -{ - public MyClass2() : base(5, ""test2"") - { - } -} - -class MyClass -{ - public MyClass() : this(2, ""test"") - { - } - - public $$MyClass(int x, string y) - { - var t = new MyClass(x, y); - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; - -class MyClass2 : MyClass -{ - public MyClass2() : base(""test2"", 5) - { - } -} - -class MyClass -{ - public MyClass() : this(""test"", 2) - { - } - - public MyClass(string y, int x) - { - var t = new MyClass(y, x); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderAttributeConstructorParametersAndArguments() - { - var markup = @" -[My(""test"", 8)] -class MyClass -{ -} - -class MyAttribute : System.Attribute -{ - public MyAttribute(string x, int y)$$ - { - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -[My(8, ""test"")] -class MyClass -{ -} - -class MyAttribute : System.Attribute -{ - public MyAttribute(int y, string x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderExtensionMethodParametersAndArguments_StaticCall() - { - var markup = @" -public class C -{ - static void Main(string[] args) - { - CExt.M(new C(), 1, 2, ""three"", ""four"", ""five""); - } -} - -public static class CExt -{ - public static void $$M(this C goo, int x, int y, string a = ""test_a"", string b = ""test_b"", string c = ""test_c"") - { } -}"; - var permutation = new[] { 0, 2, 1, 5, 4, 3 }; - var updatedCode = @" -public class C -{ - static void Main(string[] args) - { - CExt.M(new C(), 2, 1, ""five"", ""four"", ""three""); - } -} - -public static class CExt -{ - public static void M(this C goo, int y, int x, string c = ""test_c"", string b = ""test_b"", string a = ""test_a"") - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderExtensionMethodParametersAndArguments_ExtensionCall() - { - var markup = @" -public class C -{ - static void Main(string[] args) - { - new C().M(1, 2, ""three"", ""four"", ""five""); - } -} - -public static class CExt -{ - public static void $$M(this C goo, int x, int y, string a = ""test_a"", string b = ""test_b"", string c = ""test_c"") - { } -}"; - var permutation = new[] { 0, 2, 1, 5, 4, 3 }; - var updatedCode = @" -public class C -{ - static void Main(string[] args) - { - new C().M(2, 1, ""five"", ""four"", ""three""); - } -} - -public static class CExt -{ - public static void M(this C goo, int y, int x, string c = ""test_c"", string b = ""test_b"", string a = ""test_a"") - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamsMethodParametersAndArguments_ParamsAsArray() - { - var markup = @" -public class C -{ - void $$M(int x, int y, params int[] p) - { - M(x, y, new[] { 1, 2, 3 }); - } -}"; - var permutation = new[] { 1, 0, 2 }; - var updatedCode = @" -public class C -{ - void M(int y, int x, params int[] p) - { - M(y, x, new[] { 1, 2, 3 }); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamsMethodParametersAndArguments_ParamsExpanded() - { - var markup = @" -public class C -{ - void $$M(int x, int y, params int[] p) - { - M(x, y, 1, 2, 3); - } -}"; - var permutation = new[] { 1, 0, 2 }; - var updatedCode = @" -public class C -{ - void M(int y, int x, params int[] p) - { - M(y, x, 1, 2, 3); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderExtensionAndParamsMethodParametersAndArguments_VariedCallsites() - { - var markup = @" -public class C -{ - static void Main(string[] args) - { - CExt.M(new C(), 1, 2, ""three"", ""four"", ""five"", new[] { 6, 7, 8 }); - CExt.M(new C(), 1, 2, ""three"", ""four"", ""five"", 6, 7, 8 ); - new C().M(1, 2, ""three"", ""four"", ""five"", new[] { 6, 7, 8 }); - new C().M(1, 2, ""three"", ""four"", ""five"", 6, 7, 8); - } -} - -public static class CExt -{ - public static void $$M(this C goo, int x, int y, string a = ""test_a"", string b = ""test_b"", string c = ""test_c"", params int[] p) - { } -}"; - var permutation = new[] { 0, 2, 1, 5, 4, 3, 6 }; - var updatedCode = @" -public class C -{ - static void Main(string[] args) - { - CExt.M(new C(), 2, 1, ""five"", ""four"", ""three"", new[] { 6, 7, 8 }); - CExt.M(new C(), 2, 1, ""five"", ""four"", ""three"", 6, 7, 8 ); - new C().M(2, 1, ""five"", ""four"", ""three"", new[] { 6, 7, 8 }); - new C().M(2, 1, ""five"", ""four"", ""three"", 6, 7, 8); - } -} - -public static class CExt -{ - public static void M(this C goo, int y, int x, string c = ""test_c"", string b = ""test_b"", string a = ""test_a"", params int[] p) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderIndexerParametersAndArguments() - { - var markup = @" -class Program -{ - void M() - { - var x = new Program()[1, 2]; - new Program()[1, 2] = x; - } - - public int this[int x, int y]$$ - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class Program -{ - void M() - { - var x = new Program()[2, 1]; - new Program()[2, 1] = x; - } - - public int this[int y, int x] - { - get { return 5; } - set { } - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact(Skip = "Not Yet Implemented"), Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderCollectionInitializerAddMethodParametersAndArguments() - { - var markup = @" -using System; -using System.Collections; - -class Program : IEnumerable -{ - static void Main(string[] args) - { - new Program { { 1, 2 }, { ""three"", ""four"" }, { 5, 6 } }; - } - - public void Add(int x, int y)$$ - { - } - - public void Add(string x, string y) - { - } - - public IEnumerator GetEnumerator() - { - throw new NotImplementedException(); - } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -using System; -using System.Collections; - -class Program : IEnumerable -{ - static void Main(string[] args) - { - new Program { { 2, 1 }, { ""three"", ""four"" }, { 6, 5 } }; - } - - public void Add(int y, int x) - { - } - - public void Add(string x, string y) - { - } - - public IEnumerator GetEnumerator() - { - throw new NotImplementedException(); - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_SingleLineDocComments_OnIndividualLines() - { - var markup = @" -public class C -{ - /// - /// - /// - void $$Goo(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - /// - void Goo(int c, int b, int a) - { - - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_SingleLineDocComments_OnSameLine() - { - var markup = @" -public class C -{ - /// a is funb is func is fun - void $$Goo(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// c is funb is funa is fun - void Goo(int c, int b, int a) - { - - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_SingleLineDocComments_MixedLineDistribution() - { - var markup = @" -public class C -{ - /// - /// - /// - /// Comments spread - /// over several - /// lines - void $$Goo(int a, int b, int c, int d, int e, int f) - { - - } -}"; - var permutation = new[] { 5, 4, 3, 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// Comments spread - /// over several - /// lines - /// - /// - /// - void Goo(int f, int e, int d, int c, int b, int a) - { - - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_SingleLineDocComments_MixedWithRegularComments() - { - var markup = @" -public class C -{ - /// - // Why is there a regular comment here? - /// - void $$Goo(int a, int b, int c, int d, int e) - { - - } -}"; - var permutation = new[] { 4, 3, 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - // Why is there a regular comment here? - /// - void Goo(int e, int d, int c, int b, int a) - { - - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_MultiLineDocComments_OnSeparateLines1() - { - var markup = @" -class Program -{ - /** - * x! - * y! - * z! - */ - static void $$M(int x, int y, int z) - { - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -class Program -{ - /** - * z! - * y! - * x! - */ - static void M(int z, int y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_MultiLineDocComments_OnSingleLine() - { - var markup = @" -class Program -{ - /** x!y!z! */ - static void $$M(int x, int y, int z) - { - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -class Program -{ - /** z!y!x! */ - static void M(int z, int y, int x) - { - } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_IncorrectOrder_MaintainsOrder() - { - var markup = @" -public class C -{ - /// - /// - /// - void $$Goo(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - /// - void Goo(int c, int b, int a) - { - - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_WrongNames_MaintainsOrder() - { - var markup = @" -public class C -{ - /// - /// - /// - void $$Goo(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - /// - void Goo(int c, int b, int a) - { - - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_InsufficientTags_MaintainsOrder() - { - var markup = @" -public class C -{ - /// - /// - void $$Goo(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - void Goo(int c, int b, int a) - { - - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_ExcessiveTags_MaintainsOrder() - { - var markup = @" -public class C -{ - /// - /// - /// - /// - void $$Goo(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - /// - /// - void Goo(int c, int b, int a) - { - - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_OnConstructors() - { - var markup = @" -public class C -{ - /// - /// - /// - public $$C(int a, int b, int c) - { - - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - /// - public C(int c, int b, int a) - { - - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParamTagsInDocComments_OnIndexers() - { - var markup = @" -public class C -{ - /// - /// - /// - public int $$this[int a, int b, int c] - { - get { return 5; } - set { } - } -}"; - var permutation = new[] { 2, 1, 0 }; - var updatedCode = @" -public class C -{ - /// - /// - /// - public int this[int c, int b, int a] - { - get { return 5; } - set { } - } -}"; - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParametersInCrefs() - { - var markup = @" -class C -{ - /// - /// See and - /// - $$void M(int x, string y) - { } -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -class C -{ - /// - /// See and - /// - void M(string y, int x) - { } -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParametersInMethodThatImplementsInterfaceMethodOnlyThroughADerivedType1() - { - var markup = @" -interface I -{ - $$void M(int x, string y); -} - -class C -{ - public void M(int x, string y) - { - } -} - -class D : C, I -{ -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -interface I -{ - void M(string y, int x); -} - -class C -{ - public void M(string y, int x) - { - } -} - -class D : C, I -{ -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - - [Fact, Trait(Traits.Feature, Traits.Features.ReorderParameters)] - public void ReorderParametersInMethodThatImplementsInterfaceMethodOnlyThroughADerivedType2() - { - var markup = @" -interface I -{ - void M(int x, string y); -} - -class C -{ - $$public void M(int x, string y) - { - } -} - -class D : C, I -{ -}"; - var permutation = new[] { 1, 0 }; - var updatedCode = @" -interface I -{ - void M(string y, int x); -} - -class C -{ - public void M(string y, int x) - { - } -} - -class D : C, I -{ -}"; - - TestReorderParameters(LanguageNames.CSharp, markup, permutation: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/Structure/BlockSyntaxStructureTests.cs b/src/EditorFeatures/CSharpTest/Structure/BlockSyntaxStructureTests.cs index d85aa19c7815d..587b9dbf3c3f3 100644 --- a/src/EditorFeatures/CSharpTest/Structure/BlockSyntaxStructureTests.cs +++ b/src/EditorFeatures/CSharpTest/Structure/BlockSyntaxStructureTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Structure; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Structure @@ -412,5 +413,23 @@ void M() await VerifyBlockSpansAsync(code, Region("textspan", "hint", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); } + + [WorkItem(52493, "https://github.com/dotnet/roslyn/issues/")] + [Fact, Trait(Traits.Feature, Traits.Features.Outlining)] + public async Task LocalFunctionInTopLevelStatement_AutoCollapse() + { + const string code = @" +Foo(); +Bar(); + +{|hint:static void Foo(){|textspan: +{$$ + // ... +}|}|} +"; + + await VerifyBlockSpansAsync(code, + Region("textspan", "hint", CSharpStructureHelpers.Ellipsis, autoCollapse: true)); + } } } diff --git a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs index c5d7ae8e19b59..5e29cd7b93a29 100644 --- a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs +++ b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs @@ -74,6 +74,23 @@ public async Task RecordWithCommentsAndAttributes() // This is a doc comment. [Bar, Baz] |}{|#0:public record $$C|}{|textspan2: +{ + void M(); +}|}|#0}"; + + await VerifyBlockSpansAsync(code, + Region("textspan", "hint", CSharpStructureHelpers.Ellipsis, autoCollapse: true), + Region("textspan2", "#0", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.MetadataAsSource)] + public async Task RecordStructWithCommentsAndAttributes() + { + const string code = @" +{|hint:{|textspan:// Summary: +// This is a doc comment. +[Bar, Baz] +|}{|#0:public record struct $$C|}{|textspan2: { void M(); }|}|#0}"; diff --git a/src/EditorFeatures/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs index 48faaa1d9fc16..e6ca56d452608 100644 --- a/src/EditorFeatures/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -252,5 +253,130 @@ void M0(IQueryable q) } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUsePatternCombinators)] + [WorkItem(52397, "https://github.com/dotnet/roslyn/issues/52397")] + public async Task TestMissingInPropertyAccess_NullCheckOnLeftSide() + { + await TestMissingAsync( +@"using System; + +public class C +{ + public int I { get; } + + public EventArgs Property { get; } + + public void M() + { + if (Property != null [|&&|] I == 1) + { + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUsePatternCombinators)] + [WorkItem(52397, "https://github.com/dotnet/roslyn/issues/52397")] + public async Task TestMissingInPropertyAccess_NullCheckOnRightSide() + { + await TestMissingAsync( +@"using System; + +public class C +{ + public int I { get; } + + public EventArgs Property { get; } + + public void M() + { + if (I == 1 [|&&|] Property != null) + { + } + } +}"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUsePatternCombinators)] + [WorkItem(51691, "https://github.com/dotnet/roslyn/issues/51691")] + [InlineData("&&")] + [InlineData("||")] + public async Task TestMissingInPropertyAccess_EnumCheckAndNullCheck(string logicalOperator) + { + await TestMissingAsync( +$@"using System.Diagnostics; + +public class C +{{ + public void M() + {{ + var p = default(Process); + if (p.StartInfo.WindowStyle == ProcessWindowStyle.Hidden [|{logicalOperator}|] p.StartInfo != null) + {{ + }} + }} +}}"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUsePatternCombinators)] + [WorkItem(51691, "https://github.com/dotnet/roslyn/issues/51691")] + [InlineData("&&")] + [InlineData("||")] + public async Task TestMissingInPropertyAccess_EnumCheckAndNullCheckOnOtherType(string logicalOperator) + { + await TestMissingAsync( +$@"using System.Diagnostics; + +public class C +{{ + public void M() + {{ + var p = default(Process); + if (p.StartInfo.WindowStyle == ProcessWindowStyle.Hidden [|{logicalOperator}|] this != null) + {{ + }} + }} +}}"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUsePatternCombinators)] + [WorkItem(51693, "https://github.com/dotnet/roslyn/issues/51693")] + [InlineData("&&")] + [InlineData("||")] + public async Task TestMissingInPropertyAccess_IsCheckAndNullCheck(string logicalOperator) + { + await TestMissingAsync( +$@"using System; + +public class C +{{ + public void M() + {{ + var o1 = new object(); + if (o1 is IAsyncResult ar [|{logicalOperator}|] ar.AsyncWaitHandle != null) + {{ + }} + }} +}}"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUsePatternCombinators)] + [WorkItem(52573, "https://github.com/dotnet/roslyn/issues/52573")] + [InlineData("&&")] + [InlineData("||")] + public async Task TestMissingIntegerAndStringIndex(string logicalOperator) + { + await TestMissingAsync( +$@"using System; + +public class C +{{ + private static bool IsS(char[] ch, int count) + {{ + return count == 1 [|{logicalOperator}|] ch[0] == 'S'; + }} +}}"); + } } } diff --git a/src/EditorFeatures/CSharpTest/Wrapping/ArgumentWrappingTests.cs b/src/EditorFeatures/CSharpTest/Wrapping/ArgumentWrappingTests.cs index 8d5ac249962e2..cedbf0b7cae38 100644 --- a/src/EditorFeatures/CSharpTest/Wrapping/ArgumentWrappingTests.cs +++ b/src/EditorFeatures/CSharpTest/Wrapping/ArgumentWrappingTests.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Wrapping; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Wrapping @@ -839,6 +840,34 @@ void Goo() { }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsWrapping)] + [WorkItem(50104, "https://github.com/dotnet/roslyn/issues/50104")] + public async Task TestInImplicitObjectCreation() + { + await TestInRegularAndScript1Async( +@"class Program +{ + static void Main(string[] args) + { + Program p1 = new([||]1, 2); + } + + public Program(object o1, object o2) { } +} +", +@"class Program +{ + static void Main(string[] args) + { + Program p1 = new(1, + 2); + } + + public Program(object o1, object o2) { } +} +"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsWrapping)] public async Task TestInConstructorInitializer1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs index 917d656230b06..1687084899b56 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AddKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AddKeywordRecommenderTests.cs index 01cd758ef1d20..1621af6d440b7 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AddKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AddKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AliasKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AliasKeywordRecommenderTests.cs index 8f7504a1f36dc..1aee5d634765e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AliasKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AliasKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AnnotationsKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AnnotationsKeywordRecommenderTests.cs index 3ab44e2d658ef..9085668ac3885 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AnnotationsKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AnnotationsKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AsKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AsKeywordRecommenderTests.cs index 2f1559243a345..1c9fb6bf8ea01 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AsKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AsKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AscendingKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AscendingKeywordRecommenderTests.cs index add9672176d2e..9050ef85be8fb 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AscendingKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AscendingKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AssemblyKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AssemblyKeywordRecommenderTests.cs index ddaaab7f0ed3a..b9608e1db571f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AssemblyKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AssemblyKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -161,6 +168,17 @@ await VerifyAbsenceAsync( @" using System; +$$ +namespace Goo {}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInAttributeBeforeNamespaceAndAfterGlobalUsingWithNoOpenBracket() + { + await VerifyAbsenceAsync( +@" +global using System; + $$ namespace Goo {}"); } @@ -173,6 +191,17 @@ await VerifyKeywordAsync( @" using System; +[$$ +namespace Goo {}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInAttributeBeforeNamespaceAndAfterGlobalUsingWithOpenBracket() + { + await VerifyKeywordAsync( +@" +global using System; + [$$ namespace Goo {}"); } @@ -247,6 +276,18 @@ await VerifyAbsenceAsync( @" using System; +$$ +[assembly: Whatever] +namespace Goo {}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInAttributeBeforeAssemblyAttributeAndAfterGlobalUsingWithoutOpenBracket() + { + await VerifyAbsenceAsync( +@" +global using System; + $$ [assembly: Whatever] namespace Goo {}"); @@ -260,6 +301,18 @@ await VerifyKeywordAsync( @" using System; +[$$ +[assembly: Whatever] +namespace Goo {}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInBeforeAttributeAssemblyAttributeAndAfterGlobalUsingWithoutOpenBracket() + { + await VerifyKeywordAsync( +@" +global using System; + [$$ [assembly: Whatever] namespace Goo {}"); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs index 2a840d609e63b..5a32ffd5205b4 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs @@ -51,6 +51,10 @@ await VerifyAbsenceAsync(AddInsideMethod( public async Task TestUsingDirective() => await VerifyAbsenceAsync("using $$"); + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestGlobalUsingDirective() + => await VerifyAbsenceAsync("global using $$"); + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestForeachStatement(bool topLevelStatement) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/BaseKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/BaseKeywordRecommenderTests.cs index 168ea6cd3f7ad..7380393c89525 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/BaseKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/BaseKeywordRecommenderTests.cs @@ -62,6 +62,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInClassConstructorInitializer() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs index d224ff8f0ca68..67cccdfe229a4 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/BreakKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/BreakKeywordRecommenderTests.cs index b7de78b1525bd..c2eee68839f53 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/BreakKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/BreakKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestEmptyStatement(bool topLevelStatement) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ByKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ByKeywordRecommenderTests.cs index 25a865f10c077..faf23641a7cb7 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ByKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ByKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestNotInEmptyStatement(bool topLevelStatement) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ByteKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ByteKeywordRecommenderTests.cs index ba9d0e45a85f9..6b7d46bc2e69f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ByteKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ByteKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/CaseKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/CaseKeywordRecommenderTests.cs index 11f11f029ec0b..059645f95c643 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/CaseKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/CaseKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/CatchKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/CatchKeywordRecommenderTests.cs index 57ae7c277a8f1..e50475dcaa3ad 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/CatchKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/CatchKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/CharKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/CharKeywordRecommenderTests.cs index 3cceec0f5b6dd..338137a0ceb48 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/CharKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/CharKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/CheckedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/CheckedKeywordRecommenderTests.cs index 34275293ffbb5..036776be784fc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/CheckedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/CheckedKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ChecksumKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ChecksumKeywordRecommenderTests.cs index 6f5bbfc3f1020..e7cd0c3588f3a 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ChecksumKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ChecksumKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs index c48a74966656e..c476df6205119 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -203,20 +234,6 @@ await VerifyKeywordAsync( @"partial $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterData() - { - await VerifyKeywordAsync( -@"data $$"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterPublicData() - { - await VerifyKeywordAsync( -@"public data $$"); - } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAbstract() { @@ -291,17 +308,48 @@ await VerifyAbsenceAsync( @"using static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterStaticInGlobalUsingDirective() + { + await VerifyAbsenceAsync( +@"global using static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass() => await VerifyAbsenceAsync(@"class $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_01() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_02() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] @@ -346,5 +394,12 @@ await VerifyKeywordAsync( @"class C { new $$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterRecord() + { + await VerifyKeywordAsync( +@"record $$"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ConstKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ConstKeywordRecommenderTests.cs index be6cf8392eaf6..df87e62193b3a 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ConstKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ConstKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestInEmptyStatement(bool topLevelStatement) @@ -72,6 +79,13 @@ public async Task TestAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -123,6 +137,16 @@ await VerifyAbsenceAsync(sourceCodeKind, using Goo;"); } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [InlineData(SourceCodeKind.Regular)] + [InlineData(SourceCodeKind.Script, Skip = "https://github.com/dotnet/roslyn/issues/9880")] + public async Task TestNotBeforeGlobalUsing(SourceCodeKind sourceCodeKind) + { + await VerifyAbsenceAsync(sourceCodeKind, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ContinueKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ContinueKeywordRecommenderTests.cs index ebca6c9bde14a..8bdeb7c06ed78 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ContinueKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ContinueKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DecimalKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DecimalKeywordRecommenderTests.cs index 13b91ed1017a2..b83e881fec04d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DecimalKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DecimalKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DefaultKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DefaultKeywordRecommenderTests.cs index a85236f35ba3b..aff0531dd2be1 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DefaultKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DefaultKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInPreprocessor1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DefineKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DefineKeywordRecommenderTests.cs index 1d1f7bf66769c..736695b25d053 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DefineKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DefineKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -88,11 +95,27 @@ await VerifyKeywordAsync( using System;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeGlobalUsing() + { + await VerifyKeywordAsync( +@"#$$ +global using System;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterUsing() { await VerifyAbsenceAsync( @"using System; +#$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync( +@"global using System; #$$"); } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DelegateKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DelegateKeywordRecommenderTests.cs index 1073277a3146e..4fa5865ab645c 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DelegateKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DelegateKeywordRecommenderTests.cs @@ -53,6 +53,15 @@ await VerifyAbsenceAsync( @"using Goo = d$$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + await VerifyAbsenceAsync( +@"global using Goo = d$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInUsingAliasTypeParameter() { @@ -60,6 +69,13 @@ await VerifyKeywordAsync( @"using Goo = T<$$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInGlobalUsingAliasTypeParameter() + { + await VerifyKeywordAsync( +@"global using Goo = T<$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInEmptyStatement() { @@ -90,6 +106,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -157,6 +181,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DescendingKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DescendingKeywordRecommenderTests.cs index c5f3e4e019cb3..1fbd9db87cd5d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DescendingKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DescendingKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DisableKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DisableKeywordRecommenderTests.cs index 8cab823c89e46..41fff7875b021 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DisableKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DisableKeywordRecommenderTests.cs @@ -66,6 +66,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DoKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DoKeywordRecommenderTests.cs index 64c537dfbc9ea..93ae503bf8d68 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DoKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DoKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DoubleKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DoubleKeywordRecommenderTests.cs index 00e7ac34b0e9e..1a5b23df05929 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DoubleKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DoubleKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/DynamicKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/DynamicKeywordRecommenderTests.cs index 0d161b6436164..d3715fce15d9b 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/DynamicKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/DynamicKeywordRecommenderTests.cs @@ -61,6 +61,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ElifKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ElifKeywordRecommenderTests.cs index 97727ee10c2d9..4f1261f878be5 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ElifKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ElifKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ElseKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ElseKeywordRecommenderTests.cs index 7f6255db5e253..c54fdab086b96 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ElseKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ElseKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInPreprocessor1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/EnableKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/EnableKeywordRecommenderTests.cs index 888cd76b6188e..3a1196700f2e2 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/EnableKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/EnableKeywordRecommenderTests.cs @@ -66,6 +66,10 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, public async Task TestNotInUsingAlias() => await VerifyAbsenceAsync(@"using Goo = $$"); + [Fact] + public async Task TestNotInGlobalUsingAlias() + => await VerifyAbsenceAsync(@"global using Goo = $$"); + [Fact] public async Task TestNotInEmptyStatement() => await VerifyAbsenceAsync(AddInsideMethod(@"$$")); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/EndIfKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/EndIfKeywordRecommenderTests.cs index 09659ef0a73ff..dcfb2f379ca4e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/EndIfKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/EndIfKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/EndRegionKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/EndRegionKeywordRecommenderTests.cs index 3bece199e0aea..1673d9910801d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/EndRegionKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/EndRegionKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/EnumKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/EnumKeywordRecommenderTests.cs index 9418e5b21af96..708de0a756d86 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/EnumKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/EnumKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/EqualsKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/EqualsKeywordRecommenderTests.cs index d5ebf69baa835..1b7d48bf6bf58 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/EqualsKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/EqualsKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ErrorKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ErrorKeywordRecommenderTests.cs index 35390e3fddcde..ff653502a0d86 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ErrorKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ErrorKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/EventKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/EventKeywordRecommenderTests.cs index f398fb6ccdea0..cc2a1a467f35e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/EventKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/EventKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -91,6 +98,20 @@ public async Task TestAfterUsing_Interactive() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"global using Goo; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, @"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -155,6 +176,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs index c1a48ff1c9942..b065e3d8da967 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ExplicitKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -76,6 +83,13 @@ public async Task TestNotAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -140,6 +154,14 @@ await VerifyAbsenceAsync( using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync( +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { @@ -266,10 +288,28 @@ public async Task TestNotAfterDelegate() [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyAbsenceAsync( @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBetweenGlobalUsings_01() + { + await VerifyAbsenceAsync( +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBetweenGlobalUsings_02() + { + await VerifyAbsenceAsync( +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ExternKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ExternKeywordRecommenderTests.cs index a00c6e8708d05..d0da17019500b 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ExternKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ExternKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestInEmptyStatement(bool topLevelStatement) @@ -172,6 +179,14 @@ public async Task TestAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FalseKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FalseKeywordRecommenderTests.cs index f1ebbe8153301..8a9a6050aaeb3 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FalseKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FalseKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInPreprocessor1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs index 25271dd32928b..ee8ac27f2c137 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -66,13 +73,16 @@ await VerifyKeywordAsync( [$$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestInAttributeInsideRecord() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestInAttributeInsideRecord(string record) { // The recommender doesn't work in record in script // Tracked by https://github.com/dotnet/roslyn/issues/44865 await VerifyWorkerAsync( -@"record C { +$@"{record} C {{ [$$", absent: false, TestOptions.RegularPreview); } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FinallyKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FinallyKeywordRecommenderTests.cs index 891728b286067..e49ac6ae19d5d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FinallyKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FinallyKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs index b3e32c3a2955a..ca6aee72ec313 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInsideEmptyMethod() { @@ -97,6 +104,14 @@ public async Task TestNotInStruct() { await VerifyAbsenceAsync( @"struct S { + $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInRecordStruct() + { + await VerifyAbsenceAsync( +@"record struct S { $$"); } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FloatKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FloatKeywordRecommenderTests.cs index 9f4fd5e8321fc..b5d28f25ecb83 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FloatKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FloatKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ForEachKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ForEachKeywordRecommenderTests.cs index a401d50036dcf..2399461306698 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ForEachKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ForEachKeywordRecommenderTests.cs @@ -73,6 +73,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestEmptyStatement(bool topLevelStatement) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ForKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ForKeywordRecommenderTests.cs index 769ab9255f1b7..5ce75e67ae79a 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ForKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ForKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FromKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FromKeywordRecommenderTests.cs index 25301c50b6ac7..f5475d6023be6 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FromKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FromKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/GetKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/GetKeywordRecommenderTests.cs index 83d1f04759651..d90e2d7593a16 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/GetKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/GetKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs index 5f0b8fed1b02c..69347bcccb1a5 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs @@ -170,5 +170,187 @@ class C { delegate*$$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInCompilationUnit() + { + await VerifyKeywordAsync(@" +$$ +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterExtern() + { + await VerifyKeywordAsync( +@"extern alias goo; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterPreviousUsing() + { + await VerifyKeywordAsync( +@"using Goo; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterPreviousGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeUsing() + { + await VerifyKeywordAsync( +@"$$ +using Goo;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeGlobalUsing() + { + await VerifyKeywordAsync( +@"$$ +global using Goo;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterUsingAlias() + { + await VerifyKeywordAsync( +@"using Goo = Bar; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsingAlias() + { + await VerifyKeywordAsync( +@"using Goo = Bar; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalKeyword() + { + await VerifyAbsenceAsync(@" +global $$ +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterUsingKeyword() + { + await VerifyAbsenceAsync(@" +using $$ +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsingKeyword() + { + await VerifyAbsenceAsync(@" +global using $$ +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeExtern() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +extern alias Goo;"); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeExtern_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +extern alias Goo;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBetweenGlobalUsings_01() + { + await VerifyKeywordAsync( +@"global using Goo; +$$ +global using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBetweenUsings_02() + { + await VerifyKeywordAsync( +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalBetweenGlobalUsings_01() + { + await VerifyAbsenceAsync( +@"global using Goo; +global $$ +global using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalBetweenUsings_02() + { + await VerifyAbsenceAsync( +@"global using Goo; +global $$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeNamespace() + { + await VerifyKeywordAsync( +@"$$ +namespace NS +{}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeClass() + { + await VerifyKeywordAsync( +@"$$ +class C1 +{}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeStatement() + { + await VerifyKeywordAsync( +@"$$ +Call();"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeAttribute_01() + { + await VerifyKeywordAsync( +@"$$ +[Call()]"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeAttribute_02() + { + await VerifyKeywordAsync( +@"$$ +[assembly: Call()]"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/GotoKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/GotoKeywordRecommenderTests.cs index 49da6c9b4a59f..009f6edfb14aa 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/GotoKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/GotoKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/GroupKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/GroupKeywordRecommenderTests.cs index baa768afcb206..e42319ccdc69c 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/GroupKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/GroupKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/HiddenKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/HiddenKeywordRecommenderTests.cs index 005a9b3160a56..0568cf11cd1fa 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/HiddenKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/HiddenKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/IfKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/IfKeywordRecommenderTests.cs index dcc1c35b410f2..859f0536539cc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/IfKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/IfKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInPreprocessor1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs index df359eb482b96..dea0b522ee7f3 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ImplicitKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -76,6 +83,13 @@ public async Task TestNotAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -140,6 +154,14 @@ await VerifyAbsenceAsync( using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync( +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { @@ -266,10 +288,28 @@ public async Task TestNotAfterDelegate() [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyAbsenceAsync( @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBetweenGlobalUsings_01() + { + await VerifyAbsenceAsync( +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBetweenGlobalUsings_02() + { + await VerifyAbsenceAsync( +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs index 19d07acf4d6cd..0a1c49120031e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/InitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/InitKeywordRecommenderTests.cs index f5ea7fcbc0e3f..95eb47147954c 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/InitKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/InitKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs index 7e627a2df922d..1085ae1c12661 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs @@ -66,6 +66,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInPreprocessor1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/InterfaceKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/InterfaceKeywordRecommenderTests.cs index 1b2e5ad5d2886..3576789a7d5d8 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/InterfaceKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/InterfaceKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/InternalKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/InternalKeywordRecommenderTests.cs index ae22741ab76b1..5ad8dacf660fe 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/InternalKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/InternalKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -259,6 +290,13 @@ await VerifyAbsenceAsync( @"using static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterStaticInGlobalUsingDirective() + { + await VerifyAbsenceAsync( +@"global using static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass() => await VerifyAbsenceAsync(@"class $$"); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/IntoKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/IntoKeywordRecommenderTests.cs index ee7ab1a26c1ea..39b08b39845be 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/IntoKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/IntoKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/IsKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/IsKeywordRecommenderTests.cs index 614d87c2a22ef..6a4e695bdc19c 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/IsKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/IsKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/JoinKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/JoinKeywordRecommenderTests.cs index a8e29714bd3d0..de5fd5bcffb16 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/JoinKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/JoinKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/LetKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/LetKeywordRecommenderTests.cs index 36f6eb484ca6d..71dd678f150e7 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/LetKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/LetKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/LineKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/LineKeywordRecommenderTests.cs index 8bd8ee695aab7..3c151383f55fe 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/LineKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/LineKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/LockKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/LockKeywordRecommenderTests.cs index fa4b3e3faef4a..49fe1f3594544 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/LockKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/LockKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/LongKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/LongKeywordRecommenderTests.cs index ff849179d0ddb..503d3a934b18f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/LongKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/LongKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/MethodKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/MethodKeywordRecommenderTests.cs index 8e1b08a2513e2..614edadc19f08 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/MethodKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/MethodKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ModuleKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ModuleKeywordRecommenderTests.cs index b7570eae870cf..b17a413315eb9 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ModuleKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ModuleKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs index 51ad1f5800501..88f2441e98b7d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs @@ -19,6 +19,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass_Interactive() { @@ -108,6 +115,14 @@ await VerifyKeywordAsync(SourceCodeKind.Regular, $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync(SourceCodeKind.Regular, +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterUsing_Interactive() { @@ -116,6 +131,14 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterUsingAlias() { @@ -132,6 +155,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsingAlias() + { + await VerifyKeywordAsync(SourceCodeKind.Regular, +@"global using Goo = Bar; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsingAlias_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"global using Goo = Bar; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterClassDeclaration() { @@ -271,13 +310,38 @@ await VerifyAbsenceAsync(@"$$ using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyAbsenceAsync( @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBetweenGlobalUsings_01() + { + await VerifyAbsenceAsync( +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBetweenGlobalUsings_02() + { + await VerifyAbsenceAsync( +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NativeIntegerKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NativeIntegerKeywordRecommenderTests.cs index cac7fdcfb28a1..b672501179bb3 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NativeIntegerKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NativeIntegerKeywordRecommenderTests.cs @@ -188,6 +188,14 @@ await VerifyKeywordAsync( @"using A = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInGlobalUsingAliasFirst() + { + // Ideally, keywords should not be recommended as first token in target. + await VerifyKeywordAsync( +@"global using A = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInUsingAliasLater() { @@ -195,6 +203,13 @@ await VerifyKeywordAsync( @"using A = List<$$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInGlobalUsingAliasLater() + { + await VerifyKeywordAsync( +@"global using A = List<$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInNameOf() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NewKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NewKeywordRecommenderTests.cs index a712010d4147f..5fac7b143f454 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NewKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NewKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestEmptyStatement(bool topLevelStatement) @@ -676,6 +683,14 @@ public async Task TestAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -733,6 +748,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NotnullKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NotnullKeywordRecommenderTests.cs index 8004164c73ba2..9de7a6c71b670 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NotnullKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NotnullKeywordRecommenderTests.cs @@ -36,6 +36,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterName_Type() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NullKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NullKeywordRecommenderTests.cs index 1eed5d4263d69..52551e35131a5 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NullKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NullKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NullableKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NullableKeywordRecommenderTests.cs index 7480a4d502245..f636cc048e559 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NullableKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NullableKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs index 6607c025f4aa0..4d045c5e04894 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/OnKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/OnKeywordRecommenderTests.cs index 796c057be9de3..905e48f46e1d6 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/OnKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/OnKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/OperatorKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/OperatorKeywordRecommenderTests.cs index 81678fcff4e74..a56ae993cf3cc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/OperatorKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/OperatorKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/OrderByKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/OrderByKeywordRecommenderTests.cs index 0c55c041d0954..9ad6ac6abba56 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/OrderByKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/OrderByKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs index 2e1e47d280939..f2e094dc00624 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInterfaceTypeVarianceAfterAngle() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/OverrideKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/OverrideKeywordRecommenderTests.cs index eb1ff5310ff9e..16b51d5bc23e9 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/OverrideKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/OverrideKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -77,6 +84,13 @@ public async Task TestNotAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -150,6 +164,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ParamKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ParamKeywordRecommenderTests.cs index 6f3a66f4c816d..8a4992b00c2f4 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ParamKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ParamKeywordRecommenderTests.cs @@ -61,6 +61,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ParamsKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ParamsKeywordRecommenderTests.cs index 42be9967cc56f..b92aa2f7316ca 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ParamsKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ParamsKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAngle() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/PartialKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/PartialKeywordRecommenderTests.cs index ed68a9f8ec46d..1a03583b02f8d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/PartialKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/PartialKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -157,6 +172,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -292,6 +323,13 @@ await VerifyAbsenceAsync( @"using static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterStaticInGlobalUsingDirective() + { + await VerifyAbsenceAsync( +@"global using static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass() => await VerifyAbsenceAsync(@"class $$"); @@ -301,12 +339,36 @@ public async Task TestNotAfterDelegate() => await VerifyAbsenceAsync(@"delegate $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_01() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_02() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/PragmaKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/PragmaKeywordRecommenderTests.cs index 4840a2731abe0..f0bf8dfb20c27 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/PragmaKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/PragmaKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/PrivateKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/PrivateKeywordRecommenderTests.cs index f6b89dd1c4cd6..4e93e4a655619 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/PrivateKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/PrivateKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -90,6 +97,20 @@ public async Task TestAfterUsing_Interactive() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"global using Goo; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, @"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -152,6 +173,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, @"$$ using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs index ccfc69f72a73a..0cd48d3c92acb 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading.Tasks; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations @@ -50,6 +49,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -190,5 +196,33 @@ await VerifyAbsenceAsync( @"enum E { [$$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter1() + { + await VerifyKeywordAsync("public record R([$$] string M);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter2() + { + await VerifyKeywordAsync("public record R([$$ SomeAttribute] string M);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter3() + { + await VerifyKeywordAsync("public record R([$$ string M);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter4() + { + await VerifyKeywordAsync("public record R([$$"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ProtectedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ProtectedKeywordRecommenderTests.cs index d355c8f96e1bf..b1113b7d5feac 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ProtectedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ProtectedKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -75,6 +82,13 @@ public async Task TestNotAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -131,6 +145,14 @@ await VerifyAbsenceAsync( using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync( +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/PublicKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/PublicKeywordRecommenderTests.cs index 582682810822b..daca25e5712c7 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/PublicKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/PublicKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -258,6 +289,13 @@ await VerifyAbsenceAsync( @"using static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterStaticInGlobalUsingDirective() + { + await VerifyAbsenceAsync( +@"global using static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass() => await VerifyAbsenceAsync(@"class $$"); @@ -267,12 +305,36 @@ public async Task TestNotAfterDelegate() => await VerifyAbsenceAsync(@"delegate $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_01() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_02() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ReadOnlyKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ReadOnlyKeywordRecommenderTests.cs index 995033b67d233..f901bb2b41a0e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ReadOnlyKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ReadOnlyKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -72,6 +79,13 @@ public async Task TestAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -136,6 +150,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs index af1afd06134e6..bcf70ccee66f2 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RecordKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -82,6 +89,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -149,6 +164,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -318,17 +349,48 @@ await VerifyAbsenceAsync( @"using static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterStaticInGlobalUsingDirective() + { + await VerifyAbsenceAsync( +@"global using static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass() => await VerifyAbsenceAsync(@"class $$"); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] public async Task TestNotBetweenUsings() { - await VerifyAbsenceAsync(AddInsideMethod( + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"using Goo; $$ -using Bar;")); +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_01() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_02() + { + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global using Goo; +$$ +global using Bar;"); } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs index bb6def44e7c00..76c396ccc1786 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAngle() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ReferenceKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ReferenceKeywordRecommenderTests.cs index ed2e3daaff5f1..01df7020e3972 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ReferenceKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ReferenceKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -102,11 +109,27 @@ await VerifyKeywordAsync(SourceCodeKind.Script, using System;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeGlobalUsing() + { + await VerifyKeywordAsync(SourceCodeKind.Script, +@"#$$ +global using System;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterUsing() { await VerifyAbsenceAsync(SourceCodeKind.Script, @"using System; +#$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"global using System; #$$"); } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RegionKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RegionKeywordRecommenderTests.cs index 72b44e2b5029b..ac18d60b3b8a2 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RegionKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RegionKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RemoveKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RemoveKeywordRecommenderTests.cs index 96f54288550a7..c9f1b3ffb98ca 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RemoveKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RemoveKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RestoreKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RestoreKeywordRecommenderTests.cs index 16207c9847161..dbc52fd510f0e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RestoreKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RestoreKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ReturnKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ReturnKeywordRecommenderTests.cs index 1eb7a115526ed..9e5938a714358 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ReturnKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ReturnKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestIncompleteStatementAttributeList() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SByteKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SByteKeywordRecommenderTests.cs index bdf20ff45c889..4c16c296891da 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SByteKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SByteKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs index 538936a298d3e..d92a732f94bb6 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SelectKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SelectKeywordRecommenderTests.cs index 57876da9c81f8..973377ab7dd25 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SelectKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SelectKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SetKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SetKeywordRecommenderTests.cs index f5908a00e1c71..ee1d56cd75c31 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SetKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SetKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ShortKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ShortKeywordRecommenderTests.cs index 9447724fe0ad4..5893d3520dd82 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ShortKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ShortKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SizeOfKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SizeOfKeywordRecommenderTests.cs index 8148afef4826a..793af91f8b526 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SizeOfKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SizeOfKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInsideEmptyMethod() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs index 709ca95725c88..0e0a75bb4dfe5 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs index 3dc5641cef54f..3c5b7e85e4f69 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [WorkItem(32174, "https://github.com/dotnet/roslyn/issues/32174")] public async Task TestInEmptyStatement() @@ -82,6 +89,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -149,6 +164,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -276,6 +307,34 @@ public async Task TestNotBetweenUsings() //await VerifyWorkerAsync(source, absent: true, Options.Script); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_01() + { + var source = @"global using Goo; +$$ +using Bar;"; + + await VerifyWorkerAsync(source, absent: true); + + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + //await VerifyWorkerAsync(source, absent: true, Options.Script); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(32214, "https://github.com/dotnet/roslyn/issues/32214")] + public async Task TestNotBetweenGlobalUsings_02() + { + var source = @"global using Goo; +$$ +global using Bar;"; + + await VerifyWorkerAsync(source, absent: true); + + // Recommendation in scripting is not stable. See https://github.com/dotnet/roslyn/issues/32214 + //await VerifyWorkerAsync(source, absent: true, Options.Script); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNestedAbstract() { @@ -327,6 +386,13 @@ await VerifyKeywordAsync( @"using $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsingInCompilationUnit() + { + await VerifyKeywordAsync( +@"global using $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterUsingInMethodBody() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StringKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StringKeywordRecommenderTests.cs index 9ee735aa9317f..26f59dc77585e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StringKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StringKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StructKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StructKeywordRecommenderTests.cs index d177e74137699..ac56796e1cfae 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StructKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StructKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -140,6 +155,14 @@ await VerifyAbsenceAsync(SourceCodeKind.Regular, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterReadonly() { @@ -204,6 +227,14 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -259,13 +290,6 @@ await VerifyKeywordAsync( @"partial $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestAfterData() - { - await VerifyKeywordAsync( -@"data $$"); - } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAbstract() => await VerifyAbsenceAsync(@"abstract $$"); @@ -298,6 +322,13 @@ await VerifyKeywordAsync( @"protected $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterRecord() + { + await VerifyKeywordAsync(SourceCodeKind.Regular, +@"record $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterSealed() => await VerifyAbsenceAsync(@"sealed $$"); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SwitchKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SwitchKeywordRecommenderTests.cs index 09fadf9c9b273..c851a4e125b8e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SwitchKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SwitchKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ThisKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ThisKeywordRecommenderTests.cs index e1fbdd0743a35..0bdb4a59cbff8 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ThisKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ThisKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAngle() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ThrowKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ThrowKeywordRecommenderTests.cs index 4786249c3e697..aeb8a5407b05f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ThrowKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ThrowKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/TrueKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/TrueKeywordRecommenderTests.cs index b1d91009a8b29..bec058d59e563 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/TrueKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/TrueKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInPreprocessor1() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/TryKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/TryKeywordRecommenderTests.cs index d8bf2e1edf0b2..5bd074840afbc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/TryKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/TryKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/TypeKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/TypeKeywordRecommenderTests.cs index 77db10a277c22..28aa02e81d71e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/TypeKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/TypeKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/TypeVarKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/TypeVarKeywordRecommenderTests.cs index 908b3d5cd04b7..db443c4706302 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/TypeVarKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/TypeVarKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UIntKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UIntKeywordRecommenderTests.cs index dfc4c0fdeacfa..b1b76c718ca3f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UIntKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UIntKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ULongKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ULongKeywordRecommenderTests.cs index 0e849d9681c88..794801e107d29 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ULongKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ULongKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UShortKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UShortKeywordRecommenderTests.cs index c1be39719b57c..c382f7d9e1f31 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UShortKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UShortKeywordRecommenderTests.cs @@ -52,6 +52,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UncheckedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UncheckedKeywordRecommenderTests.cs index caaf01de58044..6a690f270f29b 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UncheckedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UncheckedKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UndefKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UndefKeywordRecommenderTests.cs index 3ea273ed15009..9e945da110b3e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UndefKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UndefKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -88,11 +95,27 @@ await VerifyKeywordAsync( using System;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeGlobalUsing() + { + await VerifyKeywordAsync( +@"#$$ +global using System;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterUsing() { await VerifyAbsenceAsync( @"using System; +#$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync( +@"global using System; #$$"); } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UnmanagedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UnmanagedKeywordRecommenderTests.cs index 05874df6fb05c..8857b6000d989 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UnmanagedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UnmanagedKeywordRecommenderTests.cs @@ -36,6 +36,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterName_Type() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UnsafeKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UnsafeKeywordRecommenderTests.cs index 618d176070894..cd19af5e899ca 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UnsafeKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UnsafeKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInEmptyStatement() { @@ -81,6 +88,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -148,6 +163,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -263,6 +294,13 @@ await VerifyAbsenceAsync( @"using static $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterStaticInGlobalUsingDirective() + { + await VerifyAbsenceAsync( +@"global using static $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterClass() => await VerifyAbsenceAsync(@"class $$"); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs index 4c9091315a0ea..ab43e259be52d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs @@ -20,6 +20,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterClass() { @@ -97,6 +104,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterPreviousGlobalUsing() + { + await VerifyKeywordAsync( +@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterExtern() { @@ -105,6 +120,32 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalAfterExtern() + { + await VerifyKeywordAsync( +@"extern alias goo; +global $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalAfterExternBeforeUsing_01() + { + await VerifyKeywordAsync( +@"extern alias goo; +global $$ +using Goo;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalAfterExternBeforeUsing_02() + { + await VerifyKeywordAsync( +@"extern alias goo; +global $$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestBeforeUsing() { @@ -113,6 +154,14 @@ await VerifyKeywordAsync( using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeUsingAfterGlobal() + { + await VerifyKeywordAsync( +@"global $$ +using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterUsingAlias() { @@ -121,6 +170,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsingAlias() + { + await VerifyKeywordAsync( +@"global using Goo = Bar; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNestedTypeDeclaration() { @@ -195,6 +252,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, extern alias Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeExternAfterGlobal() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"global $$ +extern alias Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeExternAfterGlobal_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"global $$ +extern alias Goo;"); + } + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [CombinatorialData] public async Task TestBeforeStatement(bool topLevelStatement) @@ -275,6 +348,13 @@ await VerifyAbsenceAsync( @"using $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync( +@"global using $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInClass() { @@ -285,12 +365,122 @@ await VerifyAbsenceAsync(@"class C } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestBetweenUsings() + public async Task TestBetweenUsings_01() { await VerifyKeywordAsync( @"using Goo; $$ using Bar;"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBetweenUsings_02() + { + await VerifyKeywordAsync( +@"global using Goo; +$$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalBetweenUsings_01() + { + await VerifyKeywordAsync( +@"global using Goo; +global $$ +using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalBetweenUsings_02() + { + await VerifyKeywordAsync( +@"global using Goo; +global $$ +global using Bar;"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobal() + { + await VerifyKeywordAsync( +@"global $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeNamespace() + { + await VerifyKeywordAsync( +@"$$ +namespace NS +{}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeClass() + { + await VerifyKeywordAsync( +@"$$ +class C1 +{}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeAttribute_01() + { + await VerifyKeywordAsync( +@"$$ +[Call()]"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeAttribute_02() + { + await VerifyKeywordAsync( +@"$$ +[assembly: Call()]"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeNamespaceAfterGlobal() + { + await VerifyKeywordAsync( +@"global $$ +namespace NS +{}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeClassAfterGlobal() + { + await VerifyKeywordAsync( +@"global $$ +class C1 +{}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeStatementAfterGlobal() + { + await VerifyKeywordAsync( +@"global $$ +Call();"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeAttributeAfterGlobal_01() + { + await VerifyKeywordAsync( +@"global $$ +[Call()]"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeAttributeAfterGlobal_02() + { + await VerifyKeywordAsync( +@"global $$ +[assembly: Call()]"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs index 5347795e01c3f..89d1d0b019f9f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs @@ -61,6 +61,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterStackAlloc() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs index c826d4221b272..5c3c9a7088f29 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -75,6 +82,13 @@ public async Task TestNotAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -131,6 +145,14 @@ await VerifyAbsenceAsync( using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync( +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs index 0e2ff6ade57c3..7017292281ba6 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterStackAlloc() { @@ -111,6 +118,13 @@ public async Task TestAfterUsing() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing() + { + await VerifyKeywordAsync(@"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterNamespace() { @@ -168,6 +182,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterAssemblyAttribute() { @@ -785,6 +815,15 @@ struct S public readonly $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterReadonlyInRecordStruct() + { + await VerifyKeywordAsync(@" +record struct S +{ + public readonly $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [WorkItem(43295, "https://github.com/dotnet/roslyn/issues/43295")] public async Task TestNotAfterReadonlyInClass() diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VolatileKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VolatileKeywordRecommenderTests.cs index 246e8504a1c05..6288890c7c14f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VolatileKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VolatileKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { @@ -90,6 +97,20 @@ public async Task TestAfterUsing_Interactive() $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, @"global using Goo; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterGlobalUsing_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, @"global using Goo; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterNamespace() { @@ -154,6 +175,22 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, using Goo;"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"$$ +global using Goo;"); + } + + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/9880"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotBeforeGlobalUsing_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, +@"$$ +global using Goo;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterAssemblyAttribute() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WarningKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WarningKeywordRecommenderTests.cs index a7ea206d62060..e00620301a78d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WarningKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WarningKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WarningsKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WarningsKeywordRecommenderTests.cs index e7b17d7d2197d..c3dd09abc1b52 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WarningsKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WarningsKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs index 6f29f4ea0f333..ce5a43d478a0d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs @@ -51,6 +51,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WhileKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WhileKeywordRecommenderTests.cs index 89579a0860dd1..d2298c5bb3712 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WhileKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WhileKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WithKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WithKeywordRecommenderTests.cs index a97fc47d16733..d8ffab216d7af 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WithKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WithKeywordRecommenderTests.cs @@ -58,6 +58,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotInEmptyStatement() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/YieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/YieldKeywordRecommenderTests.cs index 0e73dc87c7868..78b612acda82f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/YieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/YieldKeywordRecommenderTests.cs @@ -50,6 +50,13 @@ await VerifyAbsenceAsync( @"using Goo = $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestEmptyStatement() { diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindBaseSymbolsCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindBaseSymbolsCommandHandler.cs index 66bcf8b11d006..5e2185b61c07a 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindBaseSymbolsCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindBaseSymbolsCommandHandler.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.Editor.Host; @@ -49,8 +48,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, var streamingPresenter = base.GetStreamingPresenter(); if (streamingPresenter != null) { - // Fire and forget. So no need for cancellation. - _ = StreamingFindBaseSymbolsAsync(document, caretPosition, streamingPresenter, CancellationToken.None); + _ = StreamingFindBaseSymbolsAsync(document, caretPosition, streamingPresenter); return true; } @@ -59,39 +57,34 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, private async Task StreamingFindBaseSymbolsAsync( Document document, int caretPosition, - IStreamingFindUsagesPresenter presenter, - CancellationToken cancellationToken) + IStreamingFindUsagesPresenter presenter) { try { using var token = _asyncListener.BeginAsyncOperation(nameof(StreamingFindBaseSymbolsAsync)); - var context = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true, cancellationToken); + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true); using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { try { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); var overriddenSymbol = relevantSymbol?.symbol.GetOverriddenMember(); while (overriddenSymbol != null) { - if (context.CancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) { return; } var definitionItem = overriddenSymbol.ToNonClassifiedDefinitionItem(document.Project.Solution, true); -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await context.OnDefinitionFoundAsync(definitionItem); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); // try getting the next one overriddenSymbol = overriddenSymbol.GetOverriddenMember(); @@ -99,7 +92,7 @@ private async Task StreamingFindBaseSymbolsAsync( } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindDerivedSymbolsCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindDerivedSymbolsCommandHandler.cs index 33c8e08fb4427..6577d0200543a 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindDerivedSymbolsCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindDerivedSymbolsCommandHandler.cs @@ -50,8 +50,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, var streamingPresenter = base.GetStreamingPresenter(); if (streamingPresenter != null) { - // Fire and forget. So no need for cancellation. - _ = FindDerivedSymbolsAsync(document, caretPosition, streamingPresenter, CancellationToken.None); + _ = FindDerivedSymbolsAsync(document, caretPosition, streamingPresenter); return true; } @@ -64,64 +63,56 @@ private static async Task> GatherSymbolsAsync(ISymbol symbo // we can use the FindInterfaceImplementationAsync call if (symbol.ContainingType is INamedTypeSymbol namedTypeSymbol && symbol.ContainingType.TypeKind == TypeKind.Interface) { - return (await SymbolFinder.FindImplementationsAsync(namedTypeSymbol, solution, null, cancellationToken).ConfigureAwait(false)).OfType(); + return await SymbolFinder.FindImplementationsAsync(namedTypeSymbol, solution, null, cancellationToken).ConfigureAwait(false); } else if (symbol is INamedTypeSymbol namedTypeSymbol2 && namedTypeSymbol2.TypeKind == TypeKind.Interface) { - return (await SymbolFinder.FindImplementationsAsync(namedTypeSymbol2, solution, null, cancellationToken).ConfigureAwait(false)).OfType(); + return await SymbolFinder.FindImplementationsAsync(namedTypeSymbol2, solution, null, cancellationToken).ConfigureAwait(false); } // if it's not, but is instead a class, we can use FindDerivedClassesAsync else if (symbol is INamedTypeSymbol namedTypeSymbol3) { - return (await SymbolFinder.FindDerivedClassesAsync(namedTypeSymbol3, solution, null, cancellationToken).ConfigureAwait(false)).OfType(); + return await SymbolFinder.FindDerivedClassesAsync(namedTypeSymbol3, solution, null, cancellationToken).ConfigureAwait(false); } // and lastly, if it's a method, we can use FindOverridesAsync else { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - return await SymbolFinder.FindOverridesAsync(symbol, solution, null, cancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + return await SymbolFinder.FindOverridesAsync(symbol, solution, null, cancellationToken).ConfigureAwait(false); } } private async Task FindDerivedSymbolsAsync( - Document document, int caretPosition, IStreamingFindUsagesPresenter presenter, CancellationToken cancellationToken) + Document document, int caretPosition, IStreamingFindUsagesPresenter presenter) { try { using var token = _asyncListener.BeginAsyncOperation(nameof(FindDerivedSymbolsAsync)); - var context = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true, cancellationToken); + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true); try { using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var candidateSymbolProjectPair = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var candidateSymbolProjectPair = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); if (candidateSymbolProjectPair?.symbol == null) return; -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task var candidates = await GatherSymbolsAsync(candidateSymbolProjectPair.Value.symbol, - document.Project.Solution, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + document.Project.Solution, cancellationToken).ConfigureAwait(false); foreach (var candidate in candidates) { var definitionItem = candidate.ToNonClassifiedDefinitionItem(document.Project.Solution, true); -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await context.OnDefinitionFoundAsync(definitionItem); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } } } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindExtensionMethodsCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindExtensionMethodsCommandHandler.cs index ccb0fdc0ab362..da6bc3658027a 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindExtensionMethodsCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindExtensionMethodsCommandHandler.cs @@ -5,14 +5,15 @@ #nullable disable using System; -using System.Linq; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -21,8 +22,6 @@ using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using VSCommanding = Microsoft.VisualStudio.Commanding; -using Microsoft.CodeAnalysis.Host.Mef; -using System.Threading; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationCommandHandlers { @@ -53,8 +52,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, var streamingPresenter = base.GetStreamingPresenter(); if (streamingPresenter != null) { - // Fire and forget. So no need for cancellation. - _ = FindExtensionMethodsAsync(document, caretPosition, streamingPresenter, CancellationToken.None); + _ = FindExtensionMethodsAsync(document, caretPosition, streamingPresenter); return true; } @@ -62,49 +60,46 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, } private async Task FindExtensionMethodsAsync( - Document document, int caretPosition, IStreamingFindUsagesPresenter presenter, CancellationToken cancellationToken) + Document document, int caretPosition, IStreamingFindUsagesPresenter presenter) { try { using var token = _asyncListener.BeginAsyncOperation(nameof(FindExtensionMethodsAsync)); - var context = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true, cancellationToken); + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true); using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var candidateSymbolProjectPair = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var candidateSymbolProjectPair = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); var symbol = candidateSymbolProjectPair?.symbol as INamedTypeSymbol; // if we didn't get the right symbol, just abort if (symbol == null) { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); return; } - Compilation compilation; - if (!document.Project.TryGetCompilation(out compilation)) + if (!document.Project.TryGetCompilation(out var compilation)) { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); return; } var solution = document.Project.Solution; - foreach (var type in compilation.Assembly.GlobalNamespace.GetAllTypes(context.CancellationToken)) + foreach (var type in compilation.Assembly.GlobalNamespace.GetAllTypes(cancellationToken)) { if (!type.MightContainExtensionMethods) continue; foreach (var extMethod in type.GetMembers().OfType().Where(method => method.IsExtensionMethod)) { - if (context.CancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) break; var reducedMethod = extMethod.ReduceExtensionMethod(symbol); @@ -112,24 +107,22 @@ private async Task FindExtensionMethodsAsync( { var loc = extMethod.Locations.First(); - var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(reducedMethod, solution, context.CancellationToken).ConfigureAwait(false); + var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(reducedMethod, solution, cancellationToken).ConfigureAwait(false); // And if our definition actually is from source, then let's re-figure out what project it came from if (sourceDefinition != null) { - var originatingProject = solution.GetProject(sourceDefinition.ContainingAssembly, context.CancellationToken); + var originatingProject = solution.GetProject(sourceDefinition.ContainingAssembly, cancellationToken); var definitionItem = reducedMethod.ToNonClassifiedDefinitionItem(solution, true); -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await context.OnDefinitionFoundAsync(definitionItem); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } } } } - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindImplementingMembersCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindImplementingMembersCommandHandler.cs index ce37f9c2f5860..7e8998cfff489 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindImplementingMembersCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindImplementingMembersCommandHandler.cs @@ -51,8 +51,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, var streamingPresenter = base.GetStreamingPresenter(); if (streamingPresenter != null) { - // Fire and forget. So no need for cancellation. - _ = FindImplementingMembersAsync(document, caretPosition, streamingPresenter, CancellationToken.None); + _ = FindImplementingMembersAsync(document, caretPosition, streamingPresenter); return true; } @@ -60,7 +59,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, } private async Task FindImplementingMembersAsync( - Document document, int caretPosition, IStreamingFindUsagesPresenter presenter, CancellationToken cancellationToken) + Document document, int caretPosition, IStreamingFindUsagesPresenter presenter) { try { @@ -69,18 +68,16 @@ private async Task FindImplementingMembersAsync( // Let the presented know we're starting a search. We pass in no cancellation token here as this // operation itself is fire-and-forget and the user won't cancel the operation through us (though // the window itself can cancel the operation if it is taken over for another find operation. - var context = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true, cancellationToken); + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true); using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { try { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); var interfaceSymbol = relevantSymbol?.symbol as INamedTypeSymbol; @@ -92,13 +89,10 @@ private async Task FindImplementingMembersAsync( // we now need to find the class that implements this particular interface, at the // caret position, or somewhere around it - SyntaxNode nodeRoot; - if (!document.TryGetSyntaxRoot(out nodeRoot)) + if (!document.TryGetSyntaxRoot(out var nodeRoot)) return; -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var documentToken = nodeRoot.FindToken(caretPosition); if (!documentToken.Span.IntersectsWith(caretPosition)) @@ -106,9 +100,7 @@ private async Task FindImplementingMembersAsync( // the parents should bring us to the class definition var parentTypeNode = documentToken.Parent?.Parent?.Parent?.Parent; -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var compilation = await document.Project.GetCompilationAsync(cancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); // let's finally get our implementing type var namedTypeSymbol = compilation.GetSemanticModel(syntaxTree).GetDeclaredSymbol(parentTypeNode, cancellationToken: cancellationToken) as INamedTypeSymbol; @@ -117,21 +109,17 @@ private async Task FindImplementingMembersAsync( return; // we can search for implementations of the interface, within this type -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await InspectInterfaceAsync(context, interfaceSymbol, namedTypeSymbol, document.Project); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await InspectInterfaceAsync(context, interfaceSymbol, namedTypeSymbol, document.Project, cancellationToken).ConfigureAwait(false); // now, we iterate on interfaces of our interfaces foreach (var iFace in interfaceSymbol.AllInterfaces) { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await InspectInterfaceAsync(context, iFace, namedTypeSymbol, document.Project); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await InspectInterfaceAsync(context, iFace, namedTypeSymbol, document.Project, cancellationToken).ConfigureAwait(false); } } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } } @@ -143,11 +131,12 @@ private async Task FindImplementingMembersAsync( } } - private static async Task InspectInterfaceAsync(IFindUsagesContext context, INamedTypeSymbol interfaceSymbol, INamedTypeSymbol namedTypeSymbol, Project project) + private static async Task InspectInterfaceAsync( + IFindUsagesContext context, INamedTypeSymbol interfaceSymbol, INamedTypeSymbol namedTypeSymbol, Project project, CancellationToken cancellationToken) { foreach (var interfaceMember in interfaceSymbol.GetMembers()) { - if (context.CancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; var impl = namedTypeSymbol.FindImplementationForInterfaceMember(interfaceMember); @@ -155,9 +144,7 @@ private static async Task InspectInterfaceAsync(IFindUsagesContext context, INam continue; var definitionItem = impl.ToNonClassifiedDefinitionItem(project.Solution, true); -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await context.OnDefinitionFoundAsync(definitionItem); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindMemberOverloadsCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindMemberOverloadsCommandHandler.cs index 201037f4d185f..9f28f81723d43 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindMemberOverloadsCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindMemberOverloadsCommandHandler.cs @@ -49,8 +49,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, var streamingPresenter = base.GetStreamingPresenter(); if (streamingPresenter != null) { - // Fire and forget. So no need for cancellation. - _ = FindMemberOverloadsAsync(document, caretPosition, streamingPresenter, CancellationToken.None); + _ = FindMemberOverloadsAsync(document, caretPosition, streamingPresenter); return true; } @@ -58,25 +57,22 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, } private async Task FindMemberOverloadsAsync( - Document document, int caretPosition, IStreamingFindUsagesPresenter presenter, CancellationToken cancellationToken) + Document document, int caretPosition, IStreamingFindUsagesPresenter presenter) { try { using var token = _asyncListener.BeginAsyncOperation(nameof(FindMemberOverloadsAsync)); - var context = presenter.StartSearch( - EditorFeaturesResources.Navigating, supportsReferences: true, cancellationToken); + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Navigating, supportsReferences: true); using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { try { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var candidateSymbolProjectPair = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var candidateSymbolProjectPair = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); // we need to get the containing type (i.e. class) var symbol = candidateSymbolProjectPair?.symbol; @@ -89,14 +85,12 @@ private async Task FindMemberOverloadsAsync( .Where(m => m.Kind == symbol.Kind && m.Name == symbol.Name)) { var definitionItem = curSymbol.ToNonClassifiedDefinitionItem(document.Project.Solution, true); -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await context.OnDefinitionFoundAsync(definitionItem); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs index 302272ee68d8e..433908df46f91 100644 --- a/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs +++ b/src/EditorFeatures/Core.Cocoa/NavigationCommandHandlers/FindReferencesOfOverloadsCommandHandler.cs @@ -64,8 +64,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, // a presenter that can accept streamed results. if (streamingService != null && streamingPresenter != null) { - // Fire and forget. So no need for cancellation. - _ = StreamingFindReferencesAsync(document, caretPosition, streamingPresenter, CancellationToken.None); + _ = StreamingFindReferencesAsync(document, caretPosition, streamingPresenter); return true; } @@ -74,9 +73,7 @@ protected override bool TryExecuteCommand(int caretPosition, Document document, private static async Task GatherSymbolsAsync(ISymbol symbol, Microsoft.CodeAnalysis.Solution solution, CancellationToken token) { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var implementations = await SymbolFinder.FindImplementationsAsync(symbol, solution, null, token); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var implementations = await SymbolFinder.FindImplementationsAsync(symbol, solution, null, token).ConfigureAwait(false); var result = new ISymbol[implementations.Count() + 1]; result[0] = symbol; var i = 1; @@ -88,14 +85,12 @@ private static async Task GatherSymbolsAsync(ISymbol symbol, Microsof } private async Task StreamingFindReferencesAsync( - Document document, int caretPosition, IStreamingFindUsagesPresenter presenter, CancellationToken cancellationToken) + Document document, int caretPosition, IStreamingFindUsagesPresenter presenter) { try { // first, let's see if we even have a comment, otherwise there's no use in starting a search -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, new CancellationToken()); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, new CancellationToken()).ConfigureAwait(false); var symbol = relevantSymbol?.symbol; if (symbol == null) return; // would be useful if we could notify the user why we didn't do anything @@ -105,40 +100,35 @@ private async Task StreamingFindReferencesAsync( using var token = _asyncListener.BeginAsyncOperation(nameof(StreamingFindReferencesAsync)); - var context = presenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true, cancellationToken); + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true); using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { var symbolsToLookup = new List(); foreach (var curSymbol in symbol.ContainingType.GetMembers() .Where(m => m.Kind == symbol.Kind && m.Name == symbol.Name)) { - Compilation compilation; - if (!document.Project.TryGetCompilation(out compilation)) + if (!document.Project.TryGetCompilation(out var compilation)) { // TODO: should we do anything more here? continue; } - foreach (var sym in SymbolFinder.FindSimilarSymbols(curSymbol, compilation, context.CancellationToken)) + foreach (var sym in SymbolFinder.FindSimilarSymbols(curSymbol, compilation, cancellationToken)) { // assumption here is, that FindSimilarSymbols returns symbols inside same project -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - var symbolsToAdd = await GatherSymbolsAsync(sym, document.Project.Solution, context.CancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + var symbolsToAdd = await GatherSymbolsAsync(sym, document.Project.Solution, cancellationToken).ConfigureAwait(false); symbolsToLookup.AddRange(symbolsToAdd); } } foreach (var candidate in symbolsToLookup) { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await AbstractFindUsagesService.FindSymbolReferencesAsync(context, candidate, document.Project); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + await AbstractFindUsagesService.FindSymbolReferencesAsync(context, candidate, document.Project, cancellationToken).ConfigureAwait(false); } // Note: we don't need to put this in a finally. The only time we might not hit @@ -146,7 +136,7 @@ private async Task StreamingFindReferencesAsync( // that means that a new search has started. We don't care about telling the // context it has completed. In the latter case something wrong has happened // and we don't want to run any more code in this particular context. - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) diff --git a/src/EditorFeatures/Core.Cocoa/Preview/PreviewPaneService.cs b/src/EditorFeatures/Core.Cocoa/Preview/PreviewPaneService.cs index 1fb1b80ecacea..16c715483f911 100644 --- a/src/EditorFeatures/Core.Cocoa/Preview/PreviewPaneService.cs +++ b/src/EditorFeatures/Core.Cocoa/Preview/PreviewPaneService.cs @@ -97,13 +97,6 @@ object IPreviewPaneService.GetPreviewPane( var helpLinkUri = BrowserHelper.GetHelpLink(data); var helpLinkToolTip = BrowserHelper.GetHelpLinkToolTip(data.Id, helpLinkUri); - Guid optionPageGuid = default; - if (data.Properties.TryGetValue("OptionName", out _)) - { - data.Properties.TryGetValue("OptionLanguage", out _); - throw new NotImplementedException(); - } - return new PreviewPane( severityIcon: null,//TODO: Mac GetSeverityIconForDiagnostic(diagnostic), id: data.Id, title: title, @@ -111,8 +104,7 @@ object IPreviewPaneService.GetPreviewPane( helpLink: helpLinkUri, helpLinkToolTipText: helpLinkToolTip, previewContent: previewContent, - logIdVerbatimInTelemetry: data.CustomTags.Contains(WellKnownDiagnosticTags.Telemetry), - optionPageGuid: optionPageGuid); + logIdVerbatimInTelemetry: data.CustomTags.Contains(WellKnownDiagnosticTags.Telemetry)); } } } diff --git a/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs b/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs index f632c49ffbe2f..fabe6cecf8b1a 100644 --- a/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs +++ b/src/EditorFeatures/Core.Cocoa/Structure/StructureTaggerProvider.cs @@ -24,11 +24,10 @@ internal partial class StructureTaggerProvider : [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public StructureTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, IEditorOptionsFactoryService editorOptionsFactoryService, IProjectionBufferFactoryService projectionBufferFactoryService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, notificationService, editorOptionsFactoryService, projectionBufferFactoryService, listenerProvider) + : base(threadingContext, editorOptionsFactoryService, projectionBufferFactoryService, listenerProvider) { } diff --git a/src/EditorFeatures/Core.Wpf/BraceMatching/ClassificationTypeFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/BraceMatching/BraceMatchingTypeFormatDefinitions.cs similarity index 95% rename from src/EditorFeatures/Core.Wpf/BraceMatching/ClassificationTypeFormatDefinitions.cs rename to src/EditorFeatures/Core.Wpf/BraceMatching/BraceMatchingTypeFormatDefinitions.cs index 8f16a89b90d4d..0dce2638b6d40 100644 --- a/src/EditorFeatures/Core.Wpf/BraceMatching/ClassificationTypeFormatDefinitions.cs +++ b/src/EditorFeatures/Core.Wpf/BraceMatching/BraceMatchingTypeFormatDefinitions.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.BraceMatching { - internal sealed class ClassificationTypeFormatDefinitions + internal sealed class BraceMatchingTypeFormatDefinitions { [Export(typeof(EditorFormatDefinition))] [ClassificationType(ClassificationTypeNames = ClassificationTypeDefinitions.BraceMatchingName)] diff --git a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs index 995659f5580e7..2ee5feed1ee41 100644 --- a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs +++ b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs @@ -127,6 +127,25 @@ public OperatorOverloadedFormatDefinition() } #endregion + #region Reassigned Variable + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.ReassignedVariable)] + [Name(ClassificationTypeNames.ReassignedVariable)] + [Order(After = Priority.High)] + [UserVisible(false)] + [ExcludeFromCodeCoverage] + private class ReassignedVariableFormatDefinition : ClassificationFormatDefinition + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ReassignedVariableFormatDefinition() + { + this.DisplayName = EditorFeaturesResources.Reassigned_variable; + this.TextDecorations = System.Windows.TextDecorations.Underline; + } + } + #endregion + #region Symbol - Static [Export(typeof(EditorFormatDefinition))] [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.StaticSymbol)] @@ -204,6 +223,25 @@ public UserTypeRecordsFormatDefinition() } } #endregion + #region User Types - Record structs + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.RecordStructName)] + [Name(ClassificationTypeNames.RecordStructName)] + [Order(After = PredefinedClassificationTypeNames.Identifier)] + [Order(After = PredefinedClassificationTypeNames.Keyword)] + [Order(Before = ClassificationTypeNames.StaticSymbol)] + [UserVisible(true)] + [ExcludeFromCodeCoverage] + private class UserTypeRecordStructsFormatDefinition : ClassificationFormatDefinition + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public UserTypeRecordStructsFormatDefinition() + { + this.DisplayName = EditorFeaturesResources.User_Types_Record_Structs; + } + } + #endregion #region User Types - Delegates [Export(typeof(EditorFormatDefinition))] [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.DelegateName)] diff --git a/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs b/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs index 531e22176b322..bb49cea83a519 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs @@ -19,6 +19,7 @@ using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using System.Collections.Generic; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Interactive { @@ -49,8 +50,8 @@ internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) { // Now, we're going to do a bunch of async operations. So create a wait // indicator so the user knows something is happening, and also so they cancel. - var waitIndicator = GetWaitIndicator(); - var waitContext = waitIndicator.StartWait(title, EditorFeaturesWpfResources.Building_Project, allowCancel: true, showProgress: false); + var uiThreadOperationExecutor = GetUIThreadOperationExecutor(); + var context = uiThreadOperationExecutor.BeginExecute(title, EditorFeaturesWpfResources.Building_Project, allowCancellation: true, showProgress: false); var resetInteractiveTask = ResetInteractiveAsync( interactiveWindow, @@ -60,13 +61,13 @@ internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) projectNamespaces, projectDirectory, platform, - waitContext); + context); // Once we're done resetting, dismiss the wait indicator and focus the REPL window. return resetInteractiveTask.SafeContinueWith( _ => { - waitContext.Dispose(); + context.Dispose(); ExecutionCompleted?.Invoke(this, new EventArgs()); }, TaskScheduler.FromCurrentSynchronizationContext()); @@ -83,14 +84,14 @@ private async Task ResetInteractiveAsync( ImmutableArray projectNamespaces, string projectDirectory, InteractiveHostPlatform? platform, - IWaitContext waitContext) + IUIThreadOperationContext uiThreadOperationContext) { // First, open the repl window. var evaluator = (IResettableInteractiveEvaluator)interactiveWindow.Evaluator; // If the user hits the cancel button on the wait indicator, then we want to stop the // build. - using (waitContext.CancellationToken.Register(() => + using (uiThreadOperationContext.UserCancellationToken.Register(() => CancelBuildProject(), useSynchronizationContext: true)) { // First, start a build. @@ -103,7 +104,7 @@ private async Task ResetInteractiveAsync( } // Then reset the REPL - waitContext.Message = EditorFeaturesWpfResources.Resetting_Interactive; + using var scope = uiThreadOperationContext.AddScope(allowCancellation: true, EditorFeaturesWpfResources.Resetting_Interactive); evaluator.ResetOptions = new InteractiveEvaluatorResetOptions(platform); await interactiveWindow.Operations.ResetAsync(initialize: true).ConfigureAwait(true); @@ -151,6 +152,6 @@ protected abstract bool GetProjectProperties( /// protected abstract void CancelBuildProject(); - protected abstract IWaitIndicator GetWaitIndicator(); + protected abstract IUIThreadOperationExecutor GetUIThreadOperationExecutor(); } } diff --git a/src/EditorFeatures/Core.Wpf/LineSeparators/EditorFormatMapChangedEventSource.cs b/src/EditorFeatures/Core.Wpf/LineSeparators/EditorFormatMapChangedEventSource.cs index 10ea5c1110773..427fcf96cc48d 100644 --- a/src/EditorFeatures/Core.Wpf/LineSeparators/EditorFormatMapChangedEventSource.cs +++ b/src/EditorFeatures/Core.Wpf/LineSeparators/EditorFormatMapChangedEventSource.cs @@ -2,10 +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. -#nullable disable - using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.VisualStudio.Text.Classification; namespace Microsoft.CodeAnalysis.Editor.Implementation.LineSeparators @@ -14,11 +11,8 @@ internal sealed class EditorFormatMapChangedEventSource : AbstractTaggerEventSou { private readonly IEditorFormatMap _editorFormatMap; - public EditorFormatMapChangedEventSource(IEditorFormatMap editorFormatMap, TaggerDelay delay) - : base(delay) - { - _editorFormatMap = editorFormatMap; - } + public EditorFormatMapChangedEventSource(IEditorFormatMap editorFormatMap) + => _editorFormatMap = editorFormatMap; public override void Connect() => _editorFormatMap.FormatMappingChanged += OnEditorFormatMapChanged; diff --git a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs index eaddf7f2815cb..1e1bb25c76b7f 100644 --- a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs @@ -50,15 +50,16 @@ internal partial class LineSeparatorTaggerProvider : AsynchronousTaggerProvider< public LineSeparatorTaggerProvider( IThreadingContext threadingContext, IEditorFormatMapService editorFormatMapService, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.LineSeparators), notificationService) + : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.LineSeparators)) { _editorFormatMap = editorFormatMapService.GetEditorFormatMap("text"); _editorFormatMap.FormatMappingChanged += OnFormatMappingChanged; _lineSeparatorTag = new LineSeparatorTag(_editorFormatMap); } + protected override TaggerDelay EventChangeDelay => TaggerDelay.NearImmediate; + private void OnFormatMappingChanged(object sender, FormatItemsEventArgs e) { lock (_lineSeperatorTagGate) @@ -71,8 +72,8 @@ protected override ITaggerEventSource CreateEventSource( ITextView textView, ITextBuffer subjectBuffer) { return TaggerEventSources.Compose( - new EditorFormatMapChangedEventSource(_editorFormatMap, TaggerDelay.NearImmediate), - TaggerEventSources.OnTextChanged(subjectBuffer, TaggerDelay.NearImmediate)); + new EditorFormatMapChangedEventSource(_editorFormatMap), + TaggerEventSources.OnTextChanged(subjectBuffer)); } protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) diff --git a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs index 7639e883ee67f..af11dac4108be 100644 --- a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs +++ b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.FindUsages; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.NavigableSymbols @@ -24,7 +25,7 @@ private class NavigableSymbol : INavigableSymbol private readonly Document _document; private readonly IThreadingContext _threadingContext; private readonly IStreamingFindUsagesPresenter _presenter; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; public NavigableSymbol( ImmutableArray definitions, @@ -32,7 +33,7 @@ public NavigableSymbol( Document document, IThreadingContext threadingContext, IStreamingFindUsagesPresenter streamingPresenter, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uiThreadOperationExecutor) { Contract.ThrowIfFalse(definitions.Length > 0); @@ -41,7 +42,7 @@ public NavigableSymbol( SymbolSpan = symbolSpan; _threadingContext = threadingContext; _presenter = streamingPresenter; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public SnapshotSpan SymbolSpan { get; } @@ -50,10 +51,10 @@ public NavigableSymbol( SpecializedCollections.SingletonEnumerable(PredefinedNavigableRelationships.Definition); public void Navigate(INavigableRelationship relationship) => - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Go_to_Definition, - message: EditorFeaturesResources.Navigating_to_definition, - allowCancel: true, + defaultDescription: EditorFeaturesResources.Navigating_to_definition, + allowCancellation: true, showProgress: false, action: context => GoToDefinitionHelpers.TryGoToDefinition( _definitions, @@ -61,7 +62,7 @@ public void Navigate(INavigableRelationship relationship) => _definitions[0].NameDisplayParts.GetFullText(), _threadingContext, _presenter, - context.CancellationToken)); + context.UserCancellationToken)); } } } diff --git a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbolSource.cs b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbolSource.cs index efb8652207950..59f44d6592860 100644 --- a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbolSource.cs +++ b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbolSource.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Editor.NavigableSymbols { @@ -24,18 +25,18 @@ private partial class NavigableSymbolSource : INavigableSymbolSource { private readonly IThreadingContext _threadingContext; private readonly IStreamingFindUsagesPresenter _presenter; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private bool _disposed; public NavigableSymbolSource( IThreadingContext threadingContext, IStreamingFindUsagesPresenter streamingPresenter, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uiThreadOperationExecutor) { _threadingContext = threadingContext; _presenter = streamingPresenter; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public void Dispose() @@ -72,7 +73,7 @@ public async Task GetNavigableSymbolAsync(SnapshotSpan trigger } var snapshotSpan = new SnapshotSpan(snapshot, context.Span.ToSpan()); - return new NavigableSymbol(definitions.ToImmutableArray(), snapshotSpan, document, _threadingContext, _presenter, _waitIndicator); + return new NavigableSymbol(definitions.ToImmutableArray(), snapshotSpan, document, _threadingContext, _presenter, _uiThreadOperationExecutor); } } } diff --git a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.cs b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.cs index ae20e28382b32..ef40e9a7bd96d 100644 --- a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.cs +++ b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.cs @@ -22,18 +22,18 @@ namespace Microsoft.CodeAnalysis.Editor.NavigableSymbols internal partial class NavigableSymbolService : INavigableSymbolSourceProvider { private static readonly object s_key = new(); - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly IThreadingContext _threadingContext; private readonly IStreamingFindUsagesPresenter _streamingPresenter; [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public NavigableSymbolService( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, IThreadingContext threadingContext, IStreamingFindUsagesPresenter streamingPresenter) { - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _threadingContext = threadingContext; _streamingPresenter = streamingPresenter; } @@ -41,7 +41,7 @@ public NavigableSymbolService( public INavigableSymbolSource TryCreateNavigableSymbolSource(ITextView textView, ITextBuffer buffer) { return textView.GetOrCreatePerSubjectBufferProperty(buffer, s_key, - (v, b) => new NavigableSymbolSource(_threadingContext, _streamingPresenter, _waitIndicator)); + (v, b) => new NavigableSymbolSource(_threadingContext, _streamingPresenter, _uiThreadOperationExecutor)); } } } diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index 4e1c1550397f7..0d6b2cf9ee099 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs @@ -52,12 +52,15 @@ private void ReportMatchResult(Project project, INavigateToSearchResult result) { var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); - var patternMatch = new PatternMatch(GetPatternMatchKind(result.MatchKind), - punctuationStripped: true, result.IsCaseSensitive, matchedSpans); + var patternMatch = new PatternMatch( + GetPatternMatchKind(result.MatchKind), + punctuationStripped: false, + result.IsCaseSensitive, + matchedSpans); var navigateToItem = new NavigateToItem( result.Name, - GetKind(result.Kind), + result.Kind, GetNavigateToLanguage(project.Language), result.SecondarySort, result, @@ -66,46 +69,6 @@ private void ReportMatchResult(Project project, INavigateToSearchResult result) _callback.AddItem(navigateToItem); } - private static string GetKind(string kind) - => kind switch - { - CodeAnalysis.NavigateTo.NavigateToItemKind.Class - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Class, - CodeAnalysis.NavigateTo.NavigateToItemKind.Constant - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Constant, - CodeAnalysis.NavigateTo.NavigateToItemKind.Delegate - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Delegate, - CodeAnalysis.NavigateTo.NavigateToItemKind.Enum - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Enum, - CodeAnalysis.NavigateTo.NavigateToItemKind.EnumItem - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.EnumItem, - CodeAnalysis.NavigateTo.NavigateToItemKind.Event - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Event, - CodeAnalysis.NavigateTo.NavigateToItemKind.Field - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Field, - CodeAnalysis.NavigateTo.NavigateToItemKind.File - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.File, - CodeAnalysis.NavigateTo.NavigateToItemKind.Interface - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Interface, - CodeAnalysis.NavigateTo.NavigateToItemKind.Line - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Line, - CodeAnalysis.NavigateTo.NavigateToItemKind.Method - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Method, - CodeAnalysis.NavigateTo.NavigateToItemKind.Module - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Module, - CodeAnalysis.NavigateTo.NavigateToItemKind.OtherSymbol - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.OtherSymbol, - CodeAnalysis.NavigateTo.NavigateToItemKind.Property - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Property, - // VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind doesn't have a record, fall back to class. - // This should be updated whenever NavigateToItemKind has a record. - CodeAnalysis.NavigateTo.NavigateToItemKind.Record - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Class, - CodeAnalysis.NavigateTo.NavigateToItemKind.Structure - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Structure, - _ => throw ExceptionUtilities.UnexpectedValue(kind) - }; - private static PatternMatchKind GetPatternMatchKind(NavigateToMatchKind matchKind) => matchKind switch { diff --git a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs index 6cefc5d98fca8..bd823f0af90ba 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Threading; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Peek; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Navigation; @@ -14,6 +13,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Peek @@ -23,18 +23,18 @@ internal sealed class PeekableItemSource : IPeekableItemSource private readonly ITextBuffer _textBuffer; private readonly IPeekableItemFactory _peekableItemFactory; private readonly IPeekResultFactory _peekResultFactory; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; public PeekableItemSource( ITextBuffer textBuffer, IPeekableItemFactory peekableItemFactory, IPeekResultFactory peekResultFactory, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uiThreadOperationExecutor) { _textBuffer = textBuffer; _peekableItemFactory = peekableItemFactory; _peekResultFactory = peekResultFactory; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public void AugmentPeekSession(IPeekSession session, IList peekableItems) @@ -56,9 +56,9 @@ public void AugmentPeekSession(IPeekSession session, IList peekab return; } - _waitIndicator.Wait(EditorFeaturesResources.Peek, EditorFeaturesResources.Loading_Peek_information, allowCancel: true, action: context => + _uiThreadOperationExecutor.Execute(EditorFeaturesResources.Peek, EditorFeaturesResources.Loading_Peek_information, allowCancellation: true, showProgress: false, action: context => { - var cancellationToken = context.CancellationToken; + var cancellationToken = context.UserCancellationToken; IEnumerable results; @@ -79,7 +79,7 @@ public void AugmentPeekSession(IPeekSession session, IList peekab } else { - var semanticModel = document.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken); + var semanticModel = document.GetRequiredSemanticModelAsync(cancellationToken).AsTask().WaitAndGetResult(cancellationToken); var symbol = SymbolFinder.GetSemanticInfoAtPositionAsync( semanticModel, triggerPoint.Value.Position, diff --git a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs index 74be4c0722ea1..dca8d5dd1efb9 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs @@ -24,21 +24,21 @@ internal sealed class PeekableItemSourceProvider : IPeekableItemSourceProvider { private readonly IPeekableItemFactory _peekableItemFactory; private readonly IPeekResultFactory _peekResultFactory; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public PeekableItemSourceProvider( IPeekableItemFactory peekableItemFactory, IPeekResultFactory peekResultFactory, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uiThreadOperationExecutor) { _peekableItemFactory = peekableItemFactory; _peekResultFactory = peekResultFactory; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public IPeekableItemSource TryCreatePeekableItemSource(ITextBuffer textBuffer) - => textBuffer.Properties.GetOrCreateSingletonProperty(() => new PeekableItemSource(textBuffer, _peekableItemFactory, _peekResultFactory, _waitIndicator)); + => textBuffer.Properties.GetOrCreateSingletonProperty(() => new PeekableItemSource(textBuffer, _peekableItemFactory, _peekResultFactory, _uiThreadOperationExecutor)); } } diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs index be92e32cd5915..e79e7b06eb3f8 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs @@ -9,6 +9,7 @@ using System.Windows; using System.Windows.Controls; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; namespace Microsoft.CodeAnalysis.Editor.QuickInfo { @@ -50,26 +51,40 @@ public static void AttachTo(FrameworkElement element, IThreadingContext threadin private void OnToolTipOpening(object sender, ToolTipEventArgs e) { - AssertIsForeground(); + try + { + AssertIsForeground(); - Debug.Assert(_element.ToolTip == this); - Debug.Assert(_disposableToolTip == null); + Debug.Assert(_element.ToolTip == this); + Debug.Assert(_disposableToolTip == null); - _disposableToolTip = _createToolTip(); - _element.ToolTip = _disposableToolTip.ToolTip; + _disposableToolTip = _createToolTip(); + _element.ToolTip = _disposableToolTip.ToolTip; + } + catch (Exception ex) when (FatalError.ReportAndCatch(ex)) + { + // Do nothing, since this is a WPF event handler and propagating the exception would cause a crash + } } private void OnToolTipClosing(object sender, ToolTipEventArgs e) { - AssertIsForeground(); + try + { + AssertIsForeground(); - Debug.Assert(_disposableToolTip != null); - Debug.Assert(_element.ToolTip == _disposableToolTip.ToolTip); + Debug.Assert(_disposableToolTip != null); + Debug.Assert(_element.ToolTip == _disposableToolTip.ToolTip); - _element.ToolTip = this; + _element.ToolTip = this; - _disposableToolTip.Dispose(); - _disposableToolTip = null; + _disposableToolTip.Dispose(); + _disposableToolTip = null; + } + catch (Exception ex) when (FatalError.ReportAndCatch(ex)) + { + // Do nothing, since this is a WPF event handler and propagating the exception would cause a crash + } } } } diff --git a/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs index ba7621a54ed8c..7d8c163b424b1 100644 --- a/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Structure/StructureTaggerProvider.cs @@ -32,12 +32,11 @@ internal class StructureTaggerProvider : [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public StructureTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, IEditorOptionsFactoryService editorOptionsFactoryService, IProjectionBufferFactoryService projectionBufferFactoryService, ITextEditorFactoryService textEditorFactoryService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, notificationService, editorOptionsFactoryService, projectionBufferFactoryService, listenerProvider) + : base(threadingContext, editorOptionsFactoryService, projectionBufferFactoryService, listenerProvider) { _textEditorFactoryService = textEditorFactoryService; } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs index 73ad122401b91..6a3104f5f8724 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Core.Imaging; using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; @@ -95,10 +96,13 @@ protected Task> GetPreviewOperationsAsync(Ca public void Invoke(CancellationToken cancellationToken) { - SourceProvider.WaitIndicator.Wait(CodeAction.Title, CodeAction.Message, allowCancel: true, showProgress: true, action: waitContext => + SourceProvider.UIThreadOperationExecutor.Execute(CodeAction.Title, CodeAction.Message, allowCancellation: true, showProgress: true, action: context => { - using var combinedCancellationToken = cancellationToken.CombineWith(waitContext.CancellationToken); - Invoke(waitContext.ProgressTracker, combinedCancellationToken.Token); + // If we want to report progress, we need to create a scope inside the context -- the main context itself doesn't have a way + // to report progress. We pass the same allow cancellation and message so otherwise nothing changes. + using var scope = context.AddScope(allowCancellation: true, CodeAction.Message); + using var combinedCancellationToken = cancellationToken.CombineWith(context.UserCancellationToken); + Invoke(new UIThreadOperationContextProgressTracker(scope), combinedCancellationToken.Token); }); } @@ -262,11 +266,16 @@ ImageMoniker ISuggestedAction.IconMoniker var tags = CodeAction.Tags; if (tags.Length > 0) { - foreach (var service in SourceProvider.ImageMonikerServices) + foreach (var service in SourceProvider.ImageIdServices) { - if (service.Value.TryGetImageMoniker(tags, out var moniker) && !moniker.Equals(default(ImageMoniker))) + if (service.Value.TryGetImageId(tags, out var imageId) && !imageId.Equals(default(ImageId))) { - return moniker; + // Not using the extension method because it's not available in Cocoa + return new ImageMoniker + { + Guid = imageId.Guid, + Id = imageId.Id + }; } } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.State.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.State.cs new file mode 100644 index 0000000000000..d8b85311b3a18 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.State.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions +{ + internal partial class SuggestedActionsSourceProvider + { + private partial class SuggestedActionsSource + { + private sealed class State : IDisposable + { + private readonly SuggestedActionsSource _source; + + public readonly SuggestedActionsSourceProvider Owner; + public readonly ITextView TextView; + public readonly ITextBuffer SubjectBuffer; + public readonly WorkspaceRegistration Registration; + + // mutable state + public Workspace? Workspace { get; set; } + public int LastSolutionVersionReported; + + public State(SuggestedActionsSource source, SuggestedActionsSourceProvider owner, ITextView textView, ITextBuffer textBuffer) + { + _source = source; + + Owner = owner; + TextView = textView; + SubjectBuffer = textBuffer; + Registration = Workspace.GetWorkspaceRegistration(textBuffer.AsTextContainer()); + LastSolutionVersionReported = InvalidSolutionVersion; + } + + void IDisposable.Dispose() + { + if (Owner != null) + { + var updateSource = (IDiagnosticUpdateSource)Owner._diagnosticService; + updateSource.DiagnosticsUpdated -= _source.OnDiagnosticsUpdated; + } + + if (Workspace != null) + { + Workspace.Services.GetRequiredService().StatusChanged -= _source.OnWorkspaceStatusChanged; + Workspace.DocumentActiveContextChanged -= _source.OnActiveContextChanged; + } + + if (Registration != null) + Registration.WorkspaceChanged -= _source.OnWorkspaceChanged; + + if (TextView != null) + TextView.Closed -= _source.OnTextViewClosed; + } + } + } + } +} diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 876d0bc7f7c98..0c2ba98de4b62 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -31,20 +31,11 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions { internal partial class SuggestedActionsSourceProvider { - private class SuggestedActionsSource : ForegroundThreadAffinitizedObject, ISuggestedActionsSource3 + private partial class SuggestedActionsSource : ForegroundThreadAffinitizedObject, ISuggestedActionsSource3 { private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - // state that will be only reset when source is disposed. - private SuggestedActionsSourceProvider _owner; - private ITextView _textView; - private ITextBuffer _subjectBuffer; - private WorkspaceRegistration _registration; - - // mutable state - private Workspace? _workspace; - private IWorkspaceStatusService? _workspaceStatusService; - private int _lastSolutionVersionReported; + private readonly ReferenceCountedDisposable _state; public event EventHandler? SuggestedActionsChanged; @@ -56,71 +47,41 @@ public SuggestedActionsSource( ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry) : base(threadingContext) { - _owner = owner; - _textView = textView; - _textView.Closed += OnTextViewClosed; - _subjectBuffer = textBuffer; _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; - _registration = Workspace.GetWorkspaceRegistration(textBuffer.AsTextContainer()); + _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + + _state.Target.TextView.Closed += OnTextViewClosed; - _lastSolutionVersionReported = InvalidSolutionVersion; - var updateSource = (IDiagnosticUpdateSource)_owner._diagnosticService; + var updateSource = (IDiagnosticUpdateSource)owner._diagnosticService; updateSource.DiagnosticsUpdated += OnDiagnosticsUpdated; - RegisterEventsToWorkspace(_registration.Workspace); + RegisterEventsToWorkspace(_state, _state.Target.Registration.Workspace); - _registration.WorkspaceChanged += OnWorkspaceChanged; + _state.Target.Registration.WorkspaceChanged += OnWorkspaceChanged; } public void Dispose() { - if (_owner != null) - { - var updateSource = (IDiagnosticUpdateSource)_owner._diagnosticService; - updateSource.DiagnosticsUpdated -= OnDiagnosticsUpdated; - } - - if (_workspaceStatusService != null) - { - _workspaceStatusService.StatusChanged -= OnWorkspaceStatusChanged; - } - - if (_workspace != null) - { - _workspace.DocumentActiveContextChanged -= OnActiveContextChanged; - } - - if (_registration != null) - { - _registration.WorkspaceChanged -= OnWorkspaceChanged; - } - - if (_textView != null) - { - _textView.Closed -= OnTextViewClosed; - } - - _owner = null!; - _workspace = null; - _workspaceStatusService = null; - _registration = null!; - _textView = null!; - _subjectBuffer = null!; + _state.Dispose(); } - private bool IsDisposed => _subjectBuffer == null; - public bool TryGetTelemetryId(out Guid telemetryId) { telemetryId = default; - var workspace = _workspace; - if (workspace == null || _subjectBuffer == null) + using var state = _state.TryAddReference(); + if (state is null) + { + return false; + } + + var workspace = state.Target.Workspace; + if (workspace == null) { return false; } - var documentId = workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); + var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); if (documentId == null) { return false; @@ -174,18 +135,18 @@ public bool TryGetTelemetryId(out Guid telemetryId) { AssertIsForeground(); - if (IsDisposed) - { + using var state = _state.TryAddReference(); + if (state is null) return null; - } - if (_workspaceStatusService != null) + if (state.Target.Workspace == null) + return null; + + using (operationContext?.AddScope(allowCancellation: true, description: EditorFeaturesResources.Gathering_Suggestions_Waiting_for_the_solution_to_fully_load)) { - using (operationContext?.AddScope(allowCancellation: true, description: EditorFeaturesResources.Gathering_Suggestions_Waiting_for_the_solution_to_fully_load)) - { - // This needs to run under threading context otherwise, we can deadlock on VS - ThreadingContext.JoinableTaskFactory.Run(() => _workspaceStatusService.WaitUntilFullyLoadedAsync(cancellationToken)); - } + // This needs to run under threading context otherwise, we can deadlock on VS + var statusService = state.Target.Workspace.Services.GetRequiredService(); + ThreadingContext.JoinableTaskFactory.Run(() => statusService.WaitUntilFullyLoadedAsync(cancellationToken)); } using (Logger.LogBlock(FunctionId.SuggestedActions_GetSuggestedActions, cancellationToken)) @@ -201,7 +162,7 @@ public bool TryGetTelemetryId(out Guid telemetryId) var workspace = document.Project.Solution.Workspace; var supportsFeatureService = workspace.Services.GetRequiredService(); - var selection = TryGetCodeRefactoringSelection(range); + var selection = TryGetCodeRefactoringSelection(state, range); Func addOperationScope = description => operationContext?.AddScope(allowCancellation: true, string.Format(EditorFeaturesResources.Gathering_Suggestions_0, description)); @@ -209,24 +170,19 @@ public bool TryGetTelemetryId(out Guid telemetryId) // We convert the code fixes and refactorings to UnifiedSuggestedActionSets instead of // SuggestedActionSets so that we can share logic between local Roslyn and LSP. var fixes = GetCodeFixes( - supportsFeatureService, requestedActionCategories, workspace, + state, supportsFeatureService, requestedActionCategories, workspace, document, range, addOperationScope, cancellationToken); var refactorings = GetRefactorings( - supportsFeatureService, requestedActionCategories, workspace, + state, supportsFeatureService, requestedActionCategories, workspace, document, selection, addOperationScope, cancellationToken); var filteredSets = UnifiedSuggestedActionsSource.FilterAndOrderActionSets(fixes, refactorings, selection); - if (!filteredSets.HasValue) - { - return null; - } - - return filteredSets.Value.Select(s => ConvertToSuggestedActionSet(s)).WhereNotNull(); + return filteredSets.SelectAsArray(s => ConvertToSuggestedActionSet(s, state.Target.Owner, state.Target.SubjectBuffer)).WhereNotNull(); } } [return: NotNullIfNotNull("unifiedSuggestedActionSet")] - private SuggestedActionSet? ConvertToSuggestedActionSet(UnifiedSuggestedActionSet? unifiedSuggestedActionSet) + private SuggestedActionSet? ConvertToSuggestedActionSet(UnifiedSuggestedActionSet? unifiedSuggestedActionSet, SuggestedActionsSourceProvider owner, ITextBuffer subjectBuffer) { // May be null in cases involving CodeFixSuggestedActions since FixAllFlavors may be null. if (unifiedSuggestedActionSet == null) @@ -252,19 +208,19 @@ ISuggestedAction ConvertToSuggestedAction(IUnifiedSuggestedAction unifiedSuggest => unifiedSuggestedAction switch { UnifiedCodeFixSuggestedAction codeFixAction => new CodeFixSuggestedAction( - ThreadingContext, _owner, codeFixAction.Workspace, _subjectBuffer, + ThreadingContext, owner, codeFixAction.Workspace, subjectBuffer, codeFixAction.CodeFix, codeFixAction.Provider, codeFixAction.OriginalCodeAction, - ConvertToSuggestedActionSet(codeFixAction.FixAllFlavors)), + ConvertToSuggestedActionSet(codeFixAction.FixAllFlavors, owner, subjectBuffer)), UnifiedCodeRefactoringSuggestedAction codeRefactoringAction => new CodeRefactoringSuggestedAction( - ThreadingContext, _owner, codeRefactoringAction.Workspace, _subjectBuffer, + ThreadingContext, owner, codeRefactoringAction.Workspace, subjectBuffer, codeRefactoringAction.CodeRefactoringProvider, codeRefactoringAction.OriginalCodeAction), UnifiedFixAllSuggestedAction fixAllAction => new FixAllSuggestedAction( - ThreadingContext, _owner, fixAllAction.Workspace, _subjectBuffer, + ThreadingContext, owner, fixAllAction.Workspace, subjectBuffer, fixAllAction.FixAllState, fixAllAction.Diagnostic, fixAllAction.OriginalCodeAction), UnifiedSuggestedActionWithNestedActions nestedAction => new SuggestedActionWithNestedActions( - ThreadingContext, _owner, nestedAction.Workspace, _subjectBuffer, + ThreadingContext, owner, nestedAction.Workspace, subjectBuffer, nestedAction.Provider ?? this, nestedAction.OriginalCodeAction, - nestedAction.NestedActionSets.SelectAsArray(s => ConvertToSuggestedActionSet(s))), + nestedAction.NestedActionSets.SelectAsArray((s, arg) => ConvertToSuggestedActionSet(s, arg.owner, arg.subjectBuffer), (owner, subjectBuffer))), _ => throw ExceptionUtilities.Unreachable }; @@ -280,6 +236,7 @@ static SuggestedActionSetPriority ConvertToSuggestedActionSetPriority(UnifiedSug } private ImmutableArray GetCodeFixes( + ReferenceCountedDisposable state, ITextBufferSupportsFeatureService supportsFeatureService, ISuggestedActionCategorySet requestedActionCategories, Workspace workspace, @@ -290,20 +247,16 @@ private ImmutableArray GetCodeFixes( { this.AssertIsForeground(); - if (_owner._codeFixService == null || - !supportsFeatureService.SupportsCodeFixes(_subjectBuffer) || + if (state.Target.Owner._codeFixService == null || + !supportsFeatureService.SupportsCodeFixes(state.Target.SubjectBuffer) || !requestedActionCategories.Contains(PredefinedSuggestedActionCategoryNames.CodeFix)) { return ImmutableArray.Empty; } - // Make sure we include the suppression fixes even when the light bulb is only asking for only code fixes. - // See https://github.com/dotnet/roslyn/issues/29589 - const bool includeSuppressionFixes = true; - return UnifiedSuggestedActionsSource.GetFilterAndOrderCodeFixesAsync( - workspace, _owner._codeFixService, document, range.Span.ToTextSpan(), - includeSuppressionFixes, isBlocking: true, addOperationScope, cancellationToken).WaitAndGetResult(cancellationToken); + workspace, state.Target.Owner._codeFixService, document, range.Span.ToTextSpan(), + isBlocking: true, addOperationScope, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); } private static string GetFixCategory(DiagnosticSeverity severity) @@ -322,6 +275,7 @@ private static string GetFixCategory(DiagnosticSeverity severity) } private ImmutableArray GetRefactorings( + ReferenceCountedDisposable state, ITextBufferSupportsFeatureService supportsFeatureService, ISuggestedActionCategorySet requestedActionCategories, Workspace workspace, @@ -340,8 +294,8 @@ private ImmutableArray GetRefactorings( } if (!workspace.Options.GetOption(EditorComponentOnOffOptions.CodeRefactorings) || - _owner._codeRefactoringService == null || - !supportsFeatureService.SupportsRefactorings(_subjectBuffer)) + state.Target.Owner._codeRefactoringService == null || + !supportsFeatureService.SupportsRefactorings(state.Target.SubjectBuffer)) { return ImmutableArray.Empty; } @@ -351,7 +305,7 @@ private ImmutableArray GetRefactorings( var filterOutsideSelection = !requestedActionCategories.Contains(PredefinedSuggestedActionCategoryNames.Refactoring); return UnifiedSuggestedActionsSource.GetFilterAndOrderCodeRefactoringsAsync( - workspace, _owner._codeRefactoringService, document, selection.Value, isBlocking: true, + workspace, state.Target.Owner._codeRefactoringService, document, selection.Value, isBlocking: true, addOperationScope, filterOutsideSelection, cancellationToken).WaitAndGetResult(cancellationToken); } @@ -364,7 +318,7 @@ public Task HasSuggestedActionsAsync( throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); } - private async Task GetSpanAsync(SnapshotSpan range, CancellationToken cancellationToken) + private async Task GetSpanAsync(ReferenceCountedDisposable state, SnapshotSpan range, CancellationToken cancellationToken) { // First, ensure that the snapshot we're being asked about is for an actual // roslyn document. This can fail, for example, in projection scenarios where @@ -378,7 +332,7 @@ public Task HasSuggestedActionsAsync( // Also make sure the range is from the same buffer that this source was created for Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(_subjectBuffer), + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); // Next, before we do any async work, acquire the user's selection, directly grabbing @@ -408,36 +362,34 @@ public Task HasSuggestedActionsAsync( TextSpan? selection = null; if (IsForeground()) { - selection = TryGetCodeRefactoringSelection(range); + selection = TryGetCodeRefactoringSelection(state, range); } else { await InvokeBelowInputPriorityAsync(() => { - // Make sure we were not disposed between kicking off this work and getting - // to this point. - if (IsDisposed) - { + // Make sure we were not disposed between kicking off this work and getting to this point. + using var state = _state.TryAddReference(); + if (state is null) return; - } - selection = TryGetCodeRefactoringSelection(range); + selection = TryGetCodeRefactoringSelection(state, range); }, cancellationToken).ConfigureAwait(false); } return selection; } - private async Task GetFixLevelAsync( - SuggestedActionsSourceProvider provider, + private static async Task GetFixLevelAsync( + ReferenceCountedDisposable state, Document document, SnapshotSpan range, CancellationToken cancellationToken) { - if (provider._codeFixService != null && - _subjectBuffer.SupportsCodeFixes()) + if (state.Target.Owner._codeFixService != null && + state.Target.SubjectBuffer.SupportsCodeFixes()) { - var result = await provider._codeFixService.GetMostSevereFixableDiagnosticAsync( + var result = await state.Target.Owner._codeFixService.GetMostSevereFixableDiagnosticAsync( document, range.Span.ToTextSpan(), cancellationToken).ConfigureAwait(false); if (result.HasFix) @@ -449,7 +401,7 @@ await InvokeBelowInputPriorityAsync(() => if (result.PartialResult) { // reset solution version number so that we can raise suggested action changed event - Volatile.Write(ref _lastSolutionVersionReported, InvalidSolutionVersion); + Volatile.Write(ref state.Target.LastSolutionVersionReported, InvalidSolutionVersion); return null; } } @@ -458,11 +410,14 @@ await InvokeBelowInputPriorityAsync(() => } private async Task TryGetRefactoringSuggestedActionCategoryAsync( - SuggestedActionsSourceProvider provider, Document document, TextSpan? selection, CancellationToken cancellationToken) { + using var state = _state.TryAddReference(); + if (state is null) + return null; + if (!selection.HasValue) { // this is here to fail test and see why it is failed. @@ -471,10 +426,10 @@ await InvokeBelowInputPriorityAsync(() => } if (document.Project.Solution.Options.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && - provider._codeRefactoringService != null && - _subjectBuffer.SupportsRefactorings()) + state.Target.Owner._codeRefactoringService != null && + state.Target.SubjectBuffer.SupportsRefactorings()) { - if (await provider._codeRefactoringService.HasRefactoringsAsync( + if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( document, selection.Value, cancellationToken).ConfigureAwait(false)) { return PredefinedSuggestedActionCategoryNames.Refactoring; @@ -484,14 +439,13 @@ await InvokeBelowInputPriorityAsync(() => return null; } - private TextSpan? TryGetCodeRefactoringSelection(SnapshotSpan range) + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) { this.AssertIsForeground(); - Debug.Assert(!this.IsDisposed); - var selectedSpans = _textView.Selection.SelectedSpans - .SelectMany(ss => _textView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, _subjectBuffer)) - .Where(ss => !_textView.IsReadOnlyOnSurfaceBuffer(ss)) + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) .ToList(); // We only support refactorings when there is a single selection in the document. @@ -516,40 +470,36 @@ private void OnTextViewClosed(object sender, EventArgs e) private void OnWorkspaceChanged(object sender, EventArgs e) { + using var state = _state.TryAddReference(); + if (state is null) + return; + // REVIEW: this event should give both old and new workspace as argument so that // one doesn't need to hold onto workspace in field. // remove existing event registration - if (_workspaceStatusService != null) + if (state.Target.Workspace != null) { - _workspaceStatusService.StatusChanged -= OnWorkspaceStatusChanged; - } - - if (_workspace != null) - { - _workspace.DocumentActiveContextChanged -= OnActiveContextChanged; + state.Target.Workspace.Services.GetRequiredService().StatusChanged -= OnWorkspaceStatusChanged; + state.Target.Workspace.DocumentActiveContextChanged -= OnActiveContextChanged; } // REVIEW: why one need to get new workspace from registration? why not just pass in the new workspace? // add new event registration - RegisterEventsToWorkspace(_registration.Workspace); + RegisterEventsToWorkspace(state, state.Target.Registration.Workspace); } - private void RegisterEventsToWorkspace(Workspace? workspace) + private void RegisterEventsToWorkspace(ReferenceCountedDisposable state, Workspace? workspace) { - _workspace = workspace; + state.Target.Workspace = workspace; - if (_workspace == null) + if (state.Target.Workspace == null) { return; } - _workspace.DocumentActiveContextChanged += OnActiveContextChanged; - _workspaceStatusService = _workspace.Services.GetService(); - if (_workspaceStatusService != null) - { - _workspaceStatusService.StatusChanged += OnWorkspaceStatusChanged; - } + state.Target.Workspace.DocumentActiveContextChanged += OnActiveContextChanged; + state.Target.Workspace.Services.GetRequiredService().StatusChanged += OnWorkspaceStatusChanged; } private void OnActiveContextChanged(object sender, DocumentActiveContextChangedEventArgs e) @@ -571,7 +521,11 @@ private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e) private void OnWorkspaceStatusChanged(object sender, EventArgs args) { - var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); + using var state = _state.TryAddReference(); + if (state is null) + return; + + var document = state.Target.SubjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); if (document == null) { // document is already closed @@ -584,15 +538,11 @@ private void OnWorkspaceStatusChanged(object sender, EventArgs args) private void OnSuggestedActionsChanged(Workspace currentWorkspace, DocumentId? currentDocumentId, int solutionVersion) { - // Explicitly hold onto the _subjectBuffer field in a local and use this local in this function to avoid crashes - // if this field happens to be cleared by Dispose() below. This is required since this code path involves code - // that can run on background thread. - var buffer = _subjectBuffer; - if (buffer == null) - { + using var state = _state.TryAddReference(); + if (state is null) return; - } + var buffer = state.Target.SubjectBuffer; var workspace = buffer.GetWorkspace(); // workspace is not ready, nothing to do. @@ -602,56 +552,49 @@ private void OnSuggestedActionsChanged(Workspace currentWorkspace, DocumentId? c } if (currentDocumentId != workspace.GetDocumentIdInCurrentContext(buffer.AsTextContainer()) || - solutionVersion == Volatile.Read(ref _lastSolutionVersionReported)) + solutionVersion == Volatile.Read(ref state.Target.LastSolutionVersionReported)) { return; } this.SuggestedActionsChanged?.Invoke(this, EventArgs.Empty); - Volatile.Write(ref _lastSolutionVersionReported, solutionVersion); + Volatile.Write(ref state.Target.LastSolutionVersionReported, solutionVersion); } public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { - if (_workspaceStatusService != null && !await _workspaceStatusService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) - { - // never show light bulb if solution is not fully loaded yet + using var state = _state.TryAddReference(); + if (state is null) + return null; + + var workspace = state.Target.Workspace; + if (workspace == null) return null; - } - var provider = _owner; + // never show light bulb if solution is not fully loaded yet + if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) + return null; - // The _owner flag is cleared on Dispose. This should only occur when cancellation has already been - // requested, but at least in 16.8 Preview 3.0 it was not always the case. cancellationToken.ThrowIfCancellationRequested(); - //Assumes.Present(provider); - if (provider is null) - { - // The object was unexpectedly disposed while still in use (caller error) - return null; - } - using var asyncToken = provider.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); + using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); var document = range.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) - { return null; - } using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var linkedToken = linkedTokenSource.Token; - var errorTask = Task.Run( - () => GetFixLevelAsync(provider, document, range, linkedToken), linkedToken); + var errorTask = Task.Run(() => GetFixLevelAsync(state, document, range, linkedToken), linkedToken); - var selection = await GetSpanAsync(range, linkedToken).ConfigureAwait(false); + var selection = await GetSpanAsync(state, range, linkedToken).ConfigureAwait(false); var refactoringTask = SpecializedTasks.Null(); if (selection != null) { refactoringTask = Task.Run( - () => TryGetRefactoringSuggestedActionCategoryAsync(provider, document, selection, linkedToken), linkedToken); + () => TryGetRefactoringSuggestedActionCategoryAsync(document, selection, linkedToken), linkedToken); } // If we happen to get the result of the error task before the refactoring task, diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs index 761f12f734159..01c0a2bbe9517 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs @@ -19,6 +19,7 @@ using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions @@ -45,10 +46,10 @@ internal partial class SuggestedActionsSourceProvider : ISuggestedActionsSourceP public readonly ICodeActionEditHandlerService EditHandler; public readonly IAsynchronousOperationListener OperationListener; - public readonly IWaitIndicator WaitIndicator; + public readonly IUIThreadOperationExecutor UIThreadOperationExecutor; public readonly ImmutableArray> ActionCallbacks; - public readonly ImmutableArray> ImageMonikerServices; + public readonly ImmutableArray> ImageIdServices; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -58,10 +59,10 @@ public SuggestedActionsSourceProvider( IDiagnosticAnalyzerService diagnosticService, ICodeFixService codeFixService, ICodeActionEditHandlerService editHandler, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, IAsynchronousOperationListenerProvider listenerProvider, - [ImportMany] IEnumerable> imageMonikerServices, + [ImportMany] IEnumerable> imageIdServices, [ImportMany] IEnumerable> actionCallbacks) { _threadingContext = threadingContext; @@ -71,10 +72,10 @@ public SuggestedActionsSourceProvider( _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; ActionCallbacks = actionCallbacks.ToImmutableArray(); EditHandler = editHandler; - WaitIndicator = waitIndicator; + UIThreadOperationExecutor = uiThreadOperationExecutor; OperationListener = listenerProvider.GetListener(FeatureAttribute.LightBulb); - ImageMonikerServices = ExtensionOrderer.Order(imageMonikerServices).ToImmutableArray(); + ImageIdServices = ExtensionOrderer.Order(imageIdServices).ToImmutableArray(); } public ISuggestedActionsSource? CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) diff --git a/src/EditorFeatures/Core.Wpf/Tags/DefaultImageMonikerService.cs b/src/EditorFeatures/Core.Wpf/Tags/DefaultImageIdService.cs similarity index 62% rename from src/EditorFeatures/Core.Wpf/Tags/DefaultImageMonikerService.cs rename to src/EditorFeatures/Core.Wpf/Tags/DefaultImageIdService.cs index c6ab073d8680b..084e33a1f3a77 100644 --- a/src/EditorFeatures/Core.Wpf/Tags/DefaultImageMonikerService.cs +++ b/src/EditorFeatures/Core.Wpf/Tags/DefaultImageIdService.cs @@ -7,25 +7,24 @@ using System; using System.Collections.Immutable; using System.ComponentModel.Composition; -using Microsoft.CodeAnalysis.Editor.Wpf; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.Imaging; -using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Core.Imaging; namespace Microsoft.CodeAnalysis.Editor.Tags { - [ExportImageMonikerService(Name = Name)] - internal class DefaultImageMonikerService : IImageMonikerService + [ExportImageIdService(Name = Name)] + internal class DefaultImageIdService : IImageIdService { - public const string Name = nameof(DefaultImageMonikerService); + public const string Name = nameof(DefaultImageIdService); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultImageMonikerService() + public DefaultImageIdService() { } - public bool TryGetImageMoniker(ImmutableArray tags, out ImageMoniker imageMoniker) + public bool TryGetImageId(ImmutableArray tags, out ImageId imageId) { var glyph = tags.GetFirstGlyph(); @@ -38,8 +37,8 @@ public bool TryGetImageMoniker(ImmutableArray tags, out ImageMoniker ima break; } - imageMoniker = glyph.GetImageMoniker(); - return !imageMoniker.IsNullImage(); + imageId = glyph.GetImageId(); + return imageId != default; } } } diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf index 91c72a02780df..883e747b85a21 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Sestavování projektu Copying selection to Interactive Window. - Copying selection to Interactive Window. + Výběr se kopíruje do okna Interactive. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Výběr se spouští v okně Interactive. Interactive host process platform - Interactive host process platform + Interaktivní platforma procesů hostitelů Print a list of referenced assemblies. - Print a list of referenced assemblies. + Vytiskne seznam odkazovaných sestavení. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Obnoví původní stav spouštěcího prostředí, zachová historii. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Obnoví čisté prostředí (jenom s odkazem na mscorlib), nespustí inicializační skript. Resetting Interactive - Resetting Interactive + Znovu se nastavuje interaktivní stav. Resetting execution engine. - Resetting execution engine. + Resetuje se prováděcí modul. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + Vlastnost CurrentWindow se dá přiřadit jenom jednou. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + Příkaz references není v této implementaci okna Interactive podporovaný. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf index f8e81d2bcb068..7089c2188ed9b 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Projekt wird erstellt Copying selection to Interactive Window. - Copying selection to Interactive Window. + Die Auswahl wird in Interactive-Fenster kopiert. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Die Auswahl wird im Interactive-Fenster ausgeführt. Interactive host process platform - Interactive host process platform + Interaktive Hostprozessplattform Print a list of referenced assemblies. - Print a list of referenced assemblies. + Gibt eine Liste der Assemblys aus, auf die verwiesen wird. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Setzt die Ausführungsumgebung in den ursprünglichen Zustand zurück. Der Verlauf wird beibehalten. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Setzt auf eine saubere Umgebung zurück (nur Verweis auf "mscorlib") und führt das Initialisierungsskript nicht aus. Resetting Interactive - Resetting Interactive + Interactives Element wird zurückgesetzt Resetting execution engine. - Resetting execution engine. + Das Ausführungsmodul wird zurückgesetzt. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + Die Eigenschaft "CurrentWindow" kann nur ein Mal zugewiesen werden. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + Der Befehl "references" wird in dieser Implementierung von Interactive-Fenster nicht unterstützt. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf index 1737d10f2bea1..3fe4b1d314e55 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Generando proyecto Copying selection to Interactive Window. - Copying selection to Interactive Window. + Copiando selección en ventana interactiva. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Ejecutando selección en ventana interactiva. Interactive host process platform - Interactive host process platform + Plataforma de proceso de host interactiva Print a list of referenced assemblies. - Print a list of referenced assemblies. + Imprima una lista de ensamblados de referencia. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Restablecer el entorno de ejecución al estado inicial, conservar el historial. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Restablecer a un entorno limpio (referencia exclusiva a mscorlib), no ejecutar el script de inicialización. Resetting Interactive - Resetting Interactive + Restableciendo interactivo Resetting execution engine. - Resetting execution engine. + Restableciendo el motor de ejecución. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + La propiedad CurrentWindow solo se puede asignar una vez. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + No se admite el comando de referencias en esta implementación de ventana interactiva. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf index ae09f21dbfc2d..937a484f61f74 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Génération du projet Copying selection to Interactive Window. - Copying selection to Interactive Window. + Copie de la sélection dans la fenêtre interactive. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Exécution de la sélection dans la fenêtre interactive. Interactive host process platform - Interactive host process platform + Plateforme de processus hôte interactive Print a list of referenced assemblies. - Print a list of referenced assemblies. + Affichez la liste des assemblys référencés. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Rétablir l'état initial de l'environnement d'exécution, conserver l'historique. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Rétablir un environnement propre (seul mscorlib est référencé), ne pas exécuter de script d'initialisation. Resetting Interactive - Resetting Interactive + Réinitialisation interactive Resetting execution engine. - Resetting execution engine. + Réinitialisation du moteur d'exécution. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + La propriété CurrentWindow ne peut être assignée qu'une seule fois. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + La commande references n'est pas prise en charge dans cette implémentation de fenêtre interactive. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf index fedb5db83797a..8dd81055aba28 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Compilazione progetto Copying selection to Interactive Window. - Copying selection to Interactive Window. + Copia della selezione nella finestra interattiva. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Esecuzione della selezione nella finestra interattiva. Interactive host process platform - Interactive host process platform + Piattaforma interattiva per processi host Print a list of referenced assemblies. - Print a list of referenced assemblies. + Stampa un elenco di tutti gli assembly di riferimento. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Ripristina lo stato iniziale dell'ambiente di esecuzione, mantenendo la cronologia. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Esegue il ripristino a un ambiente pulito (solo con riferimenti a mscorlib), senza eseguire lo script di inizializzazione. Resetting Interactive - Resetting Interactive + Reimpostazione di Interactive Resetting execution engine. - Resetting execution engine. + Il motore di esecuzione verrà reimpostato. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + La proprietà CurrentWindow può essere assegnata una sola volta. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + Il comando references non è supportato in questa implementazione di Finestra interattiva. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf index 7383d2728b5ba..c8311add9640b 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + プロジェクトのビルド Copying selection to Interactive Window. - Copying selection to Interactive Window. + インタラクティブ ウィンドウに選択内容をコピーしています。 @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + インタラクティブ ウィンドウで選択を実行します。 Interactive host process platform - Interactive host process platform + 対話型ホスト プロセス プラットフォーム Print a list of referenced assemblies. - Print a list of referenced assemblies. + 参照アセンブリの一覧を印刷します。 @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + 実行環境を初期状態にリセットし、履歴を保持します。 Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + クリーンな環境 (mscorlib のみを参照) にリセットし、初期化スクリプトは実行しません。 Resetting Interactive - Resetting Interactive + 対話をリセットしています Resetting execution engine. - Resetting execution engine. + 実行エンジンをリセットしています。 The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + CurrentWindow プロパティは 1 回のみ割り当てることができます。 The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + このインタラクティブ ウィンドウの実装で、参照コマンドはサポートされていません。 diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf index 64e576357f119..bf6784a067043 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + 프로젝트 빌드 중 Copying selection to Interactive Window. - Copying selection to Interactive Window. + 선택 영역을 대화형 창으로 복사하는 중입니다. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + 선택 영역을 대화형 창에서 실행하는 중입니다. Interactive host process platform - Interactive host process platform + 대화형 호스트 프로세스 플랫폼 Print a list of referenced assemblies. - Print a list of referenced assemblies. + 참조된 어셈블리의 목록을 인쇄합니다. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + 실행 환경을 초기 상태로 다시 설정하고 기록을 보관합니다. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + 정리된 환경(mscorlib만 참조됨)으로 다시 설정하고 초기화 스크립트를 실행하지 않습니다. Resetting Interactive - Resetting Interactive + 대화형을 다시 설정하는 중 Resetting execution engine. - Resetting execution engine. + 실행 엔진을 다시 설정하는 중입니다. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + CurrentWindow 속성은 한 번만 할당될 수 있습니다. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + 참조 명령은 이 대화형 창 구현에서 지원되지 않습니다. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf index 1b418bf47d74b..7e4cfbaf15479 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Trwa kompilowanie projektu Copying selection to Interactive Window. - Copying selection to Interactive Window. + Kopiowanie zaznaczenia do okna Interactive. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Wykonywanie zaznaczenia w oknie Interactive. Interactive host process platform - Interactive host process platform + Platforma procesu hosta interaktywnego Print a list of referenced assemblies. - Print a list of referenced assemblies. + Wydrukuj listę przywoływanych zestawów. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Przywróć początkowy stan środowiska wykonawczego, zachowując historię. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Przywróć czyste środowisko (tylko z odwołaniem do elementu mscorlib), nie uruchamiaj skryptu inicjowania. Resetting Interactive - Resetting Interactive + Resetowanie trybu interaktywnego Resetting execution engine. - Resetting execution engine. + Resetowanie aparatu wykonywania. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + Właściwość CurrentWindow można przypisać tylko raz. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + Polecenie references nie jest obsługiwane w tej implementacji okna Interactive. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf index 889e6d6bce5f2..1644ea163f484 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Projeto de Compilação Copying selection to Interactive Window. - Copying selection to Interactive Window. + Copiando seleção para a Janela Interativa. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Executando seleção na Janela Interativa. Interactive host process platform - Interactive host process platform + Plataforma interativa de processo de host Print a list of referenced assemblies. - Print a list of referenced assemblies. + Imprima uma lista de assemblies referenciados. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Redefina o ambiente de execução para o estado inicial e mantenha o histórico. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Redefina para um ambiente limpo (apenas mscorlib referenciado) e não execute o script de inicialização. Resetting Interactive - Resetting Interactive + Redefinindo Interativo Resetting execution engine. - Resetting execution engine. + Redefinindo o mecanismo de execução. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + A propriedade CurrentWindow deve ser atribuído apenas uma vez. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + O comando de referências não tem suporte nessa implementação de Janela Interativa. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf index fb3b587951dee..e84642a5f9850 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Сборка проекта Copying selection to Interactive Window. - Copying selection to Interactive Window. + Копирование выделенного фрагмента в интерактивное окно. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Выполнение выделенного фрагмента в Интерактивном окне. Interactive host process platform - Interactive host process platform + Интерактивная платформа хост-процесса Print a list of referenced assemblies. - Print a list of referenced assemblies. + Печать списка сборок, на которые указывают ссылки. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Сброс окружения выполнения до первоначального состояния с сохранением журнала. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Сброс до состояния чистого окружения (есть только ссылка на mscorlib) без запуска скрипта инициализации. Resetting Interactive - Resetting Interactive + Сброс интерактивного режима Resetting execution engine. - Resetting execution engine. + Идет сброс подсистемы выполнения. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + Свойство CurrentWindow может быть назначено только один раз. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + Команда, на которую указывает ссылка, не поддерживается в этой реализации Интерактивного окна. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf index 23c20126ebed1..5a565b5a9c150 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + Proje Oluşturuluyor Copying selection to Interactive Window. - Copying selection to Interactive Window. + Seçim, Etkileşimli Pencere'ye kopyalanıyor. @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + Seçim, Etkileşimli Pencere'de yürütülüyor. Interactive host process platform - Interactive host process platform + Etkileşimli ana işlem platformu Print a list of referenced assemblies. - Print a list of referenced assemblies. + Başvurulan bütünleştirilmiş kodların listesini yazdırın. @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + Yürütme ortamını ilk durumuna sıfırlayın, geçmişi saklayın. Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + Temiz bir ortama sıfırlayın (yalnızca mscorlib öğesine başvurulur), başlatma betiğini çalıştırmayın. Resetting Interactive - Resetting Interactive + Etkileşimli Sıfırlama Resetting execution engine. - Resetting execution engine. + Yürütme altyapısı sıfırlanıyor. The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + CurrentWindow özelliği yalnızca bir kez atanabilir. The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + Başvurular komutu bu Etkileşimli Pencere uygulamasında desteklenmiyor. diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf index 17bc080fd4c95..f62a22adbab9c 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + 正在生成项目 Copying selection to Interactive Window. - Copying selection to Interactive Window. + 将所选内容复制到交互窗口。 @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + 在交互窗口中执行所选内容。 Interactive host process platform - Interactive host process platform + 交互式主机进程平台 Print a list of referenced assemblies. - Print a list of referenced assemblies. + 打印引用程序集的列表。 @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + 将执行环境重置为初始状态,保留历史记录。 Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + 重置为干净环境(仅引用 mscorlib),不要运行初始化脚本。 Resetting Interactive - Resetting Interactive + 正在重置交互 Resetting execution engine. - Resetting execution engine. + 正在重置执行引擎。 The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + 只能对 CurrentWindow 属性进行一次赋值。 The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + 此交互窗口实现中不支持引用命令。 diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf index 46c234a96d311..51f6642b34442 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf @@ -1,15 +1,15 @@ - + Building Project - Building Project + 正在建置專案 Copying selection to Interactive Window. - Copying selection to Interactive Window. + 正在將選取範圍複製到互動視窗。 @@ -19,17 +19,17 @@ Executing selection in Interactive Window. - Executing selection in Interactive Window. + 正在於互動視窗中執行選取範圍。 Interactive host process platform - Interactive host process platform + 互動式主機處理序平台 Print a list of referenced assemblies. - Print a list of referenced assemblies. + 列印參考組件的清單。 @@ -79,32 +79,32 @@ Reset the execution environment to the initial state, keep history. - Reset the execution environment to the initial state, keep history. + 將執行環境重設為初始狀態,但保留記錄。 Reset to a clean environment (only mscorlib referenced), do not run initialization script. - Reset to a clean environment (only mscorlib referenced), do not run initialization script. + 重設為初始環境 (僅參考 mscorlib),不要執行初始化指令碼。 Resetting Interactive - Resetting Interactive + 正在重設互動 Resetting execution engine. - Resetting execution engine. + 正在重設執行引擎。 The CurrentWindow property may only be assigned once. - The CurrentWindow property may only be assigned once. + CurrentWindow 屬性只能受指派一次。 The references command is not supported in this Interactive Window implementation. - The references command is not supported in this Interactive Window implementation. + 在此互動視窗實作中不支援參考命令。 diff --git a/src/EditorFeatures/Core/CommandHandlers/AbstractGoToCommandHandler.cs b/src/EditorFeatures/Core/CommandHandlers/AbstractGoToCommandHandler.cs index d961bdbe78da2..4ad732394b8dc 100644 --- a/src/EditorFeatures/Core/CommandHandlers/AbstractGoToCommandHandler.cs +++ b/src/EditorFeatures/Core/CommandHandlers/AbstractGoToCommandHandler.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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.FindUsages; @@ -14,7 +12,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Notification; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text.Editor.Commanding; @@ -39,36 +36,38 @@ public AbstractGoToCommandHandler( public abstract string DisplayName { get; } protected abstract string ScopeDescription { get; } protected abstract FunctionId FunctionId { get; } - protected abstract Task FindActionAsync(TLanguageService service, Document document, int caretPosition, IFindUsagesContext context); + protected abstract Task FindActionAsync(TLanguageService service, Document document, int caretPosition, IFindUsagesContext context, CancellationToken cancellationToken); public CommandState GetCommandState(TCommandArgs args) { // Because this is expensive to compute, we just always say yes as long as the language allows it. var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - var findUsagesService = document?.GetLanguageService(); + var findUsagesService = GetService(document); return findUsagesService != null ? CommandState.Available : CommandState.Unspecified; } + protected abstract TLanguageService? GetService(Document? document); + public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context) { using (context.OperationContext.AddScope(allowCancellation: true, ScopeDescription)) { - var caret = args.TextView.GetCaretPoint(args.SubjectBuffer); + var subjectBuffer = args.SubjectBuffer; + var caret = args.TextView.GetCaretPoint(subjectBuffer); if (!caret.HasValue) return false; - var subjectBuffer = args.SubjectBuffer; - if (!subjectBuffer.TryGetWorkspace(out var workspace)) + var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) return false; - var service = workspace.Services.GetLanguageServices(args.SubjectBuffer)?.GetService(); + var service = GetService(document); if (service == null) return false; - var document = subjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChanges( - context.OperationContext, _threadingContext); + document = subjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChanges(context.OperationContext, _threadingContext); if (document == null) return false; @@ -78,14 +77,15 @@ public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context) } private void ExecuteCommand( - Document document, int caretPosition, + Document document, + int caretPosition, TLanguageService service, CommandExecutionContext context) { if (service != null) { // We have all the cheap stuff, so let's do expensive stuff now - string messageToShow = null; + string? messageToShow = null; var userCancellationToken = context.OperationContext.UserCancellationToken; using (Logger.LogBlock(FunctionId, KeyValueLogMessage.Create(LogType.UserAction), userCancellationToken)) @@ -100,7 +100,7 @@ private void ExecuteCommand( // wait context. That means the command system won't attempt to show its own wait dialog // and also will take it into consideration when measuring command handling duration. context.OperationContext.TakeOwnership(); - var notificationService = document.Project.Solution.Workspace.Services.GetService(); + var notificationService = document.Project.Solution.Workspace.Services.GetRequiredService(); notificationService.SendNotification( message: messageToShow, title: DisplayName, @@ -109,7 +109,7 @@ private void ExecuteCommand( } } - private async Task NavigateToOrPresentResultsAsync( + private async Task NavigateToOrPresentResultsAsync( Document document, int caretPosition, TLanguageService service, @@ -119,9 +119,9 @@ private async Task NavigateToOrPresentResultsAsync( // the individual TLanguageService. Once we get the results back we'll then decide // what to do with them. If we get only a single result back, then we'll just go // directly to it. Otherwise, we'll present the results in the IStreamingFindUsagesPresenter. - var context = new SimpleFindUsagesContext(cancellationToken); + var context = new SimpleFindUsagesContext(); - await FindActionAsync(service, document, caretPosition, context).ConfigureAwait(false); + await FindActionAsync(service, document, caretPosition, context, cancellationToken).ConfigureAwait(false); if (context.Message != null) return context.Message; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs index 98cef0b4f22be..3c925b1e1c9bf 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/AnalyzerSetting.cs @@ -51,7 +51,7 @@ internal void ChangeSeverity(DiagnosticSeverity severity) return; Severity = severity; - _ = _settingsUpdater.QueueUpdateAsync(this, severity); + _settingsUpdater.QueueUpdate(this, severity); } } } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs index 907245d073918..66d5d9392db3b 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.BooleanCodeStyleSetting.cs @@ -35,17 +35,15 @@ public BooleanCodeStyleSetting(Option2> option, protected override void ChangeSeverity(NotificationOption2 severity) { - var option = GetOption(); - option.Notification = severity; - _ = Updater.QueueUpdateAsync(_option, option); + ICodeStyleOption option = GetOption(); + Updater.QueueUpdate(_option, option.WithNotification(severity)); } public override void ChangeValue(int valueIndex) { var value = valueIndex == 0; - var option = GetOption(); - option.Value = value; - _ = Updater.QueueUpdateAsync(_option, option); + ICodeStyleOption option = GetOption(); + Updater.QueueUpdate(_option, option.WithValue(value)); } protected override CodeStyleOption2 GetOption() diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs index 8506d092f0315..a9888a218e2af 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.EnumCodeStyleSetting.cs @@ -37,16 +37,14 @@ public EnumCodeStyleSetting(Option2> option, protected override void ChangeSeverity(NotificationOption2 severity) { - var option = GetOption(); - option.Notification = severity; - _ = Updater.QueueUpdateAsync(_option, option); + ICodeStyleOption option = GetOption(); + Updater.QueueUpdate(_option, option.WithNotification(severity)); } public override void ChangeValue(int valueIndex) { - var option = GetOption(); - option.Value = _enumValues[valueIndex]; - _ = Updater.QueueUpdateAsync(_option, option); + ICodeStyleOption option = GetOption(); + Updater.QueueUpdate(_option, option.WithValue(_enumValues[valueIndex])); } protected override CodeStyleOption2 GetOption() diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs index b71d06986e1a5..878798c60e44c 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageBooleanCodeStyleSetting.cs @@ -35,17 +35,15 @@ public PerLanguageBooleanCodeStyleSetting(PerLanguageOption2 GetOption() diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs index df33ae0c833e6..c007ff27961de 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/CodeStyle/CodeStyleSetting.PerLanguageEnumCodeStyleSetting.cs @@ -37,16 +37,14 @@ public PerLanguageEnumCodeStyleSetting(PerLanguageOption2> o protected override void ChangeSeverity(NotificationOption2 severity) { - var option = GetOption(); - option.Notification = severity; - _ = Updater.QueueUpdateAsync(_option, option); + ICodeStyleOption option = GetOption(); + Updater.QueueUpdate(_option, option.WithNotification(severity)); } public override void ChangeValue(int valueIndex) { - var option = GetOption(); - option.Value = _enumValues[valueIndex]; - _ = Updater.QueueUpdateAsync(_option, option); + ICodeStyleOption option = GetOption(); + Updater.QueueUpdate(_option, option.WithValue(_enumValues[valueIndex])); } protected override CodeStyleOption2 GetOption() diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs index 0fcfecad99b36..d701147cc4dd7 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/FormattingSetting`1.cs @@ -68,7 +68,7 @@ public FormattingSetting(Option2 option, public override void SetValue(object value) { Value = (T)value; - _ = Updater.QueueUpdateAsync(_option, value); + Updater.QueueUpdate(_option, value); } public override object? GetValue() => Value; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs index aebf191e07ba2..0e61736243e49 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Data/Formatting/PerLanguageFormattingSetting.cs @@ -68,7 +68,7 @@ public PerLanguageFormattingSetting(PerLanguageOption2 option, public override void SetValue(object value) { Value = (T)value; - _ = Updater.QueueUpdateAsync(_option, value); + Updater.QueueUpdate(_option, value); } public override object? GetValue() => Value; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs index c39d19813506b..f852cc936043b 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs @@ -44,7 +44,13 @@ protected void Update() var givenFolder = new DirectoryInfo(FileName).Parent; var solution = Workspace.CurrentSolution; var projects = solution.GetProjectsForPath(FileName); - var project = projects.First(); + var project = projects.FirstOrDefault(); + if (project is null) + { + // no .NET projects in the solution + return; + } + var configOptionsProvider = new WorkspaceAnalyzerConfigOptionsProvider(project.State); var workspaceOptions = configOptionsProvider.GetOptionsForSourcePath(givenFolder.FullName); var result = project.GetAnalyzerConfigOptions(); diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs index fac1c922c97b6..34365e8a3234c 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/ISettingUpdater.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; @@ -11,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater { internal interface ISettingUpdater { - Task QueueUpdateAsync(TSetting setting, TValue value); + void QueueUpdate(TSetting setting, TValue value); Task GetChangedEditorConfigAsync(SourceText sourceText, CancellationToken token); Task HasAnyChangesAsync(); } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs index e9eea754fdfcc..12d806742aa6a 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdaterBase.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -15,6 +17,7 @@ internal abstract class SettingsUpdaterBase : ISettingUpdater _queue = new(); private readonly SemaphoreSlim _guard = new(1); + private readonly IAsynchronousOperationListener _listener; protected readonly Workspace Workspace; protected readonly string EditorconfigPath; @@ -23,17 +26,25 @@ internal abstract class SettingsUpdaterBase : ISettingUpdater().GetListener(); EditorconfigPath = editorconfigPath; } - public async Task QueueUpdateAsync(TOption setting, TValue value) + public void QueueUpdate(TOption setting, TValue value) { - using (await _guard.DisposableWaitAsync().ConfigureAwait(false)) + var token = _listener.BeginAsyncOperation(nameof(QueueUpdate)); + _ = QueueUpdateAsync().CompletesAsyncOperation(token); + + return; + + // local function + async Task QueueUpdateAsync() { - _queue.Add((setting, value)); + using (await _guard.DisposableWaitAsync().ConfigureAwait(false)) + { + _queue.Add((setting, value)); + } } - - return true; } public async Task GetChangedEditorConfigAsync(AnalyzerConfigDocument analyzerConfigDocument, CancellationToken token) diff --git a/src/EditorFeatures/Core/EditorFeaturesResources.resx b/src/EditorFeatures/Core/EditorFeaturesResources.resx index 2f6899e590dce..19eb6e8e3de80 100644 --- a/src/EditorFeatures/Core/EditorFeaturesResources.resx +++ b/src/EditorFeatures/Core/EditorFeaturesResources.resx @@ -939,6 +939,9 @@ Do you want to proceed? User Types - Records + + User Types - Record Structs + Get help for '{0}' @@ -951,6 +954,9 @@ Do you want to proceed? Gathering Suggestions - Waiting for the solution to fully load + + Reassigned variable + No diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index a94ebe47409cf..51bb8beafd148 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -2,7 +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.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; @@ -10,62 +10,53 @@ using Microsoft.CodeAnalysis.NavigationBar; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem; namespace Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar { - internal abstract class AbstractEditorNavigationBarItemService : INavigationBarItemService + internal abstract class AbstractEditorNavigationBarItemService : ForegroundThreadAffinitizedObject, INavigationBarItemService { - protected abstract VirtualTreePoint? GetSymbolNavigationPoint(Document document, ISymbol symbol, CancellationToken cancellationToken); - protected abstract void NavigateToItem(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken); + protected AbstractEditorNavigationBarItemService(IThreadingContext threadingContext) + : base(threadingContext, assertIsForeground: false) + { + } + + protected abstract Task TryNavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, ITextSnapshot textSnapshot, CancellationToken cancellationToken); - public async Task> GetItemsAsync(Document document, CancellationToken cancellationToken) + public async Task> GetItemsAsync( + Document document, ITextSnapshot textSnapshot, CancellationToken cancellationToken) { var service = document.GetRequiredLanguageService(); var workspaceSupportsDocumentChanges = document.Project.Solution.Workspace.CanApplyChange(ApplyChangesKind.ChangeDocument); var items = await service.GetItemsAsync(document, workspaceSupportsDocumentChanges, cancellationToken).ConfigureAwait(false); - return items.SelectAsArray(v => (NavigationBarItem)new WrappedNavigationBarItem(v)); + return items.SelectAsArray(v => (NavigationBarItem)new WrappedNavigationBarItem(v, textSnapshot)); } - public void NavigateToItem(Document document, NavigationBarItem item, ITextView textView, CancellationToken cancellationToken) - => NavigateToItem(document, (WrappedNavigationBarItem)item, textView, cancellationToken); + public Task TryNavigateToItemAsync(Document document, NavigationBarItem item, ITextView textView, ITextSnapshot textSnapshot, CancellationToken cancellationToken) + => TryNavigateToItemAsync(document, (WrappedNavigationBarItem)item, textView, textSnapshot, cancellationToken); - protected void NavigateToSymbolItem( - Document document, RoslynNavigationBarItem.SymbolItem item, CancellationToken cancellationToken) + protected async Task NavigateToSymbolItemAsync( + Document document, NavigationBarItem item, RoslynNavigationBarItem.SymbolItem symbolItem, ITextSnapshot textSnapshot, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(item.Kind == RoslynNavigationBarItemKind.Symbol); - var symbolNavigationService = document.Project.Solution.Workspace.Services.GetRequiredService(); + var workspace = document.Project.Solution.Workspace; - var symbolInfo = item.NavigationSymbolId.Resolve(document.Project.GetRequiredCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken), ignoreAssemblyKey: true, cancellationToken: cancellationToken); - var symbol = symbolInfo.GetAnySymbol(); + var (documentId, position, virtualSpace) = await GetNavigationLocationAsync(document, item, symbolItem, textSnapshot, cancellationToken).ConfigureAwait(false); - // Do not allow third party navigation to types or constructors - if (symbol != null && - !(symbol is ITypeSymbol) && - !symbol.IsConstructor() && - symbolNavigationService.TrySymbolNavigationNotify(symbol, document.Project, cancellationToken)) - { - return; - } - - var navigationPoint = this.GetSymbolItemNavigationPoint(document, item, cancellationToken); - - if (navigationPoint.HasValue) - { - NavigateToVirtualTreePoint(document.Project.Solution, navigationPoint.Value, cancellationToken); - } + // Ensure we're back on the UI thread before either navigating or showing a failure message. + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + NavigateToPosition(workspace, documentId, position, virtualSpace, cancellationToken); } - protected static void NavigateToVirtualTreePoint(Solution solution, VirtualTreePoint navigationPoint, CancellationToken cancellationToken) + protected void NavigateToPosition(Workspace workspace, DocumentId? documentId, int position, int virtualSpace, CancellationToken cancellationToken) { - var documentToNavigate = solution.GetRequiredDocument(navigationPoint.Tree); - var workspace = solution.Workspace; + this.AssertIsForeground(); var navigationService = workspace.Services.GetRequiredService(); - - if (navigationService.CanNavigateToPosition(workspace, documentToNavigate.Id, navigationPoint.Position, navigationPoint.VirtualSpaces, cancellationToken)) + if (navigationService.CanNavigateToPosition(workspace, documentId, position, virtualSpace, cancellationToken)) { - navigationService.TryNavigateToPosition(workspace, documentToNavigate.Id, navigationPoint.Position, navigationPoint.VirtualSpaces, options: null, cancellationToken); + navigationService.TryNavigateToPosition(workspace, documentId, position, virtualSpace, options: null, cancellationToken); } else { @@ -74,29 +65,34 @@ protected static void NavigateToVirtualTreePoint(Solution solution, VirtualTreeP } } - public virtual bool ShowItemGrayedIfNear(NavigationBarItem item) - => true; - - public VirtualTreePoint? GetSymbolItemNavigationPoint(Document document, RoslynNavigationBarItem.SymbolItem item, CancellationToken cancellationToken) + internal virtual Task<(DocumentId documentId, int position, int virtualSpace)> GetNavigationLocationAsync( + Document document, + NavigationBarItem item, + RoslynNavigationBarItem.SymbolItem symbolItem, + ITextSnapshot textSnapshot, + CancellationToken cancellationToken) { - Contract.ThrowIfFalse(item.Kind == RoslynNavigationBarItemKind.Symbol); - var compilation = document.Project.GetRequiredCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); - var symbols = item.NavigationSymbolId.Resolve(compilation, cancellationToken: cancellationToken); - - var symbol = symbols.Symbol; - if (symbol == null) + // If the item points to a location in this document, then just determine the current location + // of that item and go directly to it. + if (item.NavigationTrackingSpan != null) + { + return Task.FromResult((document.Id, item.NavigationTrackingSpan.GetSpan(textSnapshot).Start.Position, 0)); + } + else { - if (item.NavigationSymbolIndex < symbols.CandidateSymbols.Length) - { - symbol = symbols.CandidateSymbols[item.NavigationSymbolIndex]; - } - else - { - return null; - } + // Otherwise, the item pointed to a location in another document. Just return the position we + // computed and stored for it. + Contract.ThrowIfNull(symbolItem.Location.OtherDocumentInfo); + var (documentId, navigationSpan) = symbolItem.Location.OtherDocumentInfo.Value; + return Task.FromResult((documentId, navigationSpan.Start, 0)); } + } - return GetSymbolNavigationPoint(document, symbol, cancellationToken); + public bool ShowItemGrayedIfNear(NavigationBarItem item) + { + // We only show items in gray when near that actually exist (i.e. are not meant for codegen). + // This will be all C# items, and only VB non-codegen items. + return ((WrappedNavigationBarItem)item).UnderlyingItem is SymbolItem; } } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs index a19b14cbe0ae8..baaf1471a24ef 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs @@ -2,20 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.CodeAnalysis.Editor { internal interface INavigationBarItemService : ILanguageService { - Task> GetItemsAsync(Document document, CancellationToken cancellationToken); + Task> GetItemsAsync(Document document, ITextSnapshot textSnapshot, CancellationToken cancellationToken); bool ShowItemGrayedIfNear(NavigationBarItem item); - void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken); + + /// + /// Returns if navigation (or generation) happened. otherwise. + /// + Task TryNavigateToItemAsync(Document document, NavigationBarItem item, ITextView view, ITextSnapshot textSnapshot, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs index 8312916d65c45..c6751ffa7d3c3 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.CodeAnalysis.Editor @@ -15,11 +13,11 @@ internal interface INavigationBarPresenter void Disconnect(); void PresentItems( - IList projects, - NavigationBarProjectItem selectedProject, - IList typesWithMembers, - NavigationBarItem selectedType, - NavigationBarItem selectedMember); + ImmutableArray projects, + NavigationBarProjectItem? selectedProject, + ImmutableArray typesWithMembers, + NavigationBarItem? selectedType, + NavigationBarItem? selectedMember); ITextView TryGetCurrentView(); diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs index 5c615e521bc2c..7db1358d916e4 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs @@ -2,14 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor { @@ -20,37 +16,48 @@ internal abstract class NavigationBarItem public bool Bolded { get; } public bool Grayed { get; } public int Indent { get; } - public IList ChildItems { get; } + public ImmutableArray ChildItems { get; } + + /// + /// The tracking spans in the owning document corresponding to this nav bar item. If the user's + /// caret enters one of these spans, we'll select that item in the nav bar (except if they're in + /// an item's span that is nested within this). Tracking spans allow us to know where things are + /// as the users edits while the computation for the latest items might be going on. + /// + /// This can be empty for items whose location is in another document. + public ImmutableArray TrackingSpans { get; } - public IList Spans { get; internal set; } - internal IList TrackingSpans { get; set; } + /// + /// The tracking span in the owning document corresponding to where to navigate to if the user + /// selects this item in the drop down. + /// + /// This can be for items whose location is in another document. + public ITrackingSpan? NavigationTrackingSpan { get; } public NavigationBarItem( string text, Glyph glyph, - IList spans, - IList childItems = null, + ImmutableArray trackingSpans, + ITrackingSpan? navigationTrackingSpan, + ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) { this.Text = text; this.Glyph = glyph; - this.Spans = spans; - this.ChildItems = childItems ?? SpecializedCollections.EmptyList(); + this.TrackingSpans = trackingSpans; + this.NavigationTrackingSpan = navigationTrackingSpan; + this.ChildItems = childItems.NullToEmpty(); this.Indent = indent; this.Bolded = bolded; this.Grayed = grayed; } - internal void InitializeTrackingSpans(ITextSnapshot textSnapshot) - { - this.TrackingSpans = this.Spans.Select(s => textSnapshot.CreateTrackingSpan(s.ToSpan(), SpanTrackingMode.EdgeExclusive)).ToList(); + internal static ImmutableArray GetTrackingSpans(ITextSnapshot textSnapshot, ImmutableArray spans) + => spans.NullToEmpty().SelectAsArray(static (s, ts) => GetTrackingSpan(ts, s), textSnapshot); - if (this.ChildItems != null) - { - this.ChildItems.Do(i => i.InitializeTrackingSpans(textSnapshot)); - } - } + internal static ITrackingSpan GetTrackingSpan(ITextSnapshot textSnapshot, TextSpan textSpan) + => textSnapshot.CreateTrackingSpan(textSpan.ToSpan(), SpanTrackingMode.EdgeExclusive); } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs index ba1440dffd135..1474519b5bfc2 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; -using Microsoft.CodeAnalysis.Text; +using System.Collections.Immutable; +using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor { @@ -18,14 +16,16 @@ internal class NavigationBarPresentedItem : NavigationBarItem public NavigationBarPresentedItem( string text, Glyph glyph, - IList spans, - IList childItems = null, - bool bolded = false, - bool grayed = false) + ImmutableArray trackingSpans, + ITrackingSpan? navigationTrackingSpan, + ImmutableArray childItems, + bool bolded, + bool grayed) : base( text, glyph, - spans, + trackingSpans, + navigationTrackingSpan, childItems, indent: 0, bolded: bolded, diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs index 2b6e64e37e7cd..a2e7bcdd687fb 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs @@ -2,17 +2,17 @@ // 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 Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Immutable; +using Microsoft.VisualStudio.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor { - internal sealed class NavigationBarProjectItem : NavigationBarItem + internal sealed class NavigationBarProjectItem : NavigationBarItem, IEquatable { - public DocumentId DocumentId { get; } public Workspace Workspace { get; } + public DocumentId DocumentId { get; } public string Language { get; } public NavigationBarProjectItem( @@ -20,11 +20,12 @@ public NavigationBarProjectItem( Glyph glyph, Workspace workspace, DocumentId documentId, - string language, - int indent = 0, - bool bolded = false, - bool grayed = false) - : base(text, glyph, SpecializedCollections.EmptyList(), /*childItems:*/ null, indent, bolded, grayed) + string language) + : base(text, glyph, + trackingSpans: ImmutableArray.Empty, + navigationTrackingSpan: null, + childItems: ImmutableArray.Empty, + indent: 0, bolded: false, grayed: false) { this.Workspace = workspace; this.DocumentId = documentId; @@ -39,5 +40,23 @@ internal void SwitchToContext() this.Workspace.SetDocumentContext(DocumentId); } } + + public override bool Equals(object? obj) + => Equals(obj as NavigationBarProjectItem); + + public bool Equals(NavigationBarProjectItem? item) + => item is not null && + Text == item.Text && + Glyph == item.Glyph && + Workspace == item.Workspace && + DocumentId == item.DocumentId && + Language == item.Language; + + public override int GetHashCode() + => Hash.Combine(Text, + Hash.Combine((int)Glyph, + Hash.Combine(Workspace, + Hash.Combine(DocumentId, + Language.GetHashCode())))); } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs index fe06e8bdf9c93..cf631b4e576e5 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.NavigationBar; +using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor { @@ -14,17 +15,33 @@ internal class WrappedNavigationBarItem : NavigationBarItem { public readonly RoslynNavigationBarItem UnderlyingItem; - internal WrappedNavigationBarItem(RoslynNavigationBarItem underlyingItem) + internal WrappedNavigationBarItem( + RoslynNavigationBarItem underlyingItem, ITextSnapshot textSnapshot) : base( underlyingItem.Text, underlyingItem.Glyph, - underlyingItem.Spans, - underlyingItem.ChildItems.Select(v => new WrappedNavigationBarItem(v)).ToList(), + GetTrackingSpans(underlyingItem, textSnapshot), + GetNavigationTrackingSpan(underlyingItem, textSnapshot), + underlyingItem.ChildItems.SelectAsArray(v => (NavigationBarItem)new WrappedNavigationBarItem(v, textSnapshot)), underlyingItem.Indent, underlyingItem.Bolded, underlyingItem.Grayed) { UnderlyingItem = underlyingItem; } + + private static ImmutableArray GetTrackingSpans(RoslynNavigationBarItem underlyingItem, ITextSnapshot textSnapshot) + { + return underlyingItem is RoslynNavigationBarItem.SymbolItem symbolItem && symbolItem.Location.InDocumentInfo != null + ? GetTrackingSpans(textSnapshot, symbolItem.Location.InDocumentInfo.Value.spans) + : ImmutableArray.Empty; + } + + private static ITrackingSpan? GetNavigationTrackingSpan(RoslynNavigationBarItem underlyingItem, ITextSnapshot textSnapshot) + { + return underlyingItem is RoslynNavigationBarItem.SymbolItem symbolItem && symbolItem.Location.InDocumentInfo != null + ? GetTrackingSpan(textSnapshot, symbolItem.Location.InDocumentInfo.Value.navigationSpan) + : null; + } } } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFindUsagesContext.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFindUsagesContext.cs new file mode 100644 index 0000000000000..cee552048774a --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFindUsagesContext.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api +{ + internal interface IVSTypeScriptFindUsagesContext + { + /// + /// Used for clients that are finding usages to push information about how far along they + /// are in their search. + /// + IVSTypeScriptStreamingProgressTracker ProgressTracker { get; } + + /// + /// Report a message to be displayed to the user. + /// + ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken); + + /// + /// Set the title of the window that results are displayed in. + /// + ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken); + + ValueTask OnDefinitionFoundAsync(VSTypeScriptDefinitionItem definition, CancellationToken cancellationToken); + ValueTask OnReferenceFoundAsync(VSTypeScriptSourceReferenceItem reference, CancellationToken cancellationToken); + } + + internal interface IVSTypeScriptStreamingProgressTracker + { + ValueTask AddItemsAsync(int count, CancellationToken cancellationToken); + ValueTask ItemCompletedAsync(CancellationToken cancellationToken); + } + + internal class VSTypeScriptDefinitionItem + { + public VSTypeScriptDefinitionItem( + ImmutableArray tags, + ImmutableArray displayParts, + ImmutableArray sourceSpans, + ImmutableArray nameDisplayParts = default, + ImmutableDictionary? properties = null, + ImmutableDictionary? displayableProperties = null, + bool displayIfNoReferences = true) + { + Tags = tags; + DisplayParts = displayParts; + SourceSpans = sourceSpans; + NameDisplayParts = nameDisplayParts; + Properties = properties; + DisplayableProperties = displayableProperties; + DisplayIfNoReferences = displayIfNoReferences; + } + + public ImmutableArray Tags { get; } + public ImmutableArray DisplayParts { get; } + public ImmutableArray SourceSpans { get; } + public ImmutableArray NameDisplayParts { get; } + public ImmutableDictionary? Properties { get; } + public ImmutableDictionary? DisplayableProperties { get; } + public bool DisplayIfNoReferences { get; } + } + + internal class VSTypeScriptSourceReferenceItem + { + public VSTypeScriptSourceReferenceItem( + VSTypeScriptDefinitionItem definition, + DocumentSpan sourceSpan, + SymbolUsageInfo symbolUsageInfo) + { + Definition = definition; + SourceSpan = sourceSpan; + SymbolUsageInfo = symbolUsageInfo; + } + + public VSTypeScriptDefinitionItem Definition { get; } + public DocumentSpan SourceSpan { get; } + public SymbolUsageInfo SymbolUsageInfo { get; } + } +} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFindUsagesService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFindUsagesService.cs new file mode 100644 index 0000000000000..d18d021097cbe --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptFindUsagesService.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api +{ + internal interface IVSTypeScriptFindUsagesService : ILanguageService + { + /// + /// Finds the references for the symbol at the specific position in the document, + /// pushing the results into the context instance. + /// + Task FindReferencesAsync(Document document, int position, IVSTypeScriptFindUsagesContext context, CancellationToken cancellationToken); + + /// + /// Finds the implementations for the symbol at the specific position in the document, + /// pushing the results into the context instance. + /// + Task FindImplementationsAsync(Document document, int position, IVSTypeScriptFindUsagesContext context, CancellationToken cancellationToken); + } +} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptNavigationBarItemService.cs new file mode 100644 index 0000000000000..718dda05781d5 --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/IVSTypeScriptNavigationBarItemService.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api +{ + internal interface IVSTypeScriptNavigationBarItemService + { + Task> GetItemsAsync(Document document, CancellationToken cancellationToken); + } +} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptEditorFormattingServiceWrapper.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptEditorFormattingServiceWrapper.cs index 524f737b8e991..83e7c00b38063 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptEditorFormattingServiceWrapper.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptEditorFormattingServiceWrapper.cs @@ -5,22 +5,22 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { internal readonly struct VSTypeScriptEditorFormattingServiceWrapper { - private readonly IEditorFormattingService _underlyingObject; + private readonly IFormattingInteractionService _underlyingObject; - private VSTypeScriptEditorFormattingServiceWrapper(IEditorFormattingService underlyingObject) + private VSTypeScriptEditorFormattingServiceWrapper(IFormattingInteractionService underlyingObject) => _underlyingObject = underlyingObject; public static VSTypeScriptEditorFormattingServiceWrapper Create(Document document) - => new(document.Project.LanguageServices.GetRequiredService()); + => new(document.Project.LanguageServices.GetRequiredService()); - public Task> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken) - => _underlyingObject.GetFormattingChangesAsync(document, textSpan, cancellationToken); + public async Task> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken) + => await _underlyingObject.GetFormattingChangesAsync(document, textSpan, documentOptions: null, cancellationToken).ConfigureAwait(false); } } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitContextWrapper.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitContextWrapper.cs index 959029685b2d7..a6ff6b7bdeeb0 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitContextWrapper.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitContextWrapper.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Threading; using Microsoft.CodeAnalysis.Editor.Host; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { + [Obsolete("This is just a wrapper around the public Visual Studio API IUIThreadOperationContext, please use it directly.")] internal readonly struct VSTypeScriptWaitContextWrapper { private readonly IWaitContext _underlyingObject; diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorResult.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorResult.cs index 7829a3082b8b3..90bb57dfcde6c 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorResult.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorResult.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { internal enum VSTypeScriptWaitIndicatorResult { - Canceled = WaitIndicatorResult.Canceled, - Completed = WaitIndicatorResult.Completed + Canceled = UIThreadOperationStatus.Canceled, + Completed = UIThreadOperationStatus.Completed } } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorWrapper.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorWrapper.cs index 2b93a6b6cc630..a9fb1a666adec 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorWrapper.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptWaitIndicatorWrapper.cs @@ -7,6 +7,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { + [Obsolete("This is just a wrapper around the public Visual Studio API IUIThreadOperationContext, please use it directly.")] internal readonly struct VSTypeScriptWaitIndicatorWrapper { private readonly IWaitIndicator _underlyingObject; diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypescriptNavigationBarItem.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypescriptNavigationBarItem.cs new file mode 100644 index 0000000000000..a089f8a2094a3 --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypescriptNavigationBarItem.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api +{ + internal class VSTypescriptNavigationBarItem + { + public string Text { get; } + public VSTypeScriptGlyph Glyph { get; } + public bool Bolded { get; } + public bool Grayed { get; } + public int Indent { get; } + public ImmutableArray ChildItems { get; } + public ImmutableArray Spans { get; } + + public VSTypescriptNavigationBarItem( + string text, + VSTypeScriptGlyph glyph, + ImmutableArray spans, + ImmutableArray childItems = default, + int indent = 0, + bool bolded = false, + bool grayed = false) + { + this.Text = text; + this.Glyph = glyph; + this.Spans = spans.NullToEmpty(); + this.ChildItems = childItems.NullToEmpty(); + this.Indent = indent; + this.Bolded = bolded; + this.Grayed = grayed; + } + } +} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs new file mode 100644 index 0000000000000..caa32af454098 --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptFindUsagesService.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.ExternalAccess.VSTypeScript +{ + [ExportLanguageService(typeof(IFindUsagesService), InternalLanguageNames.TypeScript), Shared] + internal class VSTypeScriptFindUsagesService : IFindUsagesService + { + private readonly IVSTypeScriptFindUsagesService _underlyingService; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public VSTypeScriptFindUsagesService(IVSTypeScriptFindUsagesService underlyingService) + { + _underlyingService = underlyingService; + } + + public Task FindReferencesAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) + => _underlyingService.FindReferencesAsync(document, position, new VSTypeScriptFindUsagesContext(context), cancellationToken); + + public Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) + => _underlyingService.FindImplementationsAsync(document, position, new VSTypeScriptFindUsagesContext(context), cancellationToken); + + private class VSTypeScriptFindUsagesContext : IVSTypeScriptFindUsagesContext + { + private readonly IFindUsagesContext _context; + private readonly Dictionary _definitionItemMap = new(); + + public VSTypeScriptFindUsagesContext(IFindUsagesContext context) + { + _context = context; + } + + public IVSTypeScriptStreamingProgressTracker ProgressTracker => new VSTypeScriptStreamingProgressTracker(_context.ProgressTracker); + + public ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken) + => _context.ReportMessageAsync(message, cancellationToken); + + public ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) + => _context.SetSearchTitleAsync(title, cancellationToken); + + private DefinitionItem GetOrCreateDefinitionItem(VSTypeScriptDefinitionItem item) + { + lock (_definitionItemMap) + { + if (!_definitionItemMap.TryGetValue(item, out var result)) + { + result = DefinitionItem.Create( + item.Tags, + item.DisplayParts, + item.SourceSpans, + item.NameDisplayParts, + item.Properties, + item.DisplayableProperties, + item.DisplayIfNoReferences); + _definitionItemMap.Add(item, result); + } + + return result; + } + } + + public ValueTask OnDefinitionFoundAsync(VSTypeScriptDefinitionItem definition, CancellationToken cancellationToken) + { + var item = GetOrCreateDefinitionItem(definition); + return _context.OnDefinitionFoundAsync(item, cancellationToken); + } + + public ValueTask OnReferenceFoundAsync(VSTypeScriptSourceReferenceItem reference, CancellationToken cancellationToken) + { + var item = GetOrCreateDefinitionItem(reference.Definition); + return _context.OnReferenceFoundAsync(new SourceReferenceItem(item, reference.SourceSpan, reference.SymbolUsageInfo), cancellationToken); + } + } + + private class VSTypeScriptStreamingProgressTracker : IVSTypeScriptStreamingProgressTracker + { + private readonly IStreamingProgressTracker _progressTracker; + + public VSTypeScriptStreamingProgressTracker(IStreamingProgressTracker progressTracker) + { + _progressTracker = progressTracker; + } + + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) + => _progressTracker.AddItemsAsync(count, cancellationToken); + + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + => _progressTracker.ItemCompletedAsync(cancellationToken); + } + } +} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs new file mode 100644 index 0000000000000..e9b8602863586 --- /dev/null +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript +{ + [ExportLanguageService(typeof(INavigationBarItemService), InternalLanguageNames.TypeScript), Shared] + internal class VSTypeScriptNavigationBarItemService : INavigationBarItemService + { + private readonly IThreadingContext _threadingContext; + private readonly IVSTypeScriptNavigationBarItemService _service; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public VSTypeScriptNavigationBarItemService( + IThreadingContext threadingContext, + IVSTypeScriptNavigationBarItemService service) + { + _threadingContext = threadingContext; + _service = service; + } + + public async Task> GetItemsAsync( + Document document, ITextSnapshot textSnapshot, CancellationToken cancellationToken) + { + var items = await _service.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); + return ConvertItems(textSnapshot, items); + } + + private static ImmutableArray ConvertItems(ITextSnapshot textSnapshot, ImmutableArray items) + => items.SelectAsArray(x => !x.Spans.IsEmpty, x => ConvertToNavigationBarItem(x, textSnapshot)); + + public async Task TryNavigateToItemAsync( + Document document, NavigationBarItem item, ITextView view, ITextSnapshot textSnapshot, CancellationToken cancellationToken) + { + if (item.NavigationTrackingSpan != null) + { + var span = item.NavigationTrackingSpan.GetSpan(textSnapshot); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var workspace = document.Project.Solution.Workspace; + var navigationService = VSTypeScriptDocumentNavigationServiceWrapper.Create(workspace); + navigationService.TryNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, options: null, cancellationToken: cancellationToken); + } + + return true; + } + + public bool ShowItemGrayedIfNear(NavigationBarItem item) + { + return true; + } + + private static NavigationBarItem ConvertToNavigationBarItem(VSTypescriptNavigationBarItem item, ITextSnapshot textSnapshot) + { + Contract.ThrowIfTrue(item.Spans.IsEmpty); + return new InternalNavigationBarItem( + item.Text, + VSTypeScriptGlyphHelpers.ConvertTo(item.Glyph), + NavigationBarItem.GetTrackingSpans(textSnapshot, item.Spans), + ConvertItems(textSnapshot, item.ChildItems), + item.Indent, + item.Bolded, + item.Grayed); + } + + private class InternalNavigationBarItem : NavigationBarItem + { + public InternalNavigationBarItem(string text, Glyph glyph, ImmutableArray trackingSpans, ImmutableArray childItems, int indent, bool bolded, bool grayed) + : base(text, glyph, trackingSpans, trackingSpans.First(), childItems, indent, bolded, grayed) + { + } + } + } +} diff --git a/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs b/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs index 139a71759f8dd..b7c79db5a3fd2 100644 --- a/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs +++ b/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs @@ -7,7 +7,6 @@ using System; using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.Editor.Host; @@ -98,6 +97,9 @@ private bool TryExecuteCommand(int caretPosition, Document document, IFindUsages // a presenter that can accept streamed results. if (findUsagesService != null && _streamingPresenter != null) { + // kick this work off in a fire and forget fashion. Importantly, this means we do + // not pass in any ambient cancellation information as the execution of this command + // will complete and will have no bearing on the computation of the references we compute. _ = StreamingFindReferencesAsync(document, caretPosition, findUsagesService, _streamingPresenter); return true; } @@ -106,7 +108,8 @@ private bool TryExecuteCommand(int caretPosition, Document document, IFindUsages } private async Task StreamingFindReferencesAsync( - Document document, int caretPosition, + Document document, + int caretPosition, IFindUsagesService findUsagesService, IStreamingFindUsagesPresenter presenter) { @@ -117,25 +120,24 @@ private async Task StreamingFindReferencesAsync( // Let the presented know we're starting a search. It will give us back the context object that the FAR // service will push results into. This operation is not externally cancellable. Instead, the find refs // window will cancel it if another request is made to use it. - var context = presenter.StartSearchWithCustomColumns( + var (context, cancellationToken) = presenter.StartSearchWithCustomColumns( EditorFeaturesResources.Find_References, supportsReferences: true, includeContainingTypeAndMemberColumns: document.Project.SupportsCompilation, - includeKindColumn: document.Project.Language != LanguageNames.FSharp, - CancellationToken.None); + includeKindColumn: document.Project.Language != LanguageNames.FSharp); using (Logger.LogBlock( FunctionId.CommandHandler_FindAllReference, KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"), - context.CancellationToken)) + cancellationToken)) { try { - await findUsagesService.FindReferencesAsync(document, caretPosition, context).ConfigureAwait(false); + await findUsagesService.FindReferencesAsync(document, caretPosition, context, cancellationToken).ConfigureAwait(false); } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs index 838f11f6990b5..dcb5d746d113d 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs @@ -2,9 +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.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -33,33 +30,26 @@ private class DefinitionTrackingContext : IFindUsagesContext public DefinitionTrackingContext(IFindUsagesContext underlyingContext) => _underlyingContext = underlyingContext; - public CancellationToken CancellationToken - => _underlyingContext.CancellationToken; - public IStreamingProgressTracker ProgressTracker => _underlyingContext.ProgressTracker; - public ValueTask ReportMessageAsync(string message) - => _underlyingContext.ReportMessageAsync(message); - - public ValueTask SetSearchTitleAsync(string title) - => _underlyingContext.SetSearchTitleAsync(title); + public ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken) + => _underlyingContext.ReportMessageAsync(message, cancellationToken); - public ValueTask OnReferenceFoundAsync(SourceReferenceItem reference) - => _underlyingContext.OnReferenceFoundAsync(reference); + public ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) + => _underlyingContext.SetSearchTitleAsync(title, cancellationToken); - [Obsolete("Use ProgressTracker instead", error: false)] - public ValueTask ReportProgressAsync(int current, int maximum) - => _underlyingContext.ReportProgressAsync(current, maximum); + public ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + => _underlyingContext.OnReferenceFoundAsync(reference, cancellationToken); - public ValueTask OnDefinitionFoundAsync(DefinitionItem definition) + public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { lock (_gate) { _definitions.Add(definition); } - return _underlyingContext.OnDefinitionFoundAsync(definition); + return _underlyingContext.OnDefinitionFoundAsync(definition, cancellationToken); } public ImmutableArray GetDefinitions() diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 7b0f007352b28..886564d876238 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -38,12 +38,12 @@ public FindLiteralsProgressAdapter( _definition = definition; } - public async ValueTask OnReferenceFoundAsync(Document document, TextSpan span) + public async ValueTask OnReferenceFoundAsync(Document document, TextSpan span, CancellationToken cancellationToken) { var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync( - document, span, _context.CancellationToken).ConfigureAwait(false); + document, span, cancellationToken).ConfigureAwait(false); await _context.OnReferenceFoundAsync(new SourceReferenceItem( - _definition, documentSpan, SymbolUsageInfo.None)).ConfigureAwait(false); + _definition, documentSpan, SymbolUsageInfo.None), cancellationToken).ConfigureAwait(false); } } @@ -66,8 +66,7 @@ private class FindReferencesProgressAdapter : IStreamingFindReferencesProgress /// This dictionary allows us to make that mapping once and then keep it around for /// all future callbacks. /// - private readonly Dictionary _definitionToItem = - new(MetadataUnifyingEquivalenceComparer.Instance); + private readonly Dictionary _definitionToItem = new(); private readonly SemaphoreSlim _gate = new(initialCount: 1); @@ -84,53 +83,50 @@ public FindReferencesProgressAdapter( // Do nothing functions. The streaming far service doesn't care about // any of these. - public ValueTask OnStartedAsync() => default; - public ValueTask OnCompletedAsync() => default; - public ValueTask OnFindInDocumentStartedAsync(Document document) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document) => default; + public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; + public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; + public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; + public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; // More complicated forwarding functions. These need to map from the symbols // used by the FAR engine to the INavigableItems used by the streaming FAR // feature. - private async ValueTask GetDefinitionItemAsync(ISymbol definition) + private async ValueTask GetDefinitionItemAsync(SymbolGroup group, CancellationToken cancellationToken) { - var cancellationToken = _context.CancellationToken; using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (!_definitionToItem.TryGetValue(definition, out var definitionItem)) + if (!_definitionToItem.TryGetValue(group, out var definitionItem)) { - definitionItem = await definition.ToClassifiedDefinitionItemAsync( + definitionItem = await group.ToClassifiedDefinitionItemAsync( _solution, isPrimary: _definitionToItem.Count == 0, includeHiddenLocations: false, _options, - _context.CancellationToken).ConfigureAwait(false); + cancellationToken).ConfigureAwait(false); - _definitionToItem[definition] = definitionItem; + _definitionToItem[group] = definitionItem; } return definitionItem; } } - public async ValueTask OnDefinitionFoundAsync(ISymbol definition) + public async ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { - var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false); - await _context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); + var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); + await _context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync(ISymbol definition, ReferenceLocation location) + public async ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) { - var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false); + var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); var referenceItem = await location.TryCreateSourceReferenceItemAsync( definitionItem, includeHiddenLocations: false, - cancellationToken: _context.CancellationToken).ConfigureAwait(false); + cancellationToken).ConfigureAwait(false); if (referenceItem != null) - { - await _context.OnReferenceFoundAsync(referenceItem).ConfigureAwait(false); - } + await _context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs index cd831496af0df..59e7fcf5ca57f 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindImplementations.cs @@ -12,35 +12,34 @@ using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages { internal abstract partial class AbstractFindUsagesService { - public async Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context) + public async Task FindImplementationsAsync( + Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; - // If this is a symbol from a metadata-as-source project, then map that symbol back to a symbol in the primary workspace. var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( document, position, cancellationToken).ConfigureAwait(false); if (symbolAndProjectOpt == null) { await context.ReportMessageAsync( - EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret).ConfigureAwait(false); + EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret, cancellationToken).ConfigureAwait(false); return; } var symbolAndProject = symbolAndProjectOpt.Value; await FindImplementationsAsync( - symbolAndProject.symbol, symbolAndProject.project, context).ConfigureAwait(false); + symbolAndProject.symbol, symbolAndProject.project, context, cancellationToken).ConfigureAwait(false); } public static async Task FindImplementationsAsync( - ISymbol symbol, Project project, IFindUsagesContext context) + ISymbol symbol, Project project, IFindUsagesContext context, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var solution = project.Solution; var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); if (client != null) @@ -61,39 +60,37 @@ await client.TryInvokeAsync( { // Couldn't effectively search in OOP. Perform the search in-process. await FindImplementationsInCurrentProcessAsync( - symbol, project, context).ConfigureAwait(false); + symbol, project, context, cancellationToken).ConfigureAwait(false); } } private static async Task FindImplementationsInCurrentProcessAsync( - ISymbol symbol, Project project, IFindUsagesContext context) + ISymbol symbol, Project project, IFindUsagesContext context, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; - var solution = project.Solution; - var (implementations, message) = await FindSourceImplementationsAsync( - solution, symbol, cancellationToken).ConfigureAwait(false); + var implementations = await FindSourceImplementationsAsync(solution, symbol, cancellationToken).ConfigureAwait(false); - if (message != null) + if (implementations.IsEmpty) { - await context.ReportMessageAsync(message).ConfigureAwait(false); + await context.ReportMessageAsync(EditorFeaturesResources.The_symbol_has_no_implementations, cancellationToken).ConfigureAwait(false); return; } await context.SetSearchTitleAsync( string.Format(EditorFeaturesResources._0_implementations, - FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false); + FindUsagesHelpers.GetDisplayName(symbol)), + cancellationToken).ConfigureAwait(false); foreach (var implementation in implementations) { var definitionItem = await implementation.ToClassifiedDefinitionItemAsync( solution, isPrimary: true, includeHiddenLocations: false, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); - await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } } - private static async Task<(ImmutableArray implementations, string? message)> FindSourceImplementationsAsync( + private static async Task> FindSourceImplementationsAsync( Solution solution, ISymbol symbol, CancellationToken cancellationToken) { var builder = new HashSet(SymbolEquivalenceComparer.Instance); @@ -103,23 +100,60 @@ await context.SetSearchTitleAsync( var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync( symbol, solution, cancellationToken).ConfigureAwait(false); + // Because we're searching linked files, we may get many symbols that are conceptually + // 'duplicates' to the user. Specifically, any symbols that would navigate to the same + // location do not provide value to the user as selecting any from that set of items + // would navigate them to the exact same location. For this, we use file-paths and spans + // as those will be the same regardless of how a file is linked or used in shared project + // scenarios. + var seenLocations = new HashSet<(string filePath, TextSpan span)>(); + foreach (var linkedSymbol in linkedSymbols) { - builder.AddRange(await FindSourceImplementationsWorkerAsync( - solution, linkedSymbol, cancellationToken).ConfigureAwait((bool)false)); + var implementations = await FindSourceImplementationsWorkerAsync( + solution, linkedSymbol, cancellationToken).ConfigureAwait(false); + foreach (var implementation in implementations) + { + if (AddedAllLocations(implementation, seenLocations)) + builder.Add(implementation); + } } - var result = builder.ToImmutableArray(); - var message = result.IsEmpty ? EditorFeaturesResources.The_symbol_has_no_implementations : null; + return builder.ToImmutableArray(); - return (result, message); + static bool AddedAllLocations(ISymbol implementation, HashSet<(string filePath, TextSpan span)> seenLocations) + { + foreach (var location in implementation.Locations) + { + Contract.ThrowIfFalse(location.IsInSource); + if (!seenLocations.Add((location.SourceTree.FilePath, location.SourceSpan))) + return false; + } + + return true; + } } private static async Task> FindSourceImplementationsWorkerAsync( Solution solution, ISymbol symbol, CancellationToken cancellationToken) { var implementations = await FindSourceAndMetadataImplementationsAsync(solution, symbol, cancellationToken).ConfigureAwait(false); - return implementations.WhereAsArray(s => s.Locations.Any(l => l.IsInSource)); + var sourceImplementations = new HashSet(implementations.Where(s => s.IsFromSource()).Select(s => s.OriginalDefinition)); + + // For members, if we've found overrides of the original symbol, then filter out any abstract + // members these inherit from. The user has asked for literal implementations, and in the case + // of an override, including the abstract as well isn't helpful. + var overrides = sourceImplementations.Where(s => s.IsOverride).ToImmutableArray(); + foreach (var ov in overrides) + { + for (var overridden = ov.GetOverriddenMember(); overridden != null; overridden = overridden.GetOverriddenMember()) + { + if (overridden.IsAbstract) + sourceImplementations.Remove(overridden.OriginalDefinition); + } + } + + return sourceImplementations.ToImmutableArray(); } private static async Task> FindSourceAndMetadataImplementationsAsync( diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs index 21683c9a1427f..a30d57c0f41f6 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Editor.FindUsages internal abstract partial class AbstractFindUsagesService { async Task IFindUsagesService.FindReferencesAsync( - Document document, int position, IFindUsagesContext context) + Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { var definitionTrackingContext = new DefinitionTrackingContext(context); @@ -29,12 +29,12 @@ async Task IFindUsagesService.FindReferencesAsync( // // Any async calls before GetThirdPartyDefinitions must be ConfigureAwait(true). await FindLiteralOrSymbolReferencesAsync( - document, position, definitionTrackingContext).ConfigureAwait(true); + document, position, definitionTrackingContext, cancellationToken).ConfigureAwait(true); // After the FAR engine is done call into any third party extensions to see // if they want to add results. var thirdPartyDefinitions = GetThirdPartyDefinitions( - document.Project.Solution, definitionTrackingContext.GetDefinitions(), context.CancellationToken); + document.Project.Solution, definitionTrackingContext.GetDefinitions(), cancellationToken); // From this point on we can do ConfigureAwait(false) as we're not calling back // into third parties anymore. @@ -42,27 +42,28 @@ await FindLiteralOrSymbolReferencesAsync( foreach (var definition in thirdPartyDefinitions) { // Don't need ConfigureAwait(true) here - await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); + await context.OnDefinitionFoundAsync(definition, cancellationToken).ConfigureAwait(false); } } Task IFindUsagesLSPService.FindReferencesAsync( - Document document, int position, IFindUsagesContext context) + Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { // We don't need to get third party definitions when finding references in LSP. // Currently, 3rd party definitions = XAML definitions, and XAML will provide // references via LSP instead of hooking into Roslyn. // This also means that we don't need to be on the UI thread. - return FindLiteralOrSymbolReferencesAsync(document, position, new DefinitionTrackingContext(context)); + return FindLiteralOrSymbolReferencesAsync( + document, position, new DefinitionTrackingContext(context), cancellationToken); } private static async Task FindLiteralOrSymbolReferencesAsync( - Document document, int position, IFindUsagesContext context) + Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { // First, see if we're on a literal. If so search for literals in the solution with // the same value. var found = await TryFindLiteralReferencesAsync( - document, position, context).ConfigureAwait(false); + document, position, context, cancellationToken).ConfigureAwait(false); if (found) { return; @@ -70,7 +71,7 @@ private static async Task FindLiteralOrSymbolReferencesAsync( // Wasn't a literal. Try again as a symbol. await FindSymbolReferencesAsync( - document, position, context).ConfigureAwait(false); + document, position, context, cancellationToken).ConfigureAwait(false); } private static ImmutableArray GetThirdPartyDefinitions( @@ -85,9 +86,8 @@ private static ImmutableArray GetThirdPartyDefinitions( } private static async Task FindSymbolReferencesAsync( - Document document, int position, IFindUsagesContext context) + Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; cancellationToken.ThrowIfCancellationRequested(); // If this is a symbol from a metadata-as-source project, then map that symbol back to a symbol in the primary workspace. @@ -99,7 +99,7 @@ private static async Task FindSymbolReferencesAsync( var (symbol, project) = symbolAndProjectOpt.Value; await FindSymbolReferencesAsync( - context, symbol, project).ConfigureAwait(false); + context, symbol, project, cancellationToken).ConfigureAwait(false); } /// @@ -107,10 +107,12 @@ await FindSymbolReferencesAsync( /// and want to push all the references to it into the Streaming-Find-References window. /// public static async Task FindSymbolReferencesAsync( - IFindUsagesContext context, ISymbol symbol, Project project) + IFindUsagesContext context, ISymbol symbol, Project project, CancellationToken cancellationToken) { - await context.SetSearchTitleAsync(string.Format(EditorFeaturesResources._0_references, - FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false); + await context.SetSearchTitleAsync( + string.Format(EditorFeaturesResources._0_references, + FindUsagesHelpers.GetDisplayName(symbol)), + cancellationToken).ConfigureAwait(false); var options = FindReferencesSearchOptions.GetFeatureOptionsForStartingSymbol(symbol); @@ -118,16 +120,16 @@ await context.SetSearchTitleAsync(string.Format(EditorFeaturesResources._0_refer // engine will push results into the 'progress' instance passed into it. // We'll take those results, massage them, and forward them along to the // FindReferencesContext instance we were given. - await FindReferencesAsync(context, symbol, project, options).ConfigureAwait(false); + await FindReferencesAsync(context, symbol, project, options, cancellationToken).ConfigureAwait(false); } public static async Task FindReferencesAsync( IFindUsagesContext context, ISymbol symbol, Project project, - FindReferencesSearchOptions options) + FindReferencesSearchOptions options, + CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var solution = project.Solution; var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); if (client != null) @@ -148,7 +150,7 @@ public static async Task FindReferencesAsync( { // Couldn't effectively search in OOP. Perform the search in-process. await FindReferencesInCurrentProcessAsync( - context, symbol, project, options).ConfigureAwait(false); + context, symbol, project, options, cancellationToken).ConfigureAwait(false); } } @@ -156,17 +158,17 @@ private static Task FindReferencesInCurrentProcessAsync( IFindUsagesContext context, ISymbol symbol, Project project, - FindReferencesSearchOptions options) + FindReferencesSearchOptions options, + CancellationToken cancellationToken) { var progress = new FindReferencesProgressAdapter(project.Solution, context, options); return SymbolFinder.FindReferencesAsync( - symbol, project.Solution, progress, documents: null, options, context.CancellationToken); + symbol, project.Solution, progress, documents: null, options, cancellationToken); } private static async Task TryFindLiteralReferencesAsync( - Document document, int position, IFindUsagesContext context) + Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; cancellationToken.ThrowIfCancellationRequested(); var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); @@ -197,7 +199,7 @@ private static async Task TryFindLiteralReferencesAsync( return false; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var symbol = semanticModel.GetSymbolInfo(token.Parent).Symbol ?? semanticModel.GetDeclaredSymbol(token.Parent); + var symbol = semanticModel.GetSymbolInfo(token.Parent, cancellationToken).Symbol ?? semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken); // Numeric labels are available in VB. In that case we want the normal FAR engine to // do the searching. For these literals we want to find symbolic results and not @@ -213,7 +215,7 @@ private static async Task TryFindLiteralReferencesAsync( } var searchTitle = string.Format(EditorFeaturesResources._0_references, title); - await context.SetSearchTitleAsync(searchTitle).ConfigureAwait(false); + await context.SetSearchTitleAsync(searchTitle, cancellationToken).ConfigureAwait(false); var solution = document.Project.Solution; @@ -223,7 +225,7 @@ private static async Task TryFindLiteralReferencesAsync( ImmutableArray.Create(TextTags.StringLiteral), ImmutableArray.Create(new TaggedText(TextTags.Text, searchTitle))); - await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); + await context.OnDefinitionFoundAsync(definition, cancellationToken).ConfigureAwait(false); var progressAdapter = new FindLiteralsProgressAdapter(context, definition); diff --git a/src/EditorFeatures/Core/FindUsages/FindUsagesContext.cs b/src/EditorFeatures/Core/FindUsages/FindUsagesContext.cs index 445f450662f4a..a733a3d746aae 100644 --- a/src/EditorFeatures/Core/FindUsages/FindUsagesContext.cs +++ b/src/EditorFeatures/Core/FindUsages/FindUsagesContext.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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -12,26 +10,21 @@ namespace Microsoft.CodeAnalysis.FindUsages { internal abstract class FindUsagesContext : IFindUsagesContext { - public virtual CancellationToken CancellationToken { get; } - public IStreamingProgressTracker ProgressTracker { get; } protected FindUsagesContext() => this.ProgressTracker = new StreamingProgressTracker(this.ReportProgressAsync); - public virtual ValueTask ReportMessageAsync(string message) => default; - - public virtual ValueTask SetSearchTitleAsync(string title) => default; + public virtual ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken) => default; - public virtual ValueTask OnCompletedAsync() => default; + public virtual ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) => default; - public virtual ValueTask OnDefinitionFoundAsync(DefinitionItem definition) => default; + public virtual ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; - public virtual ValueTask OnReferenceFoundAsync(SourceReferenceItem reference) => default; + public virtual ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) => default; - protected virtual ValueTask ReportProgressAsync(int current, int maximum) => default; + public virtual ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) => default; - ValueTask IFindUsagesContext.ReportProgressAsync(int current, int maximum) - => ReportProgressAsync(current, maximum); + protected virtual ValueTask ReportProgressAsync(int current, int maximum, CancellationToken cancellationToken) => default; } } diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 2daaaabe6682a..74f1fff6d3164 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -16,8 +17,8 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages @@ -69,6 +70,17 @@ public static DefinitionItem ToNonClassifiedDefinitionItem( options: FindReferencesSearchOptions.Default, cancellationToken: CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None); } + public static Task ToNonClassifiedDefinitionItemAsync( + this ISymbol definition, + Solution solution, + bool includeHiddenLocations, + CancellationToken cancellationToken) + { + return ToDefinitionItemAsync( + definition, solution, isPrimary: false, includeHiddenLocations, includeClassifiedSpans: false, + options: FindReferencesSearchOptions.Default.With(unidirectionalHierarchyCascade: true), cancellationToken: cancellationToken); + } + public static Task ToClassifiedDefinitionItemAsync( this ISymbol definition, Solution solution, @@ -83,8 +95,23 @@ public static Task ToClassifiedDefinitionItemAsync( options, cancellationToken); } + public static Task ToClassifiedDefinitionItemAsync( + this SymbolGroup group, Solution solution, bool isPrimary, bool includeHiddenLocations, FindReferencesSearchOptions options, CancellationToken cancellationToken) + { + // Make a single definition item that knows about all the locations of all the symbols in the group. + var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); + return ToDefinitionItemAsync(group.Symbols.First(), allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); + } + + private static Task ToDefinitionItemAsync( + ISymbol definition, Solution solution, bool isPrimary, bool includeHiddenLocations, bool includeClassifiedSpans, FindReferencesSearchOptions options, CancellationToken cancellationToken) + { + return ToDefinitionItemAsync(definition, definition.Locations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken); + } + private static async Task ToDefinitionItemAsync( - this ISymbol definition, + ISymbol definition, + ImmutableArray locations, Solution solution, bool isPrimary, bool includeHiddenLocations, @@ -112,16 +139,15 @@ private static async Task ToDefinitionItemAsync( var displayIfNoReferences = definition.ShouldShowWithNoReferenceLocations( options, showMetadataSymbolsWithoutReferences: false); - using var sourceLocationsDisposer = ArrayBuilder.GetInstance(out var sourceLocations); - var properties = GetProperties(definition, isPrimary); // If it's a namespace, don't create any normal location. Namespaces // come from many different sources, but we'll only show a single // root definition node for it. That node won't be navigable. + using var sourceLocations = TemporaryArray.Empty; if (definition.Kind != SymbolKind.Namespace) { - foreach (var location in definition.Locations) + foreach (var location in locations) { if (location.IsInMetadata) { @@ -164,7 +190,7 @@ private static async Task ToDefinitionItemAsync( var displayableProperties = AbstractReferenceFinder.GetAdditionalFindUsagesProperties(definition); return DefinitionItem.Create( - tags, displayParts, sourceLocations.ToImmutable(), + tags, displayParts, sourceLocations.ToImmutableAndClear(), nameDisplayParts, properties, displayableProperties, displayIfNoReferences); } diff --git a/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs b/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs index cfca2911abfb6..5533dd10c364a 100644 --- a/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs +++ b/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs @@ -2,8 +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. -#nullable disable - +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; @@ -16,12 +15,12 @@ internal interface IFindUsagesLSPService : ILanguageService /// Finds the references for the symbol at the specific position in the document, /// pushing the results into the context instance. /// - Task FindReferencesAsync(Document document, int position, IFindUsagesContext context); + Task FindReferencesAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken); /// /// Finds the implementations for the symbol at the specific position in the document, /// pushing the results into the context instance. /// - Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context); + Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs index 9b92dbaf237fc..e6c247e4bd999 100644 --- a/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs +++ b/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs @@ -2,8 +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. -#nullable disable - +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; @@ -16,12 +15,12 @@ internal interface IFindUsagesService : ILanguageService /// Finds the references for the symbol at the specific position in the document, /// pushing the results into the context instance. /// - Task FindReferencesAsync(Document document, int position, IFindUsagesContext context); + Task FindReferencesAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken); /// /// Finds the implementations for the symbol at the specific position in the document, /// pushing the results into the context instance. /// - Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context); + Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs b/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs index cf6becce0a3cd..6adc1175310ee 100644 --- a/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs +++ b/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs @@ -25,21 +25,20 @@ internal class SimpleFindUsagesContext : FindUsagesContext private readonly ImmutableArray.Builder _referenceItems = ImmutableArray.CreateBuilder(); - public override CancellationToken CancellationToken { get; } - - public SimpleFindUsagesContext(CancellationToken cancellationToken) - => CancellationToken = cancellationToken; + public SimpleFindUsagesContext() + { + } public string Message { get; private set; } public string SearchTitle { get; private set; } - public override ValueTask ReportMessageAsync(string message) + public override ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken) { Message = message; return default; } - public override ValueTask SetSearchTitleAsync(string title) + public override ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) { SearchTitle = title; return default; @@ -61,7 +60,7 @@ public ImmutableArray GetReferences() } } - public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition) + public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { lock (_gate) { @@ -71,7 +70,7 @@ public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition) return default; } - public override ValueTask OnReferenceFoundAsync(SourceReferenceItem reference) + public override ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) { lock (_gate) { diff --git a/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs b/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs index e5c1ee377a2c8..060fd12c467c9 100644 --- a/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs +++ b/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs @@ -5,6 +5,7 @@ #nullable disable using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.FindSymbols; @@ -14,16 +15,15 @@ namespace Microsoft.CodeAnalysis.Editor.GoToBase { internal abstract partial class AbstractGoToBaseService : IGoToBaseService { - public async Task FindBasesAsync(Document document, int position, IFindUsagesContext context) + public async Task FindBasesAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( document, position, cancellationToken).ConfigureAwait(false); if (symbolAndProjectOpt == null) { await context.ReportMessageAsync( - EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret).ConfigureAwait(false); + EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret, cancellationToken).ConfigureAwait(false); return; } @@ -34,7 +34,8 @@ await context.ReportMessageAsync( await context.SetSearchTitleAsync( string.Format(EditorFeaturesResources._0_bases, - FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false); + FindUsagesHelpers.GetDisplayName(symbol)), + cancellationToken).ConfigureAwait(false); var found = false; @@ -50,22 +51,21 @@ await context.SetSearchTitleAsync( var definitionItem = await sourceDefinition.ToClassifiedDefinitionItemAsync( solution, isPrimary: true, includeHiddenLocations: false, FindReferencesSearchOptions.Default, cancellationToken: cancellationToken).ConfigureAwait(false); - await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); found = true; } else if (baseSymbol.Locations.Any(l => l.IsInMetadata)) { var definitionItem = baseSymbol.ToNonClassifiedDefinitionItem( solution, includeHiddenLocations: true); - await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); + await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); found = true; } } if (!found) { - await context.ReportMessageAsync(EditorFeaturesResources.The_symbol_has_no_base) - .ConfigureAwait(false); + await context.ReportMessageAsync(EditorFeaturesResources.The_symbol_has_no_base, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs b/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs index 1789c1e5cc44a..d0d1787a4f08e 100644 --- a/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs +++ b/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs @@ -22,7 +22,8 @@ public static ValueTask> FindBasesAsync( namedTypeSymbol.TypeKind == TypeKind.Interface || namedTypeSymbol.TypeKind == TypeKind.Struct)) { - return ValueTaskFactory.FromResult(BaseTypeFinder.FindBaseTypesAndInterfaces(namedTypeSymbol)); + var result = BaseTypeFinder.FindBaseTypesAndInterfaces(namedTypeSymbol).CastArray(); + return ValueTaskFactory.FromResult(result); } if (symbol.Kind == SymbolKind.Property || diff --git a/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs b/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs index 16e189a741f66..2e9b12769efd8 100644 --- a/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs +++ b/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs @@ -6,6 +6,7 @@ using System; using System.ComponentModel.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.CommandHandlers; using Microsoft.CodeAnalysis.Editor.Host; @@ -13,6 +14,7 @@ using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Utilities; using VSCommanding = Microsoft.VisualStudio.Commanding; @@ -32,13 +34,16 @@ public GoToBaseCommandHandler( { } + protected override IGoToBaseService GetService(Document document) + => document?.GetLanguageService(); + public override string DisplayName => EditorFeaturesResources.Go_To_Base; protected override string ScopeDescription => EditorFeaturesResources.Locating_bases; protected override FunctionId FunctionId => FunctionId.CommandHandler_GoToBase; - protected override Task FindActionAsync(IGoToBaseService service, Document document, int caretPosition, IFindUsagesContext context) - => service.FindBasesAsync(document, caretPosition, context); + protected override Task FindActionAsync(IGoToBaseService service, Document document, int caretPosition, IFindUsagesContext context, CancellationToken cancellationToken) + => service.FindBasesAsync(document, caretPosition, context, cancellationToken); } } diff --git a/src/EditorFeatures/Core/GoToBase/IGoToBaseService.cs b/src/EditorFeatures/Core/GoToBase/IGoToBaseService.cs index 0451b71417598..ec7acee0f8a91 100644 --- a/src/EditorFeatures/Core/GoToBase/IGoToBaseService.cs +++ b/src/EditorFeatures/Core/GoToBase/IGoToBaseService.cs @@ -2,8 +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. -#nullable disable - +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; @@ -16,6 +15,6 @@ internal interface IGoToBaseService : ILanguageService /// Finds the base members overridden or implemented by the symbol at the specific position in the document, /// pushing the results into the context instance. /// - Task FindBasesAsync(Document document, int position, IFindUsagesContext context); + Task FindBasesAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs index aa1f5081f9807..16553705d15fc 100644 --- a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs +++ b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs @@ -40,10 +40,28 @@ protected AbstractGoToDefinitionService( async Task?> IGoToDefinitionService.FindDefinitionsAsync(Document document, int position, CancellationToken cancellationToken) => await FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false); + private static bool TryNavigateToSpan(Document document, int position, CancellationToken cancellationToken) + { + var solution = document.Project.Solution; + var workspace = solution.Workspace; + var service = workspace.Services.GetRequiredService(); + + var options = solution.Options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true); + options = options.WithChangedOption(NavigationOptions.ActivateTab, true); + + return service.TryNavigateToPosition(workspace, document.Id, position, virtualSpace: 0, options, cancellationToken); + } + public bool TryGoToDefinition(Document document, int position, CancellationToken cancellationToken) { - // Try to compute the referenced symbol and attempt to go to definition for the symbol. var symbolService = document.GetRequiredLanguageService(); + var targetPositionOfControlFlow = symbolService.GetTargetIfControlFlowAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken); + if (targetPositionOfControlFlow is not null) + { + return TryNavigateToSpan(document, targetPositionOfControlFlow.Value, cancellationToken); + } + + // Try to compute the referenced symbol and attempt to go to definition for the symbol. var (symbol, _) = symbolService.GetSymbolAndBoundSpanAsync(document, position, includeType: true, cancellationToken).WaitAndGetResult(cancellationToken); if (symbol is null) return false; diff --git a/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs b/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs index 12b3ad3078122..b8e4cbd407538 100644 --- a/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs +++ b/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs @@ -75,11 +75,11 @@ public static ImmutableArray GetDefinitions( // // Passing along the classified information is valuable for OOP scenarios where we want // all that expensive computation done on the OOP side and not in the VS side. - // + // // However, Go To Definition is all in-process, and is also synchronous. So we do not - // want to fetch the classifications here. It slows down the command and leads to a + // want to fetch the classifications here. It slows down the command and leads to a // measurable delay in our perf tests. - // + // // So, if we only have a single location to go to, this does no unnecessary work. And, // if we do have multiple locations to show, it will just be done in the BG, unblocking // this command thread so it can return the user faster. @@ -129,5 +129,21 @@ public static bool TryGoToDefinition( streamingPresenter.TryNavigateToOrPresentItemsAsync( threadingContext, solution.Workspace, title, definitions, cancellationToken)); } + + public static bool TryGoToDefinition( + ImmutableArray definitions, + Workspace workspace, + string title, + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingPresenter, + CancellationToken cancellationToken) + { + if (definitions.IsDefaultOrEmpty) + return false; + + return threadingContext.JoinableTaskFactory.Run(() => + streamingPresenter.TryNavigateToOrPresentItemsAsync( + threadingContext, workspace, title, definitions, cancellationToken)); + } } } diff --git a/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs b/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs index d1e60b2e4ca19..a5a72d47229bd 100644 --- a/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs +++ b/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.ComponentModel.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.CommandHandlers; using Microsoft.CodeAnalysis.Editor.Commanding.Commands; @@ -15,6 +14,7 @@ using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Utilities; @@ -39,7 +39,10 @@ public GoToImplementationCommandHandler( protected override FunctionId FunctionId => FunctionId.CommandHandler_GoToImplementation; - protected override Task FindActionAsync(IFindUsagesService service, Document document, int caretPosition, IFindUsagesContext context) - => service.FindImplementationsAsync(document, caretPosition, context); + protected override Task FindActionAsync(IFindUsagesService service, Document document, int caretPosition, IFindUsagesContext context, CancellationToken cancellationToken) + => service.FindImplementationsAsync(document, caretPosition, context, cancellationToken); + + protected override IFindUsagesService? GetService(Document? document) + => document?.GetLanguageService(); } } diff --git a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs index ecbfb2c367b63..a7d4ff2b18eff 100644 --- a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs +++ b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs @@ -32,16 +32,18 @@ internal interface IStreamingFindUsagesPresenter /// It can also show messages about no references being found at the end of the search. /// If false, the presenter will not group by definitions, and will show the definition /// items in isolation. - /// External cancellation token controlling whether finding shoudl be canceled - /// or not. This will be combined with a cancellation token owned by the . - /// Callers should consider to be the source of truth for - /// cancellation from that point on. - FindUsagesContext StartSearch(string title, bool supportsReferences, CancellationToken cancellationToken); + /// A cancellation token that will be triggered if the presenter thinks the search + /// should stop. This can normally happen if the presenter view is closed, or recycled to + /// start a new search in it. Callers should only use this if they intend to report results + /// asynchronously and thus relinquish their own control over cancellation from their own + /// surrounding context. If the caller intends to populate the presenter synchronously, + /// then this cancellation token can be ignored. + (FindUsagesContext context, CancellationToken cancellationToken) StartSearch(string title, bool supportsReferences); /// /// Call this method to display the Containing Type, Containing Member, or Kind columns /// - FindUsagesContext StartSearchWithCustomColumns(string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken); + (FindUsagesContext context, CancellationToken cancellationToken) StartSearchWithCustomColumns(string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn); /// /// Clears all the items from the presenter. @@ -101,17 +103,19 @@ public static async Task TryNavigateToOrPresentItemsAsync( if (presenter != null) { // We have multiple definitions, or we have definitions with multiple locations. Present this to the - // user so they can decide where they want to go to. If we cancel this will trigger the context to - // cancel as well. - var context = presenter.StartSearch(title, supportsReferences: false, cancellationToken); + // user so they can decide where they want to go to. + // + // We ignore the cancellation token returned by StartSearch as we're in a context where + // we've computed all the results and we're synchronously populating the UI with it. + var (context, _) = presenter.StartSearch(title, supportsReferences: false); try { foreach (var definition in nonExternalItems) - await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); + await context.OnDefinitionFoundAsync(definition, cancellationToken).ConfigureAwait(false); } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/EditorFeatures/Core/Host/IWaitContext.cs b/src/EditorFeatures/Core/Host/IWaitContext.cs index fcf23493fd618..61b36400c5674 100644 --- a/src/EditorFeatures/Core/Host/IWaitContext.cs +++ b/src/EditorFeatures/Core/Host/IWaitContext.cs @@ -10,12 +10,14 @@ namespace Microsoft.CodeAnalysis.Editor.Host { + [Obsolete("You should now use UIThreadOperationStatus, which is a platform supported version of this.")] internal enum WaitIndicatorResult { Completed, Canceled, } + [Obsolete("You should now use IUIThreadOperationContext, which is a platform supported version of this.")] internal interface IWaitContext : IDisposable { CancellationToken CancellationToken { get; } diff --git a/src/EditorFeatures/Core/Host/IWaitIndicator.cs b/src/EditorFeatures/Core/Host/IWaitIndicator.cs index 59f6ffd917c78..c0087cb5e99f2 100644 --- a/src/EditorFeatures/Core/Host/IWaitIndicator.cs +++ b/src/EditorFeatures/Core/Host/IWaitIndicator.cs @@ -8,6 +8,7 @@ namespace Microsoft.CodeAnalysis.Editor.Host { + [Obsolete("You should now use IUIThreadOperationExecutor, which is a platform supported version of this. If you have a MEF implementation, you can delete it.")] internal interface IWaitIndicator { /// @@ -17,8 +18,10 @@ internal interface IWaitIndicator IWaitContext StartWait(string title, string message, bool allowCancel, bool showProgress); } + [Obsolete("You should now use IUIThreadOperationExecutor, which is a platform supported version of this.")] internal static class IWaitIndicatorExtensions { + [Obsolete("You should now use IUIThreadOperationExecutor, which is a platform supported version of this.")] public static WaitIndicatorResult Wait( this IWaitIndicator waitIndicator, string title, string message, bool allowCancel, Action action) { diff --git a/src/EditorFeatures/Core/IForegroundNotificationService.cs b/src/EditorFeatures/Core/IForegroundNotificationService.cs deleted file mode 100644 index 8304c010bcebf..0000000000000 --- a/src/EditorFeatures/Core/IForegroundNotificationService.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Threading; -using Microsoft.CodeAnalysis.Shared.TestHooks; - -namespace Microsoft.CodeAnalysis.Editor -{ - /// - /// provide a way to call APIs from UI thread - /// - internal interface IForegroundNotificationService - { - void RegisterNotification(Action action, IAsyncToken asyncToken, CancellationToken cancellationToken); - - void RegisterNotification(Action action, int delayInMS, IAsyncToken asyncToken, CancellationToken cancellationToken); - - /// - /// if action return true, the service will call it back again when it has time. - /// - void RegisterNotification(Func action, IAsyncToken asyncToken, CancellationToken cancellationToken); - - void RegisterNotification(Func action, int delayInMS, IAsyncToken asyncToken, CancellationToken cancellationToken); - } -} diff --git a/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs index ffbefea80ce2b..60e3039d32c5c 100644 --- a/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs @@ -39,19 +39,20 @@ internal class BraceHighlightingViewTaggerProvider : AsynchronousViewTaggerProvi public BraceHighlightingViewTaggerProvider( IThreadingContext threadingContext, IBraceMatchingService braceMatcherService, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.BraceHighlighting), notificationService) + : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.BraceHighlighting)) { _braceMatcherService = braceMatcherService; } + protected override TaggerDelay EventChangeDelay => TaggerDelay.NearImmediate; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { return TaggerEventSources.Compose( - TaggerEventSources.OnTextChanged(subjectBuffer, TaggerDelay.NearImmediate), - TaggerEventSources.OnCaretPositionChanged(textView, subjectBuffer, TaggerDelay.NearImmediate), - TaggerEventSources.OnParseOptionChanged(subjectBuffer, TaggerDelay.NearImmediate)); + TaggerEventSources.OnTextChanged(subjectBuffer), + TaggerEventSources.OnCaretPositionChanged(textView, subjectBuffer), + TaggerEventSources.OnParseOptionChanged(subjectBuffer)); } protected override Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) diff --git a/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs b/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs index 122bee88aa0fb..f428f8836f352 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs @@ -58,6 +58,12 @@ internal sealed class ClassificationTypeDefinitions [BaseDefinition(ClassificationTypeNames.ClassName)] internal readonly ClassificationTypeDefinition UserTypeRecordsTypeDefinition; #endregion + #region User Types - Record Structs + [Export] + [Name(ClassificationTypeNames.RecordStructName)] + [BaseDefinition(ClassificationTypeNames.StructName)] + internal readonly ClassificationTypeDefinition UserTypeRecordStructsTypeDefinition; + #endregion #region User Types - Delegates [Export] [Name(ClassificationTypeNames.DelegateName)] @@ -341,6 +347,13 @@ internal sealed class ClassificationTypeDefinitions internal readonly ClassificationTypeDefinition XmlLiteralTextTypeDefinition; #endregion + #region Reassigned Variable + [Export] + [Name(ClassificationTypeNames.ReassignedVariable)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + internal readonly ClassificationTypeDefinition ReassignedVariableTypeDefinition; + #endregion + #region Static Symbol [Export] [Name(ClassificationTypeNames.StaticSymbol)] diff --git a/src/EditorFeatures/Core/Implementation/Classification/ClassificationUtilities.cs b/src/EditorFeatures/Core/Implementation/Classification/ClassificationUtilities.cs index 59cf82d611f8d..dc89f6ed70fa2 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/ClassificationUtilities.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/ClassificationUtilities.cs @@ -2,13 +2,10 @@ // 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.Concurrent; using System.Collections.Generic; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; @@ -17,34 +14,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification { internal static class ClassificationUtilities { - private static readonly ConcurrentQueue> s_spanCache = new(); - - public static List GetOrCreateClassifiedSpanList() - { - return s_spanCache.TryDequeue(out var result) - ? result - : new List(); - } - - public static void ReturnClassifiedSpanList(List list) - { - if (list == null) - { - return; - } - - list.Clear(); - s_spanCache.Enqueue(list); - } - - public static void Convert(ClassificationTypeMap typeMap, ITextSnapshot snapshot, List list, Action> addTag) - { - foreach (var classifiedSpan in list) - { - addTag(Convert(typeMap, snapshot, classifiedSpan)); - } - } - public static TagSpan Convert(ClassificationTypeMap typeMap, ITextSnapshot snapshot, ClassifiedSpan classifiedSpan) { return new TagSpan( @@ -52,11 +21,12 @@ public static TagSpan Convert(ClassificationTypeMap typeMap, new ClassificationTag(typeMap.GetClassificationType(classifiedSpan.ClassificationType))); } - public static List> ConvertAndReturnList(ClassificationTypeMap typeMap, ITextSnapshot snapshot, List classifiedSpans) + public static List> Convert(ClassificationTypeMap typeMap, ITextSnapshot snapshot, ArrayBuilder classifiedSpans) { - var result = new List>(); - Convert(typeMap, snapshot, classifiedSpans, result.Add); - ReturnClassifiedSpanList(classifiedSpans); + var result = new List>(capacity: classifiedSpans.Count); + foreach (var span in classifiedSpans) + result.Add(Convert(typeMap, snapshot, span)); + return result; } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs b/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs index 5b9a2c7f42e2f..e2049b312eb9e 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs @@ -3,9 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Shared.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; @@ -22,7 +21,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification internal class CompilationAvailableTaggerEventSource : ITaggerEventSource { private readonly ITextBuffer _subjectBuffer; - private readonly TaggerDelay _delay; private readonly IAsynchronousOperationListener _asyncListener; /// @@ -32,23 +30,18 @@ internal class CompilationAvailableTaggerEventSource : ITaggerEventSource private readonly ITaggerEventSource _underlyingSource; /// - /// Queue of work to go compute the compilations. + /// Cancellation tokens controlling background computation of the compilation. /// - private readonly AsynchronousSerialWorkQueue _workQueue; + private readonly CancellationSeries _cancellationSeries = new(); public CompilationAvailableTaggerEventSource( ITextBuffer subjectBuffer, - TaggerDelay delay, - IThreadingContext threadingContext, IAsynchronousOperationListener asyncListener, params ITaggerEventSource[] eventSources) { _subjectBuffer = subjectBuffer; - _delay = delay; _asyncListener = asyncListener; _underlyingSource = TaggerEventSources.Compose(eventSources); - - _workQueue = new AsynchronousSerialWorkQueue(threadingContext, asyncListener); } public event EventHandler? Changed; @@ -64,19 +57,7 @@ public void Disconnect() { _underlyingSource.Changed -= OnEventSourceChanged; _underlyingSource.Disconnect(); - _workQueue.CancelCurrentWork(); - } - - public event EventHandler UIUpdatesPaused - { - add { _underlyingSource.UIUpdatesPaused += value; } - remove { _underlyingSource.UIUpdatesPaused -= value; } - } - - public event EventHandler UIUpdatesResumed - { - add { _underlyingSource.UIUpdatesResumed += value; } - remove { _underlyingSource.UIUpdatesResumed -= value; } + _cancellationSeries.Dispose(); } private void OnEventSourceChanged(object? sender, TaggerEventArgs args) @@ -91,18 +72,18 @@ private void OnEventSourceChanged(object? sender, TaggerEventArgs args) if (!document.SupportsSemanticModel) return; - // Now, attempt to cancel any existing work to get the compilation for this project, and kick off a new - // piece of work in the future to do so. Do this after a delay so we can appropriately throttle ourselves - // if we hear about a flurry of notifications. - _workQueue.CancelCurrentWork(); - _workQueue.EnqueueBackgroundTask(async c => - { - await document.Project.GetCompilationAsync(c).ConfigureAwait(false); - this.Changed?.Invoke(this, new TaggerEventArgs(_delay)); - }, - $"{nameof(CompilationAvailableTaggerEventSource)}.{nameof(OnEventSourceChanged)}", - 500, - _workQueue.CancellationToken); + // Cancel any existing tasks that are computing the compilation and spawn a new one to compute + // it and notify any listening clients. + var cancellationToken = _cancellationSeries.CreateNext(); + + var token = _asyncListener.BeginAsyncOperation(nameof(OnEventSourceChanged)); + var task = Task.Run(async () => + { + await Task.Delay(500, cancellationToken).ConfigureAwait(false); + await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + this.Changed?.Invoke(this, new TaggerEventArgs()); + }, cancellationToken); + task.CompletesAsyncOperation(token); } } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs index de4556934d867..b0af340e38ec4 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.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.Collections.Generic; using System.Diagnostics; @@ -17,7 +15,6 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; using Roslyn.Utilities; @@ -31,10 +28,12 @@ private class Tagger : ForegroundThreadAffinitizedObject, IAccurateTagger _cachedTags_doNotAccessDirectly; - private SnapshotSpan? _cachedTaggedSpan_doNotAccessDirectly; + // State for the tagger. Can be accessed from any thread. Access should be protected by _gate. + + private readonly object _gate = new(); + private TagSpanIntervalTree? _cachedTags; + private SnapshotSpan? _cachedTaggedSpan; public Tagger( CopyPasteAndPrintingClassificationBufferTaggerProvider owner, @@ -45,87 +44,49 @@ public Tagger( _owner = owner; _subjectBuffer = subjectBuffer; - const TaggerDelay Delay = TaggerDelay.Short; - // Note: because we use frozen-partial documents for semantic classification, we may end up with incomplete // semantics (esp. during solution load). Because of this, we also register to hear when the full // compilation is available so that reclassify and bring ourselves up to date. _eventSource = new CompilationAvailableTaggerEventSource( - subjectBuffer, Delay, - owner.ThreadingContext, + subjectBuffer, asyncListener, - TaggerEventSources.OnWorkspaceChanged(subjectBuffer, Delay, asyncListener), - TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer, Delay)); + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, asyncListener), + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer)); - ConnectToEventSource(); + _eventSource.Changed += OnEventSourceChanged; + _eventSource.Connect(); } + // Explicitly a no-op. This classifier does not support change notifications. See comment in + // OnEventSourceChanged_OnForeground for more details. + public event EventHandler TagsChanged { add { } remove { } } + public void Dispose() { this.AssertIsForeground(); - _cancellationTokenSource.Cancel(); _eventSource.Changed -= OnEventSourceChanged; _eventSource.Disconnect(); } - private void ConnectToEventSource() - { - _eventSource.Changed += OnEventSourceChanged; - _eventSource.Connect(); - } - - private TagSpanIntervalTree CachedTags - { - get - { - this.AssertIsForeground(); - return _cachedTags_doNotAccessDirectly; - } - - set - { - this.AssertIsForeground(); - _cachedTags_doNotAccessDirectly = value; - } - } - - private SnapshotSpan? CachedTaggedSpan + private void OnEventSourceChanged(object? sender, TaggerEventArgs _) { - get + lock (_gate) { - this.AssertIsForeground(); - return _cachedTaggedSpan_doNotAccessDirectly; + _cachedTags = null; + _cachedTaggedSpan = null; } - set - { - this.AssertIsForeground(); - _cachedTaggedSpan_doNotAccessDirectly = value; - } - } - - private void OnEventSourceChanged(object sender, TaggerEventArgs e) - { - _owner._notificationService.RegisterNotification( - OnEventSourceChanged_OnForeground, - _owner._asyncListener.BeginAsyncOperation("SemanticClassificationBufferTaggerProvider"), - _cancellationTokenSource.Token); + // Note: we explicitly do *not* call into TagsChanged here. This type exists only for the copy/paste + // scenario, and in the case the editor always calls into us for the span in question, ignoring + // TagsChanged, as per DPugh: + // + // For rich text copy, we always call the buffer classifier to get the classifications of the copied + // text. It ignores any tags changed events. + // + // It's important that we do not call TagsChanged here as the only thing we could do is notify that the + // entire doc is changed, and that incurs a heavy cost for the editor reacting to that notification. } - private void OnEventSourceChanged_OnForeground() - { - this.AssertIsForeground(); - - // When something changes, clear the cached data we have. - this.CachedTags = null; - this.CachedTaggedSpan = null; - - // And notify any concerned parties that we have new tags. - this.TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(_subjectBuffer.CurrentSnapshot.GetFullSpan())); - } - - public event EventHandler TagsChanged; - public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { this.AssertIsForeground(); @@ -138,66 +99,65 @@ public IEnumerable> GetAllTags(NormalizedSnapshotSp { this.AssertIsForeground(); if (spans.Count == 0) - { return Array.Empty>(); - } var firstSpan = spans.First(); var snapshot = firstSpan.Snapshot; Debug.Assert(snapshot.TextBuffer == _subjectBuffer); + var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + return Array.Empty>(); + // We want to classify from the start of the first requested span to the end of the // last requested span. - var spanToTag = new SnapshotSpan(snapshot, - Span.FromBounds(spans.First().Start, spans.Last().End)); + var spanToTag = new SnapshotSpan(snapshot, Span.FromBounds(spans.First().Start, spans.Last().End)); + + GetCachedInfo(out var cachedTaggedSpan, out var cachedTags); // We don't need to actually classify if what we're being asked for is a subspan // of the last classification we performed. - var cachedTaggedSpan = this.CachedTaggedSpan; var canReuseCache = cachedTaggedSpan?.Snapshot == snapshot && cachedTaggedSpan.Value.Contains(spanToTag); if (!canReuseCache) { - // Our cache is not there, or is out of date. We need to compute the up to date - // results. - - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - { - return Array.Empty>(); - } - + // Our cache is not there, or is out of date. We need to compute the up to date results. var context = new TaggerContext(document, snapshot, cancellationToken: cancellationToken); - var task = ProduceTagsAsync( - context, new DocumentSnapshotSpan(document, spanToTag), _owner._typeMap); - - task.Wait(cancellationToken); + this.ThreadingContext.JoinableTaskFactory.Run( + () => ProduceTagsAsync(context, new DocumentSnapshotSpan(document, spanToTag), _owner._typeMap)); - CachedTaggedSpan = spanToTag; - CachedTags = new TagSpanIntervalTree(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans); - } + cachedTaggedSpan = spanToTag; + cachedTags = new TagSpanIntervalTree(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans); - if (this.CachedTags == null) - { - return Array.Empty>(); + lock (_gate) + { + _cachedTaggedSpan = cachedTaggedSpan; + _cachedTags = cachedTags; + } } - return this.CachedTags.GetIntersectingTagSpans(spans); + return cachedTags == null + ? Array.Empty>() + : cachedTags.GetIntersectingTagSpans(spans); } - private static Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSpan, ClassificationTypeMap typeMap) + private void GetCachedInfo(out SnapshotSpan? cachedTaggedSpan, out TagSpanIntervalTree? cachedTags) { - var document = documentSpan.Document; - - var classificationService = document.GetLanguageService(); - if (classificationService != null) + lock (_gate) { - return SemanticClassificationUtilities.ProduceTagsAsync(context, documentSpan, classificationService, typeMap); + cachedTaggedSpan = _cachedTaggedSpan; + cachedTags = _cachedTags; } + } - return Task.CompletedTask; + private static Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSpan, ClassificationTypeMap typeMap) + { + var classificationService = documentSpan.Document.GetLanguageService(); + return classificationService != null + ? SemanticClassificationUtilities.ProduceTagsAsync(context, documentSpan, classificationService, typeMap) + : Task.CompletedTask; } } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs index f2037a6b729b0..a6118f3c23c73 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs @@ -29,19 +29,16 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification internal partial class CopyPasteAndPrintingClassificationBufferTaggerProvider : ForegroundThreadAffinitizedObject, ITaggerProvider { private readonly IAsynchronousOperationListener _asyncListener; - private readonly IForegroundNotificationService _notificationService; private readonly ClassificationTypeMap _typeMap; [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CopyPasteAndPrintingClassificationBufferTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, ClassificationTypeMap typeMap, IAsynchronousOperationListenerProvider listenerProvider) : base(threadingContext) { - _notificationService = notificationService; _typeMap = typeMap; _asyncListener = listenerProvider.GetListener(FeatureAttribute.Classification); } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs index 9ccb7597b0ae6..21a437c43f669 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -145,13 +146,13 @@ private static async Task ClassifySpansAsync( var cancellationToken = context.CancellationToken; using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken)) { - var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList(); + using var _ = ArrayBuilder.GetInstance(out var classifiedSpans); await AddSemanticClassificationsAsync( document, snapshotSpan.Span.ToTextSpan(), classificationService, classifiedSpans, cancellationToken: cancellationToken).ConfigureAwait(false); - ClassificationUtilities.Convert(typeMap, snapshotSpan.Snapshot, classifiedSpans, context.AddTag); - ClassificationUtilities.ReturnClassifiedSpanList(classifiedSpans); + foreach (var span in classifiedSpans) + context.AddTag(ClassificationUtilities.Convert(typeMap, snapshotSpan.Snapshot, span)); var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); @@ -170,7 +171,7 @@ private static async Task AddSemanticClassificationsAsync( Document document, TextSpan textSpan, IClassificationService classificationService, - List classifiedSpans, + ArrayBuilder classifiedSpans, CancellationToken cancellationToken) { var workspaceStatusService = document.Project.Solution.Workspace.Services.GetRequiredService(); @@ -195,7 +196,7 @@ await classificationService.AddSemanticClassificationsAsync( private static async Task TryAddSemanticClassificationsFromCacheAsync( Document document, TextSpan textSpan, - List classifiedSpans, + ArrayBuilder classifiedSpans, bool isFullyLoaded, CancellationToken cancellationToken) { diff --git a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs index 2c6358d277f9c..7bb37c635ad53 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.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.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; @@ -16,7 +14,6 @@ using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; -using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -49,35 +46,32 @@ internal partial class SemanticClassificationViewTaggerProvider : AsynchronousVi [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public SemanticClassificationViewTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, ClassificationTypeMap typeMap, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.Classification), notificationService) + : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.Classification)) { _typeMap = typeMap; } + protected override TaggerDelay EventChangeDelay => TaggerDelay.Short; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { this.AssertIsForeground(); - const TaggerDelay Delay = TaggerDelay.Short; // Note: we don't listen for OnTextChanged. They'll get reported by the ViewSpan changing and also the // SemanticChange notification. // - // Note: when the user scrolls, we will try to reclassify as soon as possible. That way we appear - // semantically unclassified for a very short amount of time. - // // Note: because we use frozen-partial documents for semantic classification, we may end up with incomplete // semantics (esp. during solution load). Because of this, we also register to hear when the full // compilation is available so that reclassify and bring ourselves up to date. return new CompilationAvailableTaggerEventSource( - subjectBuffer, Delay, - ThreadingContext, + subjectBuffer, AsyncListener, - TaggerEventSources.OnViewSpanChanged(ThreadingContext, textView, textChangeDelay: Delay, scrollChangeDelay: TaggerDelay.NearImmediate), - TaggerEventSources.OnWorkspaceChanged(subjectBuffer, Delay, this.AsyncListener), - TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer, Delay)); + TaggerEventSources.OnViewSpanChanged(ThreadingContext, textView), + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, this.AsyncListener), + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer), + TaggerEventSources.OnOptionChanged(subjectBuffer, ClassificationOptions.ClassifyReassignedVariables)); } protected override IEnumerable GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer) @@ -103,23 +97,21 @@ protected override Task ProduceTagsAsync(TaggerContext conte var spanToTag = context.SpansToTag.Single(); var document = spanToTag.Document; + if (document == null) + return Task.CompletedTask; // Attempt to get a classification service which will actually produce the results. // If we can't (because we have no Document, or because the language doesn't support // this service), then bail out immediately. - var classificationService = document?.GetLanguageService(); + var classificationService = document.GetLanguageService(); if (classificationService == null) - { return Task.CompletedTask; - } // The LSP client will handle producing tags when running under the LSP editor. // Our tagger implementation should return nothing to prevent conflicts. - var workspaceContextService = document?.Project.Solution.Workspace.Services.GetRequiredService(); + var workspaceContextService = document.Project.Solution.Workspace.Services.GetRequiredService(); if (workspaceContextService?.IsInLspEditorContext() == true) - { return Task.CompletedTask; - } return SemanticClassificationUtilities.ProduceTagsAsync(context, spanToTag, classificationService, _typeMap); } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.LastLineCache.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.LastLineCache.cs index 586bf3af3ae0a..de61f30ff1020 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.LastLineCache.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.LastLineCache.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification @@ -17,59 +16,53 @@ internal partial class TagComputer /// /// it is a helper class that encapsulates logic on holding onto last classification result /// - private class LastLineCache + private class LastLineCache : ForegroundThreadAffinitizedObject { // this helper class is primarily to improve active typing perf. don't bother to cache // something very big. private const int MaxClassificationNumber = 32; - private readonly object _gate = new(); // mutating state private SnapshotSpan _span; - private List _classifications; + private readonly ArrayBuilder _classifications = new(); - public LastLineCache() - => this.Clear(); + public LastLineCache(IThreadingContext threadingContext) : base(threadingContext) + { + } private void Clear() { - lock (_gate) - { - _span = default; - ClassificationUtilities.ReturnClassifiedSpanList(_classifications); - _classifications = null; - } + this.AssertIsForeground(); + + _span = default; + _classifications.Clear(); } - public bool TryUseCache(SnapshotSpan span, out List classifications) + public bool TryUseCache(SnapshotSpan span, ArrayBuilder classifications) { - lock (_gate) - { - // currently, it is using SnapshotSpan even though holding onto it could be - // expensive. reason being it should be very soon sync-ed to latest snapshot. - if (_classifications != null && _span.Equals(span)) - { - classifications = _classifications; - return true; - } + this.AssertIsForeground(); - this.Clear(); - classifications = null; - return false; + // currently, it is using SnapshotSpan even though holding onto it could be + // expensive. reason being it should be very soon sync-ed to latest snapshot. + if (_span.Equals(span)) + { + classifications.AddRange(_classifications); + return true; } + + this.Clear(); + return false; } - public void Update(SnapshotSpan span, List classifications) + public void Update(SnapshotSpan span, ArrayBuilder classifications) { - lock (_gate) - { - this.Clear(); + this.AssertIsForeground(); + this.Clear(); - if (classifications.Count < MaxClassificationNumber) - { - _span = span; - _classifications = classifications; - } + if (classifications.Count < MaxClassificationNumber) + { + _span = span; + _classifications.AddRange(classifications); } } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index c50ebf1186e89..d4d083cd5eaca 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -2,21 +2,19 @@ // 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 System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Shared.Threading; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Experiments; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -36,527 +34,451 @@ internal partial class SyntacticClassificationTaggerProvider /// will determine which sections of the file changed and we will use that to notify the editor /// about what needs to be reclassified. /// - internal partial class TagComputer + internal partial class TagComputer : ForegroundThreadAffinitizedObject { - // This is how long we will wait after completing a parse before we tell the editor to - // re-classify the file. We do this, since the edit may have non-local changes, or the user - // may have made a change that introduced text that we didn't classify because we hadn't - // parsed it yet, and we want to get back to a known state. - private const int ReportChangeDelayInMilliseconds = TaggerConstants.ShortDelay; + private readonly SyntacticClassificationTaggerProvider _taggerProvider; + private readonly ITextBuffer2 _subjectBuffer; + private readonly IAsynchronousOperationListener _listener; + private readonly ClassificationTypeMap _typeMap; - private readonly ITextBuffer _subjectBuffer; private readonly WorkspaceRegistration _workspaceRegistration; - private readonly AsynchronousSerialWorkQueue _workQueue; - // this will cache previous classification information for a span, so that we can avoid - // digging into same tree again and again to find exactly same answer - private readonly LastLineCache _lastLineCache; + private readonly CancellationTokenSource _disposalCancellationSource = new(); + + /// + /// Work queue we use to batch up notifications about changes that will cause + /// us to classify. This ensures that if we hear a flurry of changes, we don't + /// kick off an excessive amount of background work to process them. + /// + private readonly AsyncBatchingWorkQueue _workQueue; + + /// + /// Timeout before we cancel the work to diff and return whatever we have. + /// + private readonly TimeSpan _diffTimeout; + + private Workspace? _workspace; // The latest data about the document being classified that we've cached. objects can - // be accessed from both threads, and must be obtained when this lock is held. + // be accessed from both threads, and must be obtained when this lock is held. // - // Note: we cache this data once we've retrieved the actual syntax tree for a document. This - // way, when we call into the actual classification service, it should be very quick for the - // it to get the tree if it needs it. - private readonly object _gate = new(); - private ITextSnapshot _lastProcessedSnapshot; - private Document _lastProcessedDocument; + // _lastProcessedData.lastRoot is optional and is held onto for languages that support syntax. + // it allows computing the root, and changed-spans, in the background so that we can + // report a smaller change range, and have the data ready for classifying with GetTags + // get called. - private Workspace _workspace; - private CancellationTokenSource _reportChangeCancellationSource; + private readonly object _gate = new(); + private (ITextSnapshot? lastSnapshot, Document? lastDocument, SyntaxNode? lastRoot) _lastProcessedData; - private readonly IAsynchronousOperationListener _listener; - private readonly IForegroundNotificationService _notificationService; - private readonly ClassificationTypeMap _typeMap; - private readonly SyntacticClassificationTaggerProvider _taggerProvider; + // this will cache previous classification information for a span, so that we can avoid + // digging into same tree again and again to find exactly same answer + private readonly LastLineCache _lastLineCache; private int _taggerReferenceCount; public TagComputer( - ITextBuffer subjectBuffer, - IForegroundNotificationService notificationService, + SyntacticClassificationTaggerProvider taggerProvider, + ITextBuffer2 subjectBuffer, IAsynchronousOperationListener asyncListener, ClassificationTypeMap typeMap, - SyntacticClassificationTaggerProvider taggerProvider) + TimeSpan diffTimeout) + : base(taggerProvider.ThreadingContext, assertIsForeground: false) { + _taggerProvider = taggerProvider; _subjectBuffer = subjectBuffer; - _notificationService = notificationService; _listener = asyncListener; _typeMap = typeMap; - _taggerProvider = taggerProvider; + _diffTimeout = diffTimeout; + _workQueue = new AsyncBatchingWorkQueue( + TimeSpan.FromMilliseconds(TaggerConstants.NearImmediateDelay), + ProcessChangesAsync, + equalityComparer: null, + asyncListener, + _disposalCancellationSource.Token); - _workQueue = new AsynchronousSerialWorkQueue(taggerProvider._threadingContext, asyncListener); - _reportChangeCancellationSource = new CancellationTokenSource(); - - _lastLineCache = new LastLineCache(); + _lastLineCache = new LastLineCache(taggerProvider.ThreadingContext); _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer()); _workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged; if (_workspaceRegistration.Workspace != null) - { ConnectToWorkspace(_workspaceRegistration.Workspace); - } + + _subjectBuffer.ChangedOnBackground += this.OnSubjectBufferChanged; + } + + public event EventHandler? TagsChanged; + + private IClassificationService? TryGetClassificationService(ITextSnapshot snapshot) + => _workspace?.Services.GetLanguageServices(snapshot.ContentType)?.GetService(); + + #region Workspace Hookup + + private void OnWorkspaceRegistrationChanged(object? sender, EventArgs e) + { + var token = _listener.BeginAsyncOperation(nameof(OnWorkspaceRegistrationChanged)); + var task = SwitchToMainThreadAndHookupWorkspaceAsync(); + task.CompletesAsyncOperation(token); } - private void OnWorkspaceRegistrationChanged(object sender, EventArgs e) + private async Task SwitchToMainThreadAndHookupWorkspaceAsync() { - // We both try to connect synchronously, and register for workspace registration events. - // It's possible (particularly in tests), to connect in the startup path, but then get a - // previously scheduled, but not yet delivered event. Don't bother connecting to the - // same workspace again in that case. - var newWorkspace = _workspaceRegistration.Workspace; - if (newWorkspace == _workspace) + try { - return; - } + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_disposalCancellationSource.Token); - DisconnectFromWorkspace(); + // We both try to connect synchronously, and register for workspace registration events. + // It's possible (particularly in tests), to connect in the startup path, but then get a + // previously scheduled, but not yet delivered event. Don't bother connecting to the + // same workspace again in that case. + var newWorkspace = _workspaceRegistration.Workspace; + if (newWorkspace == _workspace) + return; - if (newWorkspace != null) + DisconnectFromWorkspace(); + + if (newWorkspace != null) + ConnectToWorkspace(newWorkspace); + } + catch (OperationCanceledException) { - ConnectToWorkspace(newWorkspace); + // can happen if we were disposed of. + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + // We were in a fire and forget task. So just report the NFW and do not let + // this bleed out to an unhandled exception. } } internal void IncrementReferenceCount() - => _taggerReferenceCount++; + { + this.AssertIsForeground(); + _taggerReferenceCount++; + } - internal void DecrementReferenceCountAndDisposeIfNecessary() + internal void DecrementReferenceCount() { + this.AssertIsForeground(); _taggerReferenceCount--; if (_taggerReferenceCount == 0) { + // stop any bg work we're doing. + _disposalCancellationSource.Cancel(); + + _subjectBuffer.ChangedOnBackground -= this.OnSubjectBufferChanged; + DisconnectFromWorkspace(); _workspaceRegistration.WorkspaceChanged -= OnWorkspaceRegistrationChanged; _taggerProvider.DisconnectTagComputer(_subjectBuffer); } } - private void ResetLastParsedDocument() - { - lock (_gate) - { - _lastProcessedDocument = null; - } - } - private void ConnectToWorkspace(Workspace workspace) { - ResetLastParsedDocument(); + this.AssertIsForeground(); _workspace = workspace; _workspace.WorkspaceChanged += this.OnWorkspaceChanged; - _workspace.DocumentOpened += this.OnDocumentOpened; _workspace.DocumentActiveContextChanged += this.OnDocumentActiveContextChanged; - var textContainer = _subjectBuffer.AsTextContainer(); - - var documentId = _workspace.GetDocumentIdInCurrentContext(textContainer); - if (documentId != null) - { - var document = workspace.CurrentSolution.GetDocument(documentId); - if (document != null) - { - EnqueueProcessSnapshot(document); - } - } + // Now that we've connected to the workspace, kick off work to reclassify this buffer. + _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); } public void DisconnectFromWorkspace() { - _reportChangeCancellationSource.Cancel(); + this.AssertIsForeground(); + + lock (_gate) + { + _lastProcessedData = default; + } if (_workspace != null) { _workspace.WorkspaceChanged -= this.OnWorkspaceChanged; - _workspace.DocumentOpened -= this.OnDocumentOpened; _workspace.DocumentActiveContextChanged -= this.OnDocumentActiveContextChanged; _workspace = null; - ResetLastParsedDocument(); + // Now that we've disconnected to the workspace, kick off work to reclassify this buffer. + _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); } } - private void EnqueueProcessSnapshot(Document newDocument) + #endregion + + #region Event Handling + + private void OnSubjectBufferChanged(object? sender, TextContentChangedEventArgs args) { - if (newDocument != null) - { - _workQueue.EnqueueBackgroundTask(c => this.EnqueueProcessSnapshotWorkerAsync(newDocument, c), GetType() + ".EnqueueParseSnapshotTask.1", CancellationToken.None); - } + // we know a change to our buffer is always affecting this document. So we can + // just enqueue the work to reclassify things unilaterally. + _workQueue.AddWork(args.After); } - private async Task EnqueueProcessSnapshotWorkerAsync(Document document, CancellationToken cancellationToken) + private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs args) { - // we will enqueue new one soon, cancel pending refresh right away - _reportChangeCancellationSource.Cancel(); - - var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var snapshot = newText.FindCorrespondingEditorTextSnapshot(); - if (snapshot == null) - { - // It's possible that we're seeing a notification for an update that happened - // just before the file was opened, and so the document we're given is still the - // old one. + if (_workspace == null) return; - } - - var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); - using (latencyTracker) - { - // preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready. - // F#/typescript and other languages that doesn't support syntax tree will return null here. - _ = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - } - lock (_gate) - { - _lastProcessedSnapshot = snapshot; - _lastProcessedDocument = document; - } + var documentId = args.NewActiveContextDocumentId; + var bufferDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); + if (bufferDocumentId != documentId) + return; - _reportChangeCancellationSource = new CancellationTokenSource(); - _notificationService.RegisterNotification(() => - { - _workQueue.AssertIsForeground(); - ReportChangedSpan(snapshot.GetFullSpan()); - }, - ReportChangeDelayInMilliseconds, - _listener.BeginAsyncOperation("ReportEntireFileChanged"), - _reportChangeCancellationSource.Token); + _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); } - private void ReportChangedSpan(SnapshotSpan changeSpan) + private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) { - lock (_gate) - { - var snapshot = _lastProcessedSnapshot; - if (snapshot.Version.ReiteratedVersionNumber != changeSpan.Snapshot.Version.ReiteratedVersionNumber) - { - // wait for next call - return; - } - } + // We may be getting an event for a workspace we already disconnected from. If so, + // ignore them. We won't be able to find the Document corresponding to our text buffer, + // so we can't reasonably classify this anyways. + if (args.NewSolution.Workspace != _workspace) + return; - this.TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(changeSpan)); - } + if (args.Kind != WorkspaceChangeKind.ProjectChanged) + return; - public event EventHandler TagsChanged; + var documentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); + if (args.ProjectId != documentId?.ProjectId) + return; - public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) - { - using (Logger.LogBlock(FunctionId.Tagger_SyntacticClassification_TagComputer_GetTags, CancellationToken.None)) - { - if (spans.Count > 0 && _workspace != null) - { - var firstSpan = spans[0]; - var languageServices = _workspace.Services.GetLanguageServices(firstSpan.Snapshot.ContentType); - if (languageServices != null) - { - var result = GetTags(spans, languageServices); - if (result != null) - { - return result; - } - } - } + var oldProject = args.OldSolution.GetProject(args.ProjectId); + var newProject = args.NewSolution.GetProject(args.ProjectId); - return SpecializedCollections.EmptyEnumerable>(); - } + // In case of parse options change reclassify the doc as it may have affected things + // like preprocessor directives. + if (Equals(oldProject?.ParseOptions, newProject?.ParseOptions)) + return; + + _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); } - private IEnumerable> GetTags( - NormalizedSnapshotSpanCollection spans, HostLanguageServices languageServices) + #endregion + + /// + /// Parses the document in the background and determines what has changed to report to + /// the editor. Calls to are serialized by + /// so we don't need to worry about multiple calls to this happening concurrently. + /// + private async Task ProcessChangesAsync(ImmutableArray snapshots, CancellationToken cancellationToken) { - var classificationService = languageServices.GetService(); + // We have potentially heard about several changes to the subject buffer. However + // we only need to process the latest once. + Contract.ThrowIfTrue(snapshots.IsDefaultOrEmpty); + var currentSnapshot = GetLatest(snapshots); + + var classificationService = TryGetClassificationService(currentSnapshot); if (classificationService == null) - { - return null; - } + return; - var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList(); + var currentDocument = currentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (currentDocument == null) + return; - foreach (var span in spans) - { - AddClassifiedSpans(classificationService, span, classifiedSpans); - } + var (previousSnapshot, previousDocument, previousRoot) = GetLastProcessedData(); - return ClassificationUtilities.ConvertAndReturnList( - _typeMap, spans[0].Snapshot, classifiedSpans); - } + // Optionally pre-calculate the root of the doc so that it is ready to classify + // once GetTags is called. Also, attempt to determine a smallwe change range span + // for this document so that we can avoid reporting the entire document as changed. - private void AddClassifiedSpans( - IClassificationService classificationService, - SnapshotSpan span, - List classifiedSpans) - { - // First, get the tree and snapshot that we'll be operating over. - // From this point on we'll do all operations over these values. - ITextSnapshot lastSnapshot; - Document lastDocument; + var currentRoot = await currentDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var changedSpan = await ComputeChangedSpanAsync().ConfigureAwait(false); lock (_gate) { - lastSnapshot = _lastProcessedSnapshot; - lastDocument = _lastProcessedDocument; + _lastProcessedData = (currentSnapshot, currentDocument, currentRoot); } - if (lastDocument == null) - { - // We don't have a syntax tree yet. Just do a lexical classification of the document. - AddClassifiedSpansForTokens(classificationService, span, classifiedSpans); - return; - } + // Notify the editor now that there were changes. Note: we do not need to go the + // UI to do this. The editor supports TagsChanged being called on any thread. + this.TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(changedSpan)); - // We have a tree. However, the tree may be for an older version of the snapshot. - // If it is for an older version, then classify that older version and translate - // the classifications forward. Otherwise, just classify normally. + return; - if (lastSnapshot.Version.ReiteratedVersionNumber == span.Snapshot.Version.ReiteratedVersionNumber) + static ITextSnapshot GetLatest(ImmutableArray snapshots) { - AddClassifiedSpansForCurrentTree( - classificationService, span, lastDocument, classifiedSpans); + var latest = snapshots[0]; + for (var i = 1; i < snapshots.Length; i++) + { + var snapshot = snapshots[i]; + if (snapshot.Version.VersionNumber > latest.Version.VersionNumber) + latest = snapshot; + } + + return latest; } - else + + async ValueTask ComputeChangedSpanAsync() { - // Slightly more complicated. We have a parse tree, it's just not for the snapshot - // we're being asked for. - AddClassifiedSpansForPreviousTree( - classificationService, span, lastSnapshot, lastDocument, classifiedSpans); + var changeRange = await ComputeChangedRangeAsync().ConfigureAwait(false); + return changeRange != null + ? currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength) + : currentSnapshot.GetFullSpan(); } - } - private void AddClassifiedSpansForCurrentTree( - IClassificationService classificationService, - SnapshotSpan span, - Document document, - List classifiedSpans) - { - if (!_lastLineCache.TryUseCache(span, out var tempList)) + ValueTask ComputeChangedRangeAsync() { - tempList = ClassificationUtilities.GetOrCreateClassifiedSpanList(); + // If we have syntax available fast path the change computation without async or blocking. + if (previousRoot != null && currentRoot != null) + return new(classificationService.ComputeSyntacticChangeRange(currentDocument.Project.Solution.Workspace, previousRoot, currentRoot, _diffTimeout, cancellationToken)); - classificationService.AddSyntacticClassificationsAsync( - document, span.Span.ToTextSpan(), tempList, CancellationToken.None).Wait(CancellationToken.None); + // Otherwise, fall back to the language to compute the difference based on the document contents. + if (previousDocument != null) + return classificationService.ComputeSyntacticChangeRangeAsync(previousDocument, currentDocument, _diffTimeout, cancellationToken); - _lastLineCache.Update(span, tempList); + return new ValueTask(); } + } + + private (ITextSnapshot? lastSnapshot, Document? lastDocument, SyntaxNode? lastRoot) GetLastProcessedData() + { + lock (_gate) + return _lastProcessedData; + } - // simple case. They're asking for the classifications for a tree that we already have. - // Just get the results from the tree and return them. + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + { + this.AssertIsForeground(); - classifiedSpans.AddRange(tempList); + using (Logger.LogBlock(FunctionId.Tagger_SyntacticClassification_TagComputer_GetTags, CancellationToken.None)) + { + return GetTagsWorker(spans) ?? SpecializedCollections.EmptyEnumerable>(); + } } - private void AddClassifiedSpansForPreviousTree( - IClassificationService classificationService, - SnapshotSpan span, - ITextSnapshot lastSnapshot, - Document lastDocument, - List classifiedSpans) + private IEnumerable>? GetTagsWorker(NormalizedSnapshotSpanCollection spans) { - // Slightly more complicated case. They're asking for the classifications for a - // different snapshot than what we have a parse tree for. So we first translate the span - // that they're asking for so that is maps onto the tree that we have spans for. We then - // get the classifications from that tree. We then take the results and translate them - // back to the snapshot they want. Finally, as some of the classifications may have - // changed, we check for some common cases and touch them up manually so that things - // look right for the user. + this.AssertIsForeground(); + if (spans.Count == 0 || _workspace == null) + return null; - // Note the handling of SpanTrackingModes here: We do EdgeExclusive while mapping back , - // and EdgeInclusive mapping forward. What I've convinced myself is that EdgeExclusive - // is best when mapping back over a deletion, so that we don't end up classifying all of - // the deleted code. In most addition/modification cases, there will be overlap with - // existing spans, and so we'll end up classifying well. In the worst case, there is a - // large addition that doesn't exist when we map back, and so we don't have any - // classifications for it. That's probably okay, because: + var snapshot = spans[0].Snapshot; - // 1. If it's that large, it's likely that in reality there are multiple classification - // spans within it. + var classificationService = TryGetClassificationService(snapshot); + if (classificationService == null) + return null; - // 2.We'll eventually call ClassificationsChanged and re-classify that region anyway. + using var _ = ArrayBuilder.GetInstance(out var classifiedSpans); - // When mapping back forward, we use EdgeInclusive so that in the common typing cases we - // don't end up with half a token colored differently than the other half. + foreach (var span in spans) + AddClassifications(span); - // See bugs like http://vstfdevdiv:8080/web/wi.aspx?id=6176 for an example of what can - // happen when this goes wrong. + return ClassificationUtilities.Convert(_typeMap, snapshot, classifiedSpans); - // 1) translate the requested span onto the right span for the snapshot that corresponds - // to the syntax tree. - var translatedSpan = span.TranslateTo(lastSnapshot, SpanTrackingMode.EdgeExclusive); - if (translatedSpan.IsEmpty) + void AddClassifications(SnapshotSpan span) { - // well, there is no information we can get from previous tree, use lexer to - // classify given span. soon we will re-classify the region. - AddClassifiedSpansForTokens(classificationService, span, classifiedSpans); - return; - } + this.AssertIsForeground(); - var tempList = ClassificationUtilities.GetOrCreateClassifiedSpanList(); - AddClassifiedSpansForCurrentTree( - classificationService, translatedSpan, lastDocument, tempList); + // First, get the tree and snapshot that we'll be operating over. + var (lastProcessedSnapshot, lastProcessedDocument, lastProcessedRoot) = GetLastProcessedData(); - var currentSnapshot = span.Snapshot; - var currentText = currentSnapshot.AsText(); - foreach (var lastClassifiedSpan in tempList) - { - // 2) Translate those classifications forward so that they correspond to the true - // requested snapshot. - var lastSnapshotSpan = lastClassifiedSpan.TextSpan.ToSnapshotSpan(lastSnapshot); - var currentSnapshotSpan = lastSnapshotSpan.TranslateTo(currentSnapshot, SpanTrackingMode.EdgeInclusive); + if (lastProcessedDocument == null) + { + // We don't have a syntax tree yet. Just do a lexical classification of the document. + AddLexicalClassifications(classificationService, span, classifiedSpans); + return; + } - var currentClassifiedSpan = new ClassifiedSpan(lastClassifiedSpan.ClassificationType, currentSnapshotSpan.Span.ToTextSpan()); + // If we have a document, we must have a snapshot as well. + Contract.ThrowIfNull(lastProcessedSnapshot); - // 3) The classifications may be incorrect due to changes in the text. For example, - // if "clss" becomes "class", then we want to changes the classification from - // 'identifier' to 'keyword'. - currentClassifiedSpan = classificationService.AdjustStaleClassification(currentText, currentClassifiedSpan); + // We have a tree. However, the tree may be for an older version of the snapshot. + // If it is for an older version, then classify that older version and translate + // the classifications forward. Otherwise, just classify normally. - classifiedSpans.Add(currentClassifiedSpan); - } + if (lastProcessedSnapshot.Version.ReiteratedVersionNumber != span.Snapshot.Version.ReiteratedVersionNumber) + { + // Slightly more complicated. We have a parse tree, it's just not for the snapshot we're being asked for. + AddClassifiedSpansForPreviousDocument(classificationService, span, lastProcessedSnapshot, lastProcessedDocument, lastProcessedRoot, classifiedSpans); + return; + } - ClassificationUtilities.ReturnClassifiedSpanList(tempList); + // Mainline case. We have the corresponding document for the snapshot we're classifying. + AddSyntacticClassificationsForDocument(classificationService, span, lastProcessedDocument, lastProcessedRoot, classifiedSpans); + } } - private static void AddClassifiedSpansForTokens( - IClassificationService classificationService, - SnapshotSpan span, - List classifiedSpans) + private void AddLexicalClassifications(IClassificationService classificationService, SnapshotSpan span, ArrayBuilder classifiedSpans) { + this.AssertIsForeground(); + classificationService.AddLexicalClassifications( span.Snapshot.AsText(), span.Span.ToTextSpan(), classifiedSpans, CancellationToken.None); } - private void OnDocumentActiveContextChanged(object sender, DocumentActiveContextChangedEventArgs args) - { - if (_workspace != null && _workspace == args.Solution.Workspace) - { - ProcessIfThisDocument(args.Solution, args.NewActiveContextDocumentId); - } - } - - private void OnDocumentOpened(object sender, DocumentEventArgs args) + private void AddSyntacticClassificationsForDocument( + IClassificationService classificationService, SnapshotSpan span, + Document document, SyntaxNode? root, ArrayBuilder classifiedSpans) { - if (_workspace != null) - { - ProcessIfThisDocument(args.Document.Project.Solution, args.Document.Id); - } - } + this.AssertIsForeground(); + var cancellationToken = CancellationToken.None; - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args) - { - // We're getting an event for a workspace we already disconnected from - if (args.NewSolution.Workspace != _workspace) - { - // we are async so we are getting events from previous workspace we were associated with - // just ignore them + if (_lastLineCache.TryUseCache(span, classifiedSpans)) return; - } - switch (args.Kind) - { - case WorkspaceChangeKind.ProjectChanged: - { - var documentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - if (documentId == null || documentId.ProjectId != args.ProjectId) - { - break; - } - - var oldProject = args.OldSolution.GetProject(args.ProjectId); - var newProject = args.NewSolution.GetProject(args.ProjectId); - - // make sure in case of parse config change, we re-colorize whole document. not just edited section. - var configChanged = !object.Equals(oldProject.ParseOptions, newProject.ParseOptions); - EnqueueProcessSnapshot(newProject.GetDocument(documentId)); - break; - } - - case WorkspaceChangeKind.DocumentChanged: - { - ProcessIfThisDocument(args.NewSolution, args.DocumentId); - break; - } - } + using var _ = ArrayBuilder.GetInstance(out var tempList); + + // If we have a syntax root ready, use the direct, non-async/non-blocking approach to getting classifications. + if (root == null) + classificationService.AddSyntacticClassificationsAsync(document, span.Span.ToTextSpan(), tempList, cancellationToken).Wait(cancellationToken); + else + classificationService.AddSyntacticClassifications(document.Project.Solution.Workspace, root, span.Span.ToTextSpan(), tempList, cancellationToken); - // put a request to update last parsed document. - // this will make us to enqueue a request per workspace change event per a opened file. - // if this show up as perf cost, we might need to revisit this design. currently we do this so that our Roslyn Language Service API - // maintain its consistency. - var newSolution = args.NewSolution; - _workQueue.EnqueueBackgroundTask(c => UpdateLastParsedDocumentAsync(newSolution, c), "UpdateLastParsedDocument", CancellationToken.None); + _lastLineCache.Update(span, tempList); + classifiedSpans.AddRange(tempList); } - private async Task UpdateLastParsedDocumentAsync(Solution newSolution, CancellationToken cancellationToken) + private void AddClassifiedSpansForPreviousDocument( + IClassificationService classificationService, SnapshotSpan span, + ITextSnapshot lastProcessedSnapshot, Document lastProcessedDocument, SyntaxNode? lastProcessedRoot, + ArrayBuilder classifiedSpans) { - // lastParsedDocument only updated in the same sequential queue so don't need lock to use it - var lastDocument = Volatile.Read(ref _lastProcessedDocument); - if (lastDocument == null) - { - return; - } - - var document = newSolution.GetDocument(lastDocument.Id); - if (document == null) - { - // document no longer exist. reset it to null, if somebody calls us, we will answer using lexer. - ResetLastParsedDocument(); - return; - } - - // it is already updated. nothing to do here. - if (lastDocument == document) - { - return; - } + this.AssertIsForeground(); - var lastParsedText = await lastDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); - var lastParsedSnapshot = lastParsedText.FindCorrespondingEditorTextSnapshot(); - - var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var newSnapshot = newText.FindCorrespondingEditorTextSnapshot(); - if (newSnapshot == null) - { - // It's possible that we're seeing a notification for an update that happened - // just before the file was opened, and so the document we're given is still the - // old one. - return; - } + // Slightly more complicated case. They're asking for the classifications for a + // different snapshot than what we have a parse tree for. So we first translate the span + // that they're asking for so that is maps onto the tree that we have spans for. We then + // get the classifications from that tree. We then take the results and translate them + // back to the snapshot they want. Finally, as some of the classifications may have + // changed, we check for some common cases and touch them up manually so that things + // look right for the user. -#if DEBUG - // this must exist since we are holding it in the field. - Contract.ThrowIfNull(lastParsedSnapshot); -#endif - if (lastParsedSnapshot == newSnapshot) + // 1) translate the requested span onto the right span for the snapshot that corresponds + // to the syntax tree. + var translatedSpan = span.TranslateTo(lastProcessedSnapshot, SpanTrackingMode.EdgeExclusive); + if (translatedSpan.IsEmpty) { - // update document to new snapshot with same content - lock (_gate) - { - _lastProcessedDocument = document; - } + // well, there is no information we can get from previous tree, use lexer to + // classify given span. soon we will re-classify the region. + AddLexicalClassifications(classificationService, span, classifiedSpans); } else { - // This workspace change must have also implicitly changed the text of our file. This can happen - // if it's a linked file (and we are observing the non-active linked file changing before our own active file) - // or some other workspace change (say a SolutionChanged) caused a text edit to happen and we didn't process - // it directly. In that case, requeue a parse. This might be a redundant parse in the linked file case - // since we might also get a DocumentChanged event for our ID. It's fine. - ProcessIfThisDocument(newSolution, document.Id); - } - } + using var _ = ArrayBuilder.GetInstance(out var tempList); + AddSyntacticClassificationsForDocument(classificationService, translatedSpan, lastProcessedDocument, lastProcessedRoot, tempList); - private void ProcessIfThisDocument(Solution newSolution, DocumentId documentId) - { - if (_workspace != null) - { - var openDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - if (openDocumentId == documentId) + var currentSnapshot = span.Snapshot; + var currentText = currentSnapshot.AsText(); + foreach (var lastClassifiedSpan in tempList) { - EnqueueProcessSnapshot(newSolution.GetDocument(documentId)); + // 2) Translate those classifications forward so that they correspond to the true + // requested snapshot. + var lastSnapshotSpan = lastClassifiedSpan.TextSpan.ToSnapshotSpan(lastProcessedSnapshot); + var currentSnapshotSpan = lastSnapshotSpan.TranslateTo(currentSnapshot, SpanTrackingMode.EdgeInclusive); + + var currentClassifiedSpan = new ClassifiedSpan(lastClassifiedSpan.ClassificationType, currentSnapshotSpan.Span.ToTextSpan()); + + // 3) The classifications may be incorrect due to changes in the text. For example, + // if "clss" becomes "class", then we want to changes the classification from + // 'identifier' to 'keyword'. + currentClassifiedSpan = classificationService.AdjustStaleClassification(currentText, currentClassifiedSpan); + + classifiedSpans.Add(currentClassifiedSpan); } } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.Tagger.cs index 3e2cbe9aec290..57919f73f2881 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.Tagger.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.Collections.Generic; using Microsoft.VisualStudio.Text; @@ -15,7 +13,7 @@ internal partial class SyntacticClassificationTaggerProvider { private class Tagger : ITagger, IDisposable { - private TagComputer _tagComputer; + private TagComputer? _tagComputer; public Tagger(TagComputer tagComputer) { @@ -23,19 +21,17 @@ public Tagger(TagComputer tagComputer) _tagComputer.TagsChanged += OnTagsChanged; } - public event EventHandler TagsChanged; + public event EventHandler? TagsChanged; public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { if (_tagComputer == null) - { - throw new ObjectDisposedException("AbstractSyntacticClassificationTaggerProvider.Tagger"); - } + throw new ObjectDisposedException(GetType().FullName); return _tagComputer.GetTags(spans); } - private void OnTagsChanged(object sender, SnapshotSpanEventArgs e) + private void OnTagsChanged(object? sender, SnapshotSpanEventArgs e) => TagsChanged?.Invoke(this, e); public void Dispose() @@ -43,7 +39,7 @@ public void Dispose() if (_tagComputer != null) { _tagComputer.TagsChanged -= OnTagsChanged; - _tagComputer.DecrementReferenceCountAndDisposeIfNecessary(); + _tagComputer.DecrementReferenceCount(); _tagComputer = null; } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs index fc864470c38f4..6ff84f127e241 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs @@ -2,14 +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.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -22,10 +22,8 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification [ContentType(ContentTypeNames.RoslynContentType)] [TextViewRole(PredefinedTextViewRoles.Document)] [TagType(typeof(IClassificationTag))] - internal partial class SyntacticClassificationTaggerProvider : ITaggerProvider + internal partial class SyntacticClassificationTaggerProvider : ForegroundThreadAffinitizedObject, ITaggerProvider { - private readonly IThreadingContext _threadingContext; - private readonly IForegroundNotificationService _notificationService; private readonly IAsynchronousOperationListener _listener; private readonly ClassificationTypeMap _typeMap; @@ -35,26 +33,23 @@ internal partial class SyntacticClassificationTaggerProvider : ITaggerProvider [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public SyntacticClassificationTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, ClassificationTypeMap typeMap, IAsynchronousOperationListenerProvider listenerProvider) + : base(threadingContext, assertIsForeground: false) { - _threadingContext = threadingContext; - _notificationService = notificationService; _typeMap = typeMap; _listener = listenerProvider.GetListener(FeatureAttribute.Classification); } - public ITagger CreateTagger(ITextBuffer buffer) where T : ITag + public ITagger? CreateTagger(ITextBuffer buffer) where T : ITag { + this.AssertIsForeground(); if (!buffer.GetFeatureOnOffOption(InternalFeatureOnOffOptions.SyntacticColorizer)) - { return null; - } if (!_tagComputers.TryGetValue(buffer, out var tagComputer)) { - tagComputer = new TagComputer(buffer, _notificationService, _listener, _typeMap, this); + tagComputer = new TagComputer(this, (ITextBuffer2)buffer, _listener, _typeMap, TaggerDelay.NearImmediate.ComputeTimeDelay()); _tagComputers.Add(buffer, tagComputer); } @@ -62,16 +57,12 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag var tagger = new Tagger(tagComputer); - if (!(tagger is ITagger typedTagger)) - { - // Oops, we can't actually return this tagger, so just clean up - tagger.Dispose(); - return null; - } - else - { + if (tagger is ITagger typedTagger) return typedTagger; - } + + // Oops, we can't actually return this tagger, so just clean up + tagger.Dispose(); + return null; } private void DisconnectTagComputer(ITextBuffer buffer) diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs index f1adb2014a688..5900df1884756 100644 --- a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Threading; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; @@ -22,9 +21,8 @@ internal abstract class AbstractDiagnosticsAdornmentTaggerProvider : public AbstractDiagnosticsAdornmentTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, notificationService, listenerProvider.GetListener(FeatureAttribute.ErrorSquiggles)) + : base(threadingContext, diagnosticService, listenerProvider.GetListener(FeatureAttribute.ErrorSquiggles)) { } diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs index 11a43d41c492a..a6e9773fcc95e 100644 --- a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs @@ -54,9 +54,8 @@ internal abstract partial class AbstractDiagnosticsTaggerProvider : Asynch protected AbstractDiagnosticsTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, - IForegroundNotificationService notificationService, IAsynchronousOperationListener listener) - : base(threadingContext, listener, notificationService) + : base(threadingContext, listener) { _diagnosticService = diagnosticService; _diagnosticService.DiagnosticsUpdated += OnDiagnosticsUpdated; @@ -95,14 +94,15 @@ private void OnDiagnosticsUpdated(object? sender, DiagnosticsUpdatedArgs e) } } + protected override TaggerDelay EventChangeDelay => TaggerDelay.Short; protected override TaggerDelay AddedTagNotificationDelay => TaggerDelay.OnIdle; protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) { return TaggerEventSources.Compose( - TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer, TaggerDelay.Medium), - TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.Medium), - TaggerEventSources.OnDiagnosticsChanged(subjectBuffer, _diagnosticService, TaggerDelay.Short)); + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer), + TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer), + TaggerEventSources.OnDiagnosticsChanged(subjectBuffer, _diagnosticService)); } protected internal abstract bool IsEnabled { get; } diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsClassificationTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsClassificationTaggerProvider.cs index 9909e353a6eb8..420d916dc24d0 100644 --- a/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsClassificationTaggerProvider.cs @@ -46,10 +46,9 @@ public DiagnosticsClassificationTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, ClassificationTypeMap typeMap, - IForegroundNotificationService notificationService, IEditorOptionsFactoryService editorOptionsFactoryService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, notificationService, listenerProvider.GetListener(FeatureAttribute.Classification)) + : base(threadingContext, diagnosticService, listenerProvider.GetListener(FeatureAttribute.Classification)) { _typeMap = typeMap; _classificationTag = new ClassificationTag(_typeMap.GetClassificationType(ClassificationTypeDefinitions.UnnecessaryCode)); diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs index 94bc803f84ee7..3ff1ec5c0b98e 100644 --- a/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Diagnostics; -using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Shared.Options; @@ -19,7 +18,6 @@ using Microsoft.VisualStudio.Text.Adornments; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics { @@ -39,9 +37,8 @@ internal partial class DiagnosticsSquiggleTaggerProvider : AbstractDiagnosticsAd public DiagnosticsSquiggleTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, notificationService, listenerProvider) + : base(threadingContext, diagnosticService, listenerProvider) { } diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs index e8506c174c463..219bd48ea4278 100644 --- a/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs @@ -37,9 +37,8 @@ internal partial class DiagnosticsSuggestionTaggerProvider : public DiagnosticsSuggestionTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, notificationService, listenerProvider) + : base(threadingContext, diagnosticService, listenerProvider) { } diff --git a/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs b/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs index 3bb7d4069025c..d26b9f8eb31a6 100644 --- a/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs @@ -5,7 +5,6 @@ using System; using System.Threading; using Microsoft.CodeAnalysis.DocumentationComments; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -16,33 +15,32 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Operations; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.DocumentationComments { - internal abstract class AbstractDocumentationCommentCommandHandler : + internal abstract class AbstractDocumentationCommentCommandHandler : IChainedCommandHandler, ICommandHandler, ICommandHandler, IChainedCommandHandler, IChainedCommandHandler - where TDocumentationComment : SyntaxNode, IStructuredTriviaSyntax - where TMemberNode : SyntaxNode { - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; protected AbstractDocumentationCommentCommandHandler( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService) { - Contract.ThrowIfNull(waitIndicator); + Contract.ThrowIfNull(uiThreadOperationExecutor); Contract.ThrowIfNull(undoHistoryRegistry); Contract.ThrowIfNull(editorOperationsFactoryService); - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _undoHistoryRegistry = undoHistoryRegistry; _editorOperationsFactoryService = editorOperationsFactoryService; } @@ -203,11 +201,11 @@ public CommandState GetCommandState(InsertCommentCommandArgs args) var service = document.GetRequiredLanguageService(); var isValidTargetMember = false; - _waitIndicator.Wait("IntelliSense", allowCancel: true, action: c => + _uiThreadOperationExecutor.Execute("IntelliSense", defaultDescription: "", allowCancellation: true, showProgress: false, action: c => { - var syntaxTree = document.GetRequiredSyntaxTreeSynchronously(c.CancellationToken); - var text = syntaxTree.GetText(c.CancellationToken); - isValidTargetMember = service.IsValidTargetMember(syntaxTree, text, caretPosition, c.CancellationToken); + var syntaxTree = document.GetRequiredSyntaxTreeSynchronously(c.UserCancellationToken); + var text = syntaxTree.GetText(c.UserCancellationToken); + isValidTargetMember = service.IsValidTargetMember(syntaxTree, text, caretPosition, c.UserCancellationToken); }); return isValidTargetMember diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.EventSource.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.EventSource.cs index 17ada72e1bbe1..d32e53282f03e 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.EventSource.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.EventSource.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue @@ -12,8 +11,8 @@ internal partial class ActiveStatementTaggerProvider { private sealed class EventSource : AbstractWorkspaceTrackingTaggerEventSource { - public EventSource(ITextBuffer subjectBuffer, TaggerDelay delay) - : base(subjectBuffer, delay) + public EventSource(ITextBuffer subjectBuffer) + : base(subjectBuffer) { } diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs index 41f4de95ccbd2..1a727222eb8ba 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs @@ -3,18 +3,15 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -41,20 +38,21 @@ internal partial class ActiveStatementTaggerProvider : AsynchronousTaggerProvide [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public ActiveStatementTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.Classification), notificationService) + : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.Classification)) { } + protected override TaggerDelay EventChangeDelay => TaggerDelay.NearImmediate; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { AssertIsForeground(); return TaggerEventSources.Compose( - new EventSource(subjectBuffer, TaggerDelay.Short), - TaggerEventSources.OnTextChanged(subjectBuffer, TaggerDelay.NearImmediate), - TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer, TaggerDelay.Short)); + new EventSource(subjectBuffer), + TaggerEventSources.OnTextChanged(subjectBuffer), + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer)); } protected override async Task ProduceTagsAsync(TaggerContext context) diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingService.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingService.cs index 8eed699666666..fa8bc3a1f7063 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingService.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingService.cs @@ -19,7 +19,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; @@ -83,38 +82,42 @@ public void EndTracking() TrackingChanged?.Invoke(); } - public ValueTask> GetSpansAsync(Document document, CancellationToken cancellationToken) - => _session?.GetSpansAsync(document, cancellationToken) ?? new(ImmutableArray.Empty); + public ValueTask> GetSpansAsync(Solution solution, DocumentId? documentId, string filePath, CancellationToken cancellationToken) + => _session?.GetSpansAsync(solution, documentId, filePath, cancellationToken) ?? new(ImmutableArray.Empty); - public ValueTask> GetAdjustedTrackingSpansAsync(Document document, ITextSnapshot snapshot, CancellationToken cancellationToken) + public ValueTask> GetAdjustedTrackingSpansAsync(TextDocument document, ITextSnapshot snapshot, CancellationToken cancellationToken) => _session?.GetAdjustedTrackingSpansAsync(document, snapshot, cancellationToken) ?? new(ImmutableArray.Empty); // internal for testing internal sealed class TrackingSession { private readonly Workspace _workspace; - private readonly CancellationTokenSource _cancellationSource; + private readonly CancellationTokenSource _cancellationSource = new(); private readonly IActiveStatementSpanProvider _spanProvider; + private readonly ICompileTimeSolutionProvider _compileTimeSolutionProvider; #region lock(_trackingSpans) - // Spans that are tracking active statements contained in the specified document. - private readonly Dictionary> _trackingSpans; + /// + /// Spans that are tracking active statements contained in the document of given file path. + /// For each document the array contains spans for all active statements present in the file + /// (even if they have been deleted, in which case the spans are empty). + /// + private readonly Dictionary> _trackingSpans = new(); #endregion public TrackingSession(Workspace workspace, IActiveStatementSpanProvider spanProvider) { _workspace = workspace; - _trackingSpans = new Dictionary>(); - _cancellationSource = new CancellationTokenSource(); _spanProvider = spanProvider; + _compileTimeSolutionProvider = workspace.Services.GetRequiredService(); _workspace.DocumentOpened += DocumentOpened; _workspace.DocumentClosed += DocumentClosed; } - internal Dictionary> Test_GetTrackingSpans() + internal Dictionary> Test_GetTrackingSpans() => _trackingSpans; public void EndTracking() @@ -133,25 +136,36 @@ public void EndTracking() private void DocumentClosed(object? sender, DocumentEventArgs e) { - lock (_trackingSpans) + if (e.Document.FilePath != null) { - _trackingSpans.Remove(e.Document.Id); + lock (_trackingSpans) + { + _trackingSpans.Remove(e.Document.FilePath); + } } } private void DocumentOpened(object? sender, DocumentEventArgs e) => _ = TrackActiveSpansAsync(e.Document, _cancellationSource.Token); - private async Task TrackActiveSpansAsync(Document document, CancellationToken cancellationToken) + private async Task TrackActiveSpansAsync(Document designTimeDocument, CancellationToken cancellationToken) { try { - if (!TryGetSnapshot(document, out var snapshot)) + if (!EditAndContinueWorkspaceService.SupportsEditAndContinue(designTimeDocument.DocumentState)) { return; } - _ = await GetAdjustedTrackingSpansAsync(document, snapshot, cancellationToken).ConfigureAwait(false); + var compileTimeSolution = _compileTimeSolutionProvider.GetCompileTimeSolution(designTimeDocument.Project.Solution); + var compileTimeDocument = await compileTimeSolution.GetDocumentAsync(designTimeDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + + if (compileTimeDocument == null || !TryGetSnapshot(compileTimeDocument, out var snapshot)) + { + return; + } + + _ = await GetAdjustedTrackingSpansAsync(compileTimeDocument, snapshot, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -181,11 +195,12 @@ internal async Task TrackActiveSpansAsync(Solution solution, CancellationToken c } Debug.Assert(openDocumentIds.Length == baseActiveStatementSpans.Length); - using var _ = ArrayBuilder.GetInstance(out var documents); + using var _ = ArrayBuilder.GetInstance(out var documents); foreach (var id in openDocumentIds) { - documents.Add(await solution.GetDocumentAsync(id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false)); + // active statements may be in any document kind (#line may in theory map to analyzer config as well, no need to exclude it): + documents.Add(await solution.GetTextDocumentAsync(id, cancellationToken).ConfigureAwait(false)); } lock (_trackingSpans) @@ -193,9 +208,9 @@ internal async Task TrackActiveSpansAsync(Solution solution, CancellationToken c for (var i = 0; i < baseActiveStatementSpans.Length; i++) { var document = documents[i]; - if (document == null) + if (document?.FilePath == null) { - // Document has been deleted + // Document has been deleted, doesn't have a path or is an open design-time document (which does not exist in the compile-time solution) continue; } @@ -205,11 +220,11 @@ internal async Task TrackActiveSpansAsync(Solution solution, CancellationToken c continue; } - if (!_trackingSpans.ContainsKey(document.Id)) + if (!_trackingSpans.ContainsKey(document.FilePath)) { // Create tracking spans if they have not been created for this open document yet // (avoids race condition with DocumentOpen event handler). - _trackingSpans.Add(document.Id, CreateTrackingSpans(snapshot, baseActiveStatementSpans[i])); + _trackingSpans.Add(document.FilePath, CreateTrackingSpans(snapshot, baseActiveStatementSpans[i])); } } } @@ -224,16 +239,13 @@ internal async Task TrackActiveSpansAsync(Solution solution, CancellationToken c } } - private static ImmutableArray CreateTrackingSpans(ITextSnapshot snapshot, ImmutableArray<(LinePositionSpan span, ActiveStatementFlags flags)> activeStatementSpans) - { - return activeStatementSpans.SelectAsArray(spanAndFlags => - new ActiveStatementTrackingSpan(snapshot.CreateTrackingSpan(snapshot.GetTextSpan(spanAndFlags.span).ToSpan(), SpanTrackingMode.EdgeExclusive), spanAndFlags.flags)); - } + private static ImmutableArray CreateTrackingSpans(ITextSnapshot snapshot, ImmutableArray activeStatementSpans) + => activeStatementSpans.SelectAsArray((span, snapshot) => ActiveStatementTrackingSpan.Create(snapshot, span), snapshot); private static ImmutableArray UpdateTrackingSpans( ITextSnapshot snapshot, ImmutableArray oldSpans, - ImmutableArray<(LinePositionSpan, ActiveStatementFlags)> newSpans) + ImmutableArray newSpans) { Debug.Assert(oldSpans.Length == newSpans.Length); @@ -242,13 +254,13 @@ private static ImmutableArray UpdateTrackingSpans( for (var i = 0; i < oldSpans.Length; i++) { var oldSpan = oldSpans[i]; - var (newLineSpan, newFlags) = newSpans[i]; + var newSpan = newSpans[i]; - // flags must be preserved (can't change leaf statement to non-leaf, etc.): - Contract.ThrowIfFalse(oldSpan.Flags == newFlags); + Contract.ThrowIfFalse(oldSpan.Flags == newSpan.Flags); + Contract.ThrowIfFalse(oldSpan.Ordinal == newSpan.Ordinal); - var newSpan = snapshot.GetTextSpan(newLineSpan).ToSpan(); - if (oldSpan.Span.GetSpan(snapshot).Span != newSpan) + var newTextSpan = snapshot.GetTextSpan(newSpan.LineSpan).ToSpan(); + if (oldSpan.Span.GetSpan(snapshot).Span != newTextSpan) { if (lazyBuilder == null) { @@ -257,15 +269,17 @@ private static ImmutableArray UpdateTrackingSpans( } lazyBuilder[i] = new ActiveStatementTrackingSpan( - snapshot.CreateTrackingSpan(newSpan, SpanTrackingMode.EdgeExclusive), - newFlags); + snapshot.CreateTrackingSpan(newTextSpan, SpanTrackingMode.EdgeExclusive), + newSpan.Ordinal, + newSpan.Flags, + newSpan.UnmappedDocumentId); } } return lazyBuilder?.ToImmutableAndFree() ?? oldSpans; } - private static bool TryGetSnapshot(Document document, [NotNullWhen(true)] out ITextSnapshot? snapshot) + private static bool TryGetSnapshot(TextDocument document, [NotNullWhen(true)] out ITextSnapshot? snapshot) { if (!document.TryGetText(out var source)) { @@ -277,49 +291,72 @@ private static bool TryGetSnapshot(Document document, [NotNullWhen(true)] out IT return snapshot != null; } - public async ValueTask> GetSpansAsync(Document document, CancellationToken cancellationToken) + /// + /// Returns location of the tracking spans in the specified snapshot (#line target document). + /// + /// Empty array if tracking spans are not available for the document. + public async ValueTask> GetSpansAsync(Solution solution, DocumentId? documentId, string filePath, CancellationToken cancellationToken) { + documentId ??= solution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(); + + var document = await solution.GetTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); + if (document == null) + { + return ImmutableArray.Empty; + } + var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); lock (_trackingSpans) { - if (_trackingSpans.TryGetValue(document.Id, out var documentSpans) && !documentSpans.IsDefaultOrEmpty) + if (_trackingSpans.TryGetValue(filePath, out var documentSpans) && !documentSpans.IsDefaultOrEmpty) { var snapshot = sourceText.FindCorrespondingEditorTextSnapshot(); if (snapshot != null && snapshot.TextBuffer == documentSpans.First().Span.TextBuffer) { - return documentSpans.SelectAsArray(s => s.Span.GetSpan(snapshot).Span.ToTextSpan()); + return documentSpans.SelectAsArray(s => new ActiveStatementSpan(s.Ordinal, s.Span.GetSpan(snapshot).ToLinePositionSpan(), s.Flags, s.UnmappedDocumentId)); } } } - return ImmutableArray.Empty; + return ImmutableArray.Empty; } /// - /// Updates tracking spans with the latest positions of all active statements in the specified document snapshot. + /// Updates tracking spans with the latest positions of all active statements in the specified document snapshot (#line target document) and returns them. /// - internal async ValueTask> GetAdjustedTrackingSpansAsync(Document document, ITextSnapshot snapshot, CancellationToken cancellationToken) + internal async ValueTask> GetAdjustedTrackingSpansAsync(TextDocument document, ITextSnapshot snapshot, CancellationToken cancellationToken) { try { + if (document.FilePath == null) + { + return ImmutableArray.Empty; + } + Debug.Assert(TryGetSnapshot(document, out var s) && s == snapshot); - var activeStatementSpanProvider = new DocumentActiveStatementSpanProvider(cancellationToken => GetSpansAsync(document, cancellationToken)); - var activeStatementSpans = await _spanProvider.GetAdjustedActiveStatementSpansAsync(document, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); + var solution = document.Project.Solution; + + var activeStatementSpans = await _spanProvider.GetAdjustedActiveStatementSpansAsync( + document, + (documentId, filePath, cancellationToken) => GetSpansAsync(solution, documentId, filePath, cancellationToken), + cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfTrue(activeStatementSpans.IsDefault); lock (_trackingSpans) { - var hasExistingSpans = _trackingSpans.TryGetValue(document.Id, out var oldSpans); + var hasExistingSpans = _trackingSpans.TryGetValue(document.FilePath, out var oldSpans); - if (activeStatementSpans.IsDefault) + if (activeStatementSpans.IsEmpty) { - // Unable to determine the latest positions of active statements for the document snapshot (the document might have syntax errors). + // Unable to determine the latest positions of active statements for the document snapshot (the document is out-of-sync). // Return the current tracking spans. return oldSpans.NullToEmpty(); } - return _trackingSpans[document.Id] = hasExistingSpans ? + return _trackingSpans[document.FilePath] = hasExistingSpans ? UpdateTrackingSpans(snapshot, oldSpans, activeStatementSpans) : CreateTrackingSpans(snapshot, activeStatementSpans); } diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs index 7c0b7911a4928..2175827b9963e 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.VisualStudio.Text; @@ -11,17 +12,24 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue internal readonly struct ActiveStatementTrackingSpan { public readonly ITrackingSpan Span; + public readonly int Ordinal; public readonly ActiveStatementFlags Flags; + public readonly DocumentId? UnmappedDocumentId; - public ActiveStatementTrackingSpan(ITrackingSpan trackingSpan, ActiveStatementFlags flags) + public ActiveStatementTrackingSpan(ITrackingSpan trackingSpan, int ordinal, ActiveStatementFlags flags, DocumentId? unmappedDocumentId) { Span = trackingSpan; + Ordinal = ordinal; Flags = flags; + UnmappedDocumentId = unmappedDocumentId; } /// /// True if at least one of the threads whom this active statement belongs to is in a leaf frame. /// public bool IsLeaf => (Flags & ActiveStatementFlags.IsLeafFrame) != 0; + + public static ActiveStatementTrackingSpan Create(ITextSnapshot snapshot, ActiveStatementSpan span) + => new(snapshot.CreateTrackingSpan(snapshot.GetTextSpan(span.LineSpan).ToSpan(), SpanTrackingMode.EdgeExclusive), span.Ordinal, span.Flags, span.UnmappedDocumentId); } } diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs index 1ffc31abfbf19..8f5c5152470d4 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs @@ -5,13 +5,18 @@ #nullable disable using System.Collections.Immutable; +using System.IO; +using System.Linq; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -47,22 +52,70 @@ public override Task> AnalyzeSemanticsAsync(Document return SpecializedTasks.EmptyImmutableArray(); } - return AnalyzeSemanticsImplAsync(document, cancellationToken); + return AnalyzeSemanticsImplAsync(workspace, document, cancellationToken); + } + + // Copied from + // https://github.com/dotnet/sdk/blob/main/src/RazorSdk/SourceGenerators/RazorSourceGenerator.Helpers.cs#L32 + private static string GetIdentifierFromPath(string filePath) + { + var builder = new StringBuilder(filePath.Length); + + for (var i = 0; i < filePath.Length; i++) + { + switch (filePath[i]) + { + case ':' or '\\' or '/': + case char ch when !char.IsLetterOrDigit(ch): + builder.Append('_'); + break; + default: + builder.Append(filePath[i]); + break; + } + } + + return builder.ToString(); } [MethodImpl(MethodImplOptions.NoInlining)] - private static Task> AnalyzeSemanticsImplAsync(Document document, CancellationToken cancellationToken) + private static async Task> AnalyzeSemanticsImplAsync(Workspace workspace, Document designTimeDocument, CancellationToken cancellationToken) { - var workspace = document.Project.Solution.Workspace; + var designTimeSolution = designTimeDocument.Project.Solution; + var compileTimeSolution = workspace.Services.GetRequiredService().GetCompileTimeSolution(designTimeSolution); + + var compileTimeDocument = await compileTimeSolution.GetDocumentAsync(designTimeDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + if (compileTimeDocument == null) + { + if (!designTimeDocument.State.Attributes.DesignTimeOnly || + !designTimeDocument.FilePath.EndsWith(".razor.g.cs")) + { + return ImmutableArray.Empty; + } + + var relativeDocumentPath = Path.Combine("\\", PathUtilities.GetRelativePath(PathUtilities.GetDirectoryName(designTimeDocument.Project.FilePath), designTimeDocument.FilePath)[..^".g.cs".Length]); + var generatedDocumentPath = Path.Combine("Microsoft.NET.Sdk.Razor.SourceGenerators", "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", GetIdentifierFromPath(relativeDocumentPath)) + ".cs"; + + var sourceGeneratedDocuments = await compileTimeSolution.GetRequiredProject(designTimeDocument.Project.Id).GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + + compileTimeDocument = sourceGeneratedDocuments.SingleOrDefault(d => d.FilePath == generatedDocumentPath); + if (compileTimeDocument == null) + { + return ImmutableArray.Empty; + } + } + + // EnC services should never be called on a design-time solution. + var proxy = new RemoteEditAndContinueServiceProxy(workspace); - var activeStatementSpanProvider = new DocumentActiveStatementSpanProvider(async cancellationToken => + var activeStatementSpanProvider = new ActiveStatementSpanProvider(async (documentId, filePath, cancellationToken) => { var trackingService = workspace.Services.GetRequiredService(); - return await trackingService.GetSpansAsync(document, cancellationToken).ConfigureAwait(false); + return await trackingService.GetSpansAsync(compileTimeSolution, documentId, filePath, cancellationToken).ConfigureAwait(false); }); - return proxy.GetDocumentDiagnosticsAsync(document, activeStatementSpanProvider, cancellationToken).AsTask(); + return await proxy.GetDocumentDiagnosticsAsync(compileTimeDocument, designTimeDocument, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs index 6284f2a5e4b32..a8c80e2830e9b 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueSaveFileCommandHandler.cs @@ -40,6 +40,7 @@ void IChainedCommandHandler.ExecuteCommand(SaveCommandArgs args var documentId = workspace.GetDocumentIdInCurrentContext(textContainer); if (documentId != null) { + // ignoring source-generated files since they shouldn't be modified and saved: var currentDocument = workspace.CurrentSolution.GetDocument(documentId); if (currentDocument != null) { diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/IActiveStatementTrackingService.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/IActiveStatementTrackingService.cs index b6d6293bee13a..1cc09b7352125 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/IActiveStatementTrackingService.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/IActiveStatementTrackingService.cs @@ -6,8 +6,8 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue @@ -24,14 +24,14 @@ internal interface IActiveStatementTrackingService : IWorkspaceService event Action TrackingChanged; /// - /// Returns location of the tracking spans in the specified snapshot. + /// Returns location of the tracking spans in the specified document snapshot (#line target document). /// /// Empty array if tracking spans are not available for the document. - ValueTask> GetSpansAsync(Document document, CancellationToken cancellationToken); + ValueTask> GetSpansAsync(Solution solution, DocumentId? documentId, string filePath, CancellationToken cancellationToken); /// - /// Updates tracking spans with the latest positions of all active statements in the specified document snapshot and returns them. + /// Updates tracking spans with the latest positions of all active statements in the specified document snapshot (#line target document) and returns them. /// - ValueTask> GetAdjustedTrackingSpansAsync(Document document, ITextSnapshot snapshot, CancellationToken cancellationToken); + ValueTask> GetAdjustedTrackingSpansAsync(TextDocument document, ITextSnapshot snapshot, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/Implementation/ForegroundNotification/ForegroundNotificationService.cs b/src/EditorFeatures/Core/Implementation/ForegroundNotification/ForegroundNotificationService.cs deleted file mode 100644 index 4803364098252..0000000000000 --- a/src/EditorFeatures/Core/Implementation/ForegroundNotification/ForegroundNotificationService.cs +++ /dev/null @@ -1,424 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.ForegroundNotification -{ - [Export(typeof(IForegroundNotificationService))] - internal class ForegroundNotificationService : ForegroundThreadAffinitizedObject, IForegroundNotificationService - { - // how much time we will give notifications to run on the UI thread - private const int DefaultTimeSliceInMS = 15; - - // Don't call NotifyOnForeground more than once per 50ms - private const int MinimumDelayBetweenProcessing = 50; - - private static readonly Func s_notifyOnForegroundLogger = c => string.Format("Processed : {0}", c); - private readonly PriorityQueue _workQueue; - - private int _lastProcessedTimeInMS; - - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public ForegroundNotificationService(IThreadingContext threadingContext) - : base(threadingContext) - { - _workQueue = new PriorityQueue(); - _lastProcessedTimeInMS = Environment.TickCount; - - // Only start the background processing task if foreground work is allowed - if (threadingContext.HasMainThread) - { - Task.Factory.SafeStartNewFromAsync(ProcessAsync, CancellationToken.None, TaskScheduler.Default); - } - } - - public void RegisterNotification(Action action, IAsyncToken asyncToken, CancellationToken cancellationToken) - => RegisterNotification(action, DefaultTimeSliceInMS, asyncToken, cancellationToken); - - public void RegisterNotification(Func action, IAsyncToken asyncToken, CancellationToken cancellationToken) - => RegisterNotification(action, DefaultTimeSliceInMS, asyncToken, cancellationToken); - - public void RegisterNotification(Action action, int delay, IAsyncToken asyncToken, CancellationToken cancellationToken) - { - Debug.Assert(delay >= 0); - - if (cancellationToken.IsCancellationRequested) - { - asyncToken?.Dispose(); - return; - } - - // Assert we have some kind of foreground thread - Contract.ThrowIfFalse(ThreadingContext.HasMainThread); - - var current = Environment.TickCount; - - _workQueue.Enqueue(new PendingWork(current + delay, action, asyncToken, cancellationToken)); - } - - public void RegisterNotification(Func action, int delay, IAsyncToken asyncToken, CancellationToken cancellationToken) - { - Debug.Assert(delay >= 0); - - if (cancellationToken.IsCancellationRequested) - { - asyncToken?.Dispose(); - return; - } - - // Assert we have some kind of foreground thread - Contract.ThrowIfFalse(ThreadingContext.HasMainThread); - - var current = Environment.TickCount; - - _workQueue.Enqueue(new PendingWork(current + delay, action, asyncToken, cancellationToken)); - } - - internal void ReleaseCancelledItems() => _workQueue.ReleaseCancelledItems(); - - public bool IsEmpty_TestOnly => _workQueue.IsEmpty; - - private async Task ProcessAsync() - { - var isFirst = true; - while (true) - { - try - { - if (isFirst) - { - AssertIsBackground(); - isFirst = false; - } - - // wait until it is time to run next item - await WaitForPendingWorkAsync().ConfigureAwait(continueOnCapturedContext: false); - - // run them in UI thread - await InvokeBelowInputPriorityAsync(NotifyOnForeground).ConfigureAwait(continueOnCapturedContext: false); - } - catch (Exception ex) when (FatalError.ReportAndCatch(ex)) - { - // This is an error condition but we must continue to drain the work queue. If we - // do not then IAsyncToken values will remain uncomplete and the unit test code - // will deadlock waiting for the values to complete. - Debug.Assert(false, ex.Message); - } - } - } - - private void NotifyOnForeground() - { - NotifyOnForegroundWorker(); - - _workQueue.Touch(); - } - - private void NotifyOnForegroundWorker() - { - AssertIsForeground(); - - using (Logger.LogBlock(FunctionId.ForegroundNotificationService_NotifyOnForeground, CancellationToken.None)) - { - var processedCount = 0; - var startProcessingTime = Environment.TickCount; - while (_workQueue.TryGetWorkItem(startProcessingTime, out var pendingWork)) - { - var done = true; - - // don't process one that is already cancelled - if (!pendingWork.CancellationToken.IsCancellationRequested) - { - try - { - if (pendingWork.DoWorkAction != null) - { - pendingWork.DoWorkAction(); - } - else if (pendingWork.DoWorkFunc != null) - { - if (pendingWork.DoWorkFunc()) - { - done = false; - _workQueue.Enqueue(pendingWork.UpdateToCurrentTime()); - } - } - } - catch (OperationCanceledException) - { - // eat up cancellation - } - catch (Exception ex) when (FatalError.ReportAndCatch(ex)) - { - // The PendingWork callbacks should never throw. In the case they do we - // must ensure the IAsyncToken implementation is completed. If it is not - // then the unit test code will end up in a deadlock doing an 'await' - // on the token instance. - Debug.Assert(false, ex.Message); - done = true; - } - } - - if (done) - { - pendingWork.AsyncToken.Dispose(); - } - - processedCount++; - - // there is input to process, or we've exceeded a time slice, postpone the remaining work - if (IsInputPending() || Environment.TickCount - startProcessingTime > DefaultTimeSliceInMS) - { - return; - } - } - - // Record the current timestamp so we don't immediately process newly added items. - _lastProcessedTimeInMS = Environment.TickCount; - Logger.Log(FunctionId.ForegroundNotificationService_Processed, s_notifyOnForegroundLogger, processedCount); - } - } - - private async Task WaitForPendingWorkAsync() - { - while (true) - { - await _workQueue.WaitForItemsAsync().ConfigureAwait(false); - if (!_workQueue.TryPeekNextItemTime(out var nextItem)) - { - // Need to go back and wait for an item - continue; - } - - // The next item is ready to run - if (nextItem - Environment.TickCount <= 0) - { - break; - } - - // wait some and re-check since there could be another one inserted before the first one while we were waiting. - await Task.Delay(MinimumDelayBetweenProcessing).ConfigureAwait(false); - } - - // Throttle how often we run by waiting MinimumDelayBetweenProcessing since the last time we processed notifications - if (Environment.TickCount - _lastProcessedTimeInMS < MinimumDelayBetweenProcessing) - { - await Task.Delay(MinimumDelayBetweenProcessing).ConfigureAwait(false); - } - } - - private struct PendingWork - { - public readonly int MinimumRunPointInMS; - public readonly Action DoWorkAction; - public readonly Func DoWorkFunc; - public readonly IAsyncToken AsyncToken; - public readonly CancellationToken CancellationToken; - - private PendingWork(int minimumRunPointInMS, Action action, Func func, IAsyncToken asyncToken, CancellationToken cancellationToken) - { - this.MinimumRunPointInMS = minimumRunPointInMS; - this.DoWorkAction = action; - this.DoWorkFunc = func; - this.AsyncToken = asyncToken; - this.CancellationToken = cancellationToken; - } - - public PendingWork(int minimumRunPointInMS, Action work, IAsyncToken asyncToken, CancellationToken cancellationToken) - : this(minimumRunPointInMS, work, null, asyncToken, cancellationToken) - { - } - - public PendingWork(int minimumRunPointInMS, Func work, IAsyncToken asyncToken, CancellationToken cancellationToken) - : this(minimumRunPointInMS, null, work, asyncToken, cancellationToken) - { - } - - public PendingWork UpdateToCurrentTime() - => new(Environment.TickCount, DoWorkAction, DoWorkFunc, AsyncToken, CancellationToken); - } - - private class PriorityQueue - { - // use pool to share linked list nodes rather than re-create them every time - private static readonly ObjectPool> s_pool = - new(() => new LinkedListNode(default), 100); - - private readonly object _gate = new(); - private readonly LinkedList _list = new(); - private readonly SemaphoreSlim _hasItemsGate = new(initialCount: 0); - - public Task WaitForItemsAsync() - { - if (!IsEmpty) - { - return SpecializedTasks.True; - } - - return WaitForNewItemsAsync(); - } - - private async Task WaitForNewItemsAsync() - { - // Use a while loop, since the inserted item may have been processed by TryGetWorkItem - // leaving the semaphore count at 1 even though we're empty again. - while (IsEmpty) - { - await _hasItemsGate.WaitAsync(CancellationToken.None).ConfigureAwait(false); - } - } - - public bool IsEmpty - { - get - { - lock (_gate) - { - return _list.Count == 0; - } - } - } - - public void Touch() - { - lock (_gate) - { - if (_list.Count > 0) - { - // mark that we have more items to process - _hasItemsGate.Release(); - } - } - } - - public void Enqueue(PendingWork work) - { - var entry = s_pool.Allocate(); - entry.Value = work; - - lock (_gate) - { - Enqueue_NoLock(entry); - } - } - - private void Enqueue_NoLock(LinkedListNode entry) - { - // TODO: if this cost shows up in the trace, either use tree based implementation - // or just have separate lists for each delay (short, medium, long) - if (_list.Count == 0) - { - _list.AddLast(entry); - _hasItemsGate.Release(); - return; - } - - var current = _list.Last; - while (current != null) - { - if (current.Value.MinimumRunPointInMS <= entry.Value.MinimumRunPointInMS) - { - _list.AddAfter(current, entry); - return; - } - - current = current.Previous; - } - - _list.AddFirst(entry); - _hasItemsGate.Release(); - return; - } - - public bool TryPeekNextItemTime(out int minimumRunPoint) - { - lock (_gate) - { - if (_list.Count == 0) - { - minimumRunPoint = 0; - return false; - } - - minimumRunPoint = _list.First.Value.MinimumRunPointInMS; - return true; - } - } - - public bool TryGetWorkItem(int currentTime, out PendingWork pendingWork) - { - pendingWork = default; - - lock (_gate) - { - if (!ContainsMoreWork_NoLock(currentTime)) - { - return false; - } - - pendingWork = Dequeue_NoLock(); - return true; - } - } - - private bool ContainsMoreWork_NoLock(int currentTime) - => _list.Count > 0 && _list.First.Value.MinimumRunPointInMS <= currentTime; - - private PendingWork Dequeue_NoLock() - { - var entry = _list.First; - var work = entry.Value; - - _list.RemoveFirst(); - - // reset the value and put it back to pool - entry.Value = default; - s_pool.Free(entry); - - return work; - } - - internal void ReleaseCancelledItems() - { - var removedItems = new LinkedList(); - - lock (_gate) - { - for (LinkedListNode current = _list.First, next = current?.Next; - current != null; - current = next, next = current?.Next) - { - if (current.Value.CancellationToken.IsCancellationRequested) - { - _list.Remove(current); - removedItems.AddLast(current); - } - } - } - - // Dispose of the async tokens outside the lock - foreach (var pendingWork in removedItems) - { - pendingWork.AsyncToken?.Dispose(); - } - } - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatDocument.cs b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatDocument.cs index da954e0f87b6e..19b37ea3ea8c1 100644 --- a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatDocument.cs +++ b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatDocument.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 Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; @@ -27,7 +28,7 @@ public bool ExecuteCommand(FormatDocumentCommandArgs args, CommandExecutionConte return false; } - var formattingService = document.GetLanguageService(); + var formattingService = document.GetLanguageService(); if (formattingService == null || !formattingService.SupportsFormatDocument) { return false; diff --git a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatSelection.cs b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatSelection.cs index 36e9cca7390a2..9a09d837c9c15 100644 --- a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatSelection.cs +++ b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.FormatSelection.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; @@ -33,7 +34,7 @@ private bool TryExecuteCommand(FormatSelectionCommandArgs args, CommandExecution return false; } - var formattingService = document.GetLanguageService(); + var formattingService = document.GetLanguageService(); if (formattingService == null || !formattingService.SupportsFormatSelection) { return false; diff --git a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.Paste.cs b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.Paste.cs index 424efb7349ae6..cf1e313e83c37 100644 --- a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.Paste.cs +++ b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.Paste.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -53,7 +54,7 @@ private static void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? c return; } - if (!args.SubjectBuffer.GetFeatureOnOffOption(FeatureOnOffOptions.FormatOnPaste) || + if (!args.SubjectBuffer.GetFeatureOnOffOption(FormattingOptions2.FormatOnPaste) || !caretPosition.HasValue) { return; @@ -73,15 +74,16 @@ private static void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? c return; } - var formattingService = document.GetLanguageService(); + var formattingService = document.GetLanguageService(); if (formattingService == null || !formattingService.SupportsFormatOnPaste) { return; } var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan(); - var changes = formattingService.GetFormattingChangesOnPasteAsync(document, span, cancellationToken).WaitAndGetResult(cancellationToken); - if (changes.Count == 0) + var changes = formattingService.GetFormattingChangesOnPasteAsync( + document, span, documentOptions: null, cancellationToken).WaitAndGetResult(cancellationToken); + if (changes.IsEmpty) { return; } diff --git a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.cs b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.cs index b93a7128f8d1a..f1ccde5c64205 100644 --- a/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.cs +++ b/src/EditorFeatures/Core/Implementation/Formatting/FormatCommandHandler.cs @@ -9,6 +9,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; @@ -56,13 +57,14 @@ public FormatCommandHandler( private void Format(ITextView textView, Document document, TextSpan? selectionOpt, CancellationToken cancellationToken) { - var formattingService = document.GetRequiredLanguageService(); + var formattingService = document.GetRequiredLanguageService(); using (Logger.LogBlock(FunctionId.CommandHandler_FormatCommand, KeyValueLogMessage.Create(LogType.UserAction, m => m["Span"] = selectionOpt?.Length ?? -1), cancellationToken)) using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Formatting)) { - var changes = formattingService.GetFormattingChangesAsync(document, selectionOpt, cancellationToken).WaitAndGetResult(cancellationToken); - if (changes.Count == 0) + var changes = formattingService.GetFormattingChangesAsync( + document, selectionOpt, documentOptions: null, cancellationToken).WaitAndGetResult(cancellationToken); + if (changes.IsEmpty) { return; } @@ -139,7 +141,7 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati return; } - var service = document.GetLanguageService(); + var service = document.GetLanguageService(); if (service == null) { return; @@ -155,7 +157,8 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati return; } - textChanges = service.GetFormattingChangesOnReturnAsync(document, caretPosition.Value, cancellationToken).WaitAndGetResult(cancellationToken); + textChanges = service.GetFormattingChangesOnReturnAsync( + document, caretPosition.Value, documentOptions: null, cancellationToken).WaitAndGetResult(cancellationToken); } else if (args is TypeCharCommandArgs typeCharArgs) { @@ -164,7 +167,8 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati return; } - textChanges = service.GetFormattingChangesAsync(document, typeCharArgs.TypedChar, caretPosition.Value, cancellationToken).WaitAndGetResult(cancellationToken); + textChanges = service.GetFormattingChangesAsync( + document, typeCharArgs.TypedChar, caretPosition.Value, documentOptions: null, cancellationToken).WaitAndGetResult(cancellationToken); } else { diff --git a/src/EditorFeatures/Core/Implementation/Indentation/EditorLayerInferredIndentationService.cs b/src/EditorFeatures/Core/Implementation/Indentation/EditorLayerInferredIndentationService.cs new file mode 100644 index 0000000000000..3c80a220bc69e --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/Indentation/EditorLayerInferredIndentationService.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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Indentation; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.LanguageServices.Indentation +{ + [ExportWorkspaceService(typeof(IInferredIndentationService), ServiceLayer.Editor), Shared] + internal sealed class EditorLayerInferredIndentationService : IInferredIndentationService + { + private readonly IIndentationManagerService _indentationManagerService; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public EditorLayerInferredIndentationService(IIndentationManagerService indentationManagerService) + { + _indentationManagerService = indentationManagerService; + } + + public async Task GetDocumentOptionsWithInferredIndentationAsync(Document document, bool explicitFormat, CancellationToken cancellationToken) + { + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var snapshot = text.FindCorrespondingEditorTextSnapshot(); + + if (snapshot != null) + { + _indentationManagerService.GetIndentation(snapshot.TextBuffer, explicitFormat, out var convertTabsToSpaces, out var tabSize, out var indentSize); + + options = options.WithChangedOption(FormattingOptions.UseTabs, !convertTabsToSpaces) + .WithChangedOption(FormattingOptions.IndentationSize, indentSize) + .WithChangedOption(FormattingOptions.TabSize, tabSize); + } + + return options; + } + } +} diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs index 29b27d826f3f0..f548aeca1a891 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs @@ -25,7 +25,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename internal class InlineRenameService : IInlineRenameService { private readonly IThreadingContext _threadingContext; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly ITextBufferAssociatedViewService _textBufferAssociatedViewService; private readonly IAsynchronousOperationListener _asyncListener; private readonly IEnumerable _refactorNotifyServices; @@ -37,7 +37,7 @@ internal class InlineRenameService : IInlineRenameService [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public InlineRenameService( IThreadingContext threadingContext, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ITextBufferAssociatedViewService textBufferAssociatedViewService, ITextBufferFactoryService textBufferFactoryService, IFeatureServiceFactory featureServiceFactory, @@ -45,7 +45,7 @@ public InlineRenameService( IAsynchronousOperationListenerProvider listenerProvider) { _threadingContext = threadingContext; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _textBufferAssociatedViewService = textBufferAssociatedViewService; _textBufferFactoryService = textBufferFactoryService; _featureServiceFactory = featureServiceFactory; @@ -86,7 +86,7 @@ public InlineRenameSessionInfo StartInlineSession( document.Project.Solution.Workspace, renameInfo.TriggerSpan.ToSnapshotSpan(snapshot), renameInfo, - _waitIndicator, + _uiThreadOperationExecutor, _textBufferAssociatedViewService, _textBufferFactoryService, _featureServiceFactory, diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs index f54796ec87973..cc279135ac9bd 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs @@ -38,7 +38,7 @@ internal partial class InlineRenameSession : ForegroundThreadAffinitizedObject, { private readonly Workspace _workspace; private readonly InlineRenameService _renameService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly ITextBufferAssociatedViewService _textBufferAssociatedViewService; private readonly ITextBufferFactoryService _textBufferFactoryService; private readonly IFeatureService _featureService; @@ -116,7 +116,7 @@ public InlineRenameSession( Workspace workspace, SnapshotSpan triggerSpan, IInlineRenameInfo renameInfo, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ITextBufferAssociatedViewService textBufferAssociatedViewService, ITextBufferFactoryService textBufferFactoryService, IFeatureServiceFactory featureServiceFactory, @@ -147,7 +147,7 @@ public InlineRenameSession( _completionDisabledToken = _featureService.Disable(PredefinedEditorFeatureNames.Completion, this); _renameService = renameService; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _refactorNotifyServices = refactorNotifyServices; _asyncListener = asyncListener; _triggerView = textBufferAssociatedViewService.GetAssociatedTextViews(triggerSpan.Snapshot.TextBuffer).FirstOrDefault(v => v.HasAggregateFocus) ?? @@ -596,7 +596,7 @@ private void QueueApplyReplacements() private async Task<(IInlineRenameReplacementInfo replacementInfo, LinkedFileMergeSessionResult mergeResult)> ComputeMergeResultAsync(IInlineRenameReplacementInfo replacementInfo, CancellationToken cancellationToken) { - var diffMergingSession = new LinkedFileDiffMergingSession(_baseSolution, replacementInfo.NewSolution, replacementInfo.NewSolution.GetChanges(_baseSolution), logSessionInfo: true); + var diffMergingSession = new LinkedFileDiffMergingSession(_baseSolution, replacementInfo.NewSolution, replacementInfo.NewSolution.GetChanges(_baseSolution)); var mergeResult = await diffMergingSession.MergeDiffsAsync(mergeConflictHandler: null, cancellationToken: cancellationToken).ConfigureAwait(false); return (replacementInfo, mergeResult); } @@ -702,13 +702,14 @@ private bool CommitWorker(bool previewChanges) previewChanges = previewChanges || OptionSet.GetOption(RenameOptions.PreviewChanges); - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Rename, - message: EditorFeaturesResources.Computing_Rename_information, - allowCancel: true, - action: waitContext => CommitCore(waitContext, previewChanges)); + defaultDescription: EditorFeaturesResources.Computing_Rename_information, + allowCancellation: true, + showProgress: false, + action: context => CommitCore(context, previewChanges)); - if (result == WaitIndicatorResult.Canceled) + if (result == UIThreadOperationStatus.Canceled) { LogRenameSession(RenameLogMessage.UserActionOutcome.Canceled | RenameLogMessage.UserActionOutcome.Committed, previewChanges); Dismiss(rollbackTemporaryEdits: true); @@ -734,18 +735,18 @@ private void CancelAllOpenDocumentTrackingTasks() _conflictResolutionTaskCancellationSource.Cancel(); } - private void CommitCore(IWaitContext waitContext, bool previewChanges) + private void CommitCore(IUIThreadOperationContext operationContext, bool previewChanges) { var eventName = previewChanges ? FunctionId.Rename_CommitCoreWithPreview : FunctionId.Rename_CommitCore; - using (Logger.LogBlock(eventName, KeyValueLogMessage.Create(LogType.UserAction), waitContext.CancellationToken)) + using (Logger.LogBlock(eventName, KeyValueLogMessage.Create(LogType.UserAction), operationContext.UserCancellationToken)) { - var newSolution = _conflictResolutionTask.Join(waitContext.CancellationToken).NewSolution; - waitContext.AllowCancel = false; + var newSolution = _conflictResolutionTask.Join(operationContext.UserCancellationToken).NewSolution; if (previewChanges) { var previewService = _workspace.Services.GetService(); + operationContext.TakeOwnership(); newSolution = previewService.PreviewChanges( string.Format(EditorFeaturesResources.Preview_Changes_0, EditorFeaturesResources.Rename), "vs.csharp.refactoring.rename", @@ -764,12 +765,12 @@ private void CommitCore(IWaitContext waitContext, bool previewChanges) // The user hasn't cancelled by now, so we're done waiting for them. Off to // rename! - waitContext.Message = EditorFeaturesResources.Updating_files; + using var _ = operationContext.AddScope(allowCancellation: false, EditorFeaturesResources.Updating_files); Dismiss(rollbackTemporaryEdits: true); CancelAllOpenDocumentTrackingTasks(); - ApplyRename(newSolution, waitContext); + ApplyRename(newSolution, operationContext); LogRenameSession(RenameLogMessage.UserActionOutcome.Committed, previewChanges); @@ -777,7 +778,7 @@ private void CommitCore(IWaitContext waitContext, bool previewChanges) } } - private void ApplyRename(Solution newSolution, IWaitContext waitContext) + private void ApplyRename(Solution newSolution, IUIThreadOperationContext operationContext) { var changes = _baseSolution.GetChanges(newSolution); var changedDocumentIDs = changes.GetProjectChanges().SelectMany(c => c.GetChangedDocuments()).ToList(); @@ -805,12 +806,16 @@ private void ApplyRename(Solution newSolution, IWaitContext waitContext) if (newDocument.SupportsSyntaxTree) { - var root = newDocument.GetSyntaxRootSynchronously(waitContext.CancellationToken); + // We pass CancellationToken.None here because we don't have a usable token to pass. The IUIThreadOperationContext + // passed here as a cancellation token, but the caller in CommitCore has already turned off cancellation + // because we're committed to the update at this point. If we ever want to pass cancellation here, we'd want to move this + // part back out of this method and before the point where we've already opened a global transaction. + var root = newDocument.GetSyntaxRootSynchronously(CancellationToken.None); finalSolution = finalSolution.WithDocumentSyntaxRoot(id, root); } else { - var newText = newDocument.GetTextAsync(waitContext.CancellationToken).WaitAndGetResult(waitContext.CancellationToken); + var newText = newDocument.GetTextSynchronously(CancellationToken.None); finalSolution = finalSolution.WithDocumentText(id, newText); } @@ -834,6 +839,7 @@ private void ApplyRename(Solution newSolution, IWaitContext waitContext) if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, this.ReplacementText)) { var notificationService = _workspace.Services.GetService(); + operationContext.TakeOwnership(); notificationService.SendNotification( EditorFeaturesResources.Rename_operation_was_not_properly_completed_Some_file_might_not_have_been_updated, EditorFeaturesResources.Rename_Symbol, diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs index c1c7cbbfee728..1cebdf7e0f78e 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -195,8 +196,6 @@ private AsyncCompletionData.CommitResult Commit( return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); } - var disallowAddingImports = session.Properties.ContainsProperty(CompletionSource.DisallowAddingImports); - CompletionChange change; // We met an issue when external code threw an OperationCanceledException and the cancellationToken is not cancelled. @@ -204,7 +203,13 @@ private AsyncCompletionData.CommitResult Commit( // See https://github.com/dotnet/roslyn/issues/38455. try { - change = completionService.GetChangeAsync(document, roslynItem, completionListSpan, commitCharacter, disallowAddingImports, cancellationToken).WaitAndGetResult(cancellationToken); + // Cached items have a span computed at the point they were created. This span may no + // longer be valid when used again. In that case, override the span with the latest span + // for the completion list itself. + if (roslynItem.Flags.IsCached()) + roslynItem.Span = completionListSpan; + + change = completionService.GetChangeAsync(document, roslynItem, commitCharacter, cancellationToken).WaitAndGetResult(cancellationToken); } catch (OperationCanceledException e) when (e.CancellationToken != cancellationToken && FatalError.ReportAndCatch(e)) { @@ -267,13 +272,13 @@ private AsyncCompletionData.CommitResult Commit( // The edit updates the snapshot however other extensions may make changes there. // Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapsot defined above. var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - var formattingService = currentDocument?.GetRequiredLanguageService(); + var formattingService = currentDocument?.GetRequiredLanguageService(); if (currentDocument != null && formattingService != null) { var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); var changes = formattingService.GetFormattingChangesAsync( - currentDocument, spanToFormat.Span.ToTextSpan(), CancellationToken.None).WaitAndGetResult(CancellationToken.None); + currentDocument, spanToFormat.Span.ToTextSpan(), documentOptions: null, CancellationToken.None).WaitAndGetResult(CancellationToken.None); currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, CancellationToken.None); } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs index 214f1bc391718..d8069ec1f6559 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -36,7 +36,6 @@ internal sealed class CompletionSource : ForegroundThreadAffinitizedObject, IAsy internal const string RoslynItem = nameof(RoslynItem); internal const string TriggerLocation = nameof(TriggerLocation); internal const string CompletionListSpan = nameof(CompletionListSpan); - internal const string DisallowAddingImports = nameof(DisallowAddingImports); internal const string InsertionText = nameof(InsertionText); internal const string HasSuggestionItemOptions = nameof(HasSuggestionItemOptions); internal const string Description = nameof(Description); @@ -272,8 +271,7 @@ private bool TryInvokeSnippetCompletion( { options = options .WithChangedOption(CompletionControllerOptions.FilterOutOfScopeLocals, false) - .WithChangedOption(CompletionControllerOptions.ShowXmlDocCommentCompletion, false) - .WithChangedOption(CompletionServiceOptions.DisallowAddingImports, true); + .WithChangedOption(CompletionControllerOptions.ShowXmlDocCommentCompletion, false); } var (completionList, expandItemsAvailable) = await completionService.GetCompletionsInternalAsync( @@ -318,11 +316,6 @@ private bool TryInvokeSnippetCompletion( // It's OK to overwrite this value when expanded items are requested. session.Properties[CompletionListSpan] = completionList.Span; - if (_isDebuggerTextView) - { - session.Properties[DisallowAddingImports] = true; - } - // This is a code supporting original completion scenarios: // Controller.Session_ComputeModel: if completionList.SuggestionModeItem != null, then suggestionMode = true // If there are suggestionItemOptions, then later HandleNormalFiltering should set selection to SoftSelection. diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs index f496889858b85..2180361d30d65 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs @@ -130,10 +130,10 @@ private static readonly ObjectPool> s_listOfMatchResultPool // 2. a non-empty set of expanders are unselected var nonExpanderFilterStates = data.SelectedFilters.WhereAsArray(f => !(f.Filter is CompletionExpander)); - var selectedNonExpanderFilters = nonExpanderFilterStates.Where(f => f.IsSelected).SelectAsArray(f => f.Filter); + var selectedNonExpanderFilters = nonExpanderFilterStates.SelectAsArray(f => f.IsSelected, f => f.Filter); var needToFilter = selectedNonExpanderFilters.Length > 0 && selectedNonExpanderFilters.Length < nonExpanderFilterStates.Length; - var unselectedExpanders = data.SelectedFilters.Where(f => !f.IsSelected && f.Filter is CompletionExpander).SelectAsArray(f => f.Filter); + var unselectedExpanders = data.SelectedFilters.SelectAsArray(f => !f.IsSelected && f.Filter is CompletionExpander, f => f.Filter); var needToFilterExpanded = unselectedExpanders.Length > 0; if (session.TextView.Properties.TryGetProperty(CompletionSource.TargetTypeFilterExperimentEnabled, out bool isExperimentEnabled) && isExperimentEnabled) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs index 73f0cc055a92c..629e21ae5fa55 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Helpers.cs @@ -49,6 +49,14 @@ private static IReadOnlyCollection BuildInteractiveTextElements( while (index < taggedTexts.Length) { var part = taggedTexts[index]; + + // These tags can be ignored - they are for markdown formatting only. + if (part.Tag is TextTags.CodeBlockStart or TextTags.CodeBlockEnd) + { + index++; + continue; + } + if (part.Tag == TextTags.ContainerStart) { if (currentRuns.Count > 0) diff --git a/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs index b6d86d600d11e..ff372be59130e 100644 --- a/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs @@ -46,19 +46,20 @@ internal class HighlighterViewTaggerProvider : AsynchronousViewTaggerProvider TaggerDelay.NearImmediate; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { return TaggerEventSources.Compose( - TaggerEventSources.OnTextChanged(subjectBuffer, TaggerDelay.OnIdle), - TaggerEventSources.OnCaretPositionChanged(textView, subjectBuffer, TaggerDelay.NearImmediate), - TaggerEventSources.OnParseOptionChanged(subjectBuffer, TaggerDelay.NearImmediate)); + TaggerEventSources.OnTextChanged(subjectBuffer), + TaggerEventSources.OnCaretPositionChanged(textView, subjectBuffer), + TaggerEventSources.OnParseOptionChanged(subjectBuffer)); } protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 8206a984b9494..a645f439539ea 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -3,20 +3,21 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; +using IUIThreadOperationExecutor = Microsoft.VisualStudio.Utilities.IUIThreadOperationExecutor; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { @@ -31,30 +32,36 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje { private readonly INavigationBarPresenter _presenter; private readonly ITextBuffer _subjectBuffer; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly IAsynchronousOperationListener _asyncListener; + private bool _disconnected = false; + private Workspace? _workspace; + /// - /// If we have pushed a full list to the presenter in response to a focus event, this - /// contains the version stamp of the list that was pushed. It is null if the last thing - /// pushed to the list was due to a caret move or file change. + /// Latest model and selected items produced once completes and + /// presents the single item to the view. These can then be read in when the dropdown is expanded and we want + /// to show all items. /// - private VersionStamp? _versionStampOfFullListPushedToPresenter = null; + private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _latestModelAndSelectedInfo_OnlyAccessOnUIThread; - private bool _disconnected = false; - private Workspace? _workspace; + /// + /// The last full information we have presented. If we end up wanting to present the same thing again, we can + /// just skip doing that as the UI will already know about this. + /// + private (ImmutableArray projectItems, NavigationBarProjectItem? selectedProjectItem, NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastPresentedInfo; public NavigationBarController( IThreadingContext threadingContext, INavigationBarPresenter presenter, ITextBuffer subjectBuffer, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, IAsynchronousOperationListener asyncListener) : base(threadingContext) { _presenter = presenter; _subjectBuffer = subjectBuffer; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _asyncListener = asyncListener; presenter.CaretMoved += OnCaretMoved; @@ -66,13 +73,13 @@ public NavigationBarController( subjectBuffer.PostChanged += OnSubjectBufferPostChanged; // Initialize the tasks to be an empty model so we never have to deal with a null case. - _modelTask = Task.FromResult( - new NavigationBarModel( - SpecializedCollections.EmptyList(), - default, - null)); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread.model = new( + ImmutableArray.Empty, + semanticVersionStamp: default, + itemService: null!); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread.selectedInfo = new(typeItem: null, memberItem: null); - _selectedItemInfoTask = Task.FromResult(new NavigationBarSelectedTypeAndMember(null, null)); + _modelTask = Task.FromResult(_latestModelAndSelectedInfo_OnlyAccessOnUIThread.model); } public void SetWorkspace(Workspace? newWorkspace) @@ -95,16 +102,11 @@ private void ConnectToWorkspace(Workspace workspace) _workspace = workspace; _workspace.WorkspaceChanged += this.OnWorkspaceChanged; - - void connectToNewWorkspace() - { - // For the first time you open the file, we'll start immediately - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); - } + _workspace.DocumentActiveContextChanged += this.OnDocumentActiveContextChanged; if (IsForeground()) { - connectToNewWorkspace(); + ConnectToNewWorkspace(); } else { @@ -113,15 +115,24 @@ void connectToNewWorkspace() { await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - connectToNewWorkspace(); + ConnectToNewWorkspace(); }).CompletesAsyncOperation(asyncToken); } + + return; + + void ConnectToNewWorkspace() + { + // For the first time you open the file, we'll start immediately + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); + } } private void DisconnectFromWorkspace() { if (_workspace != null) { + _workspace.DocumentActiveContextChanged -= this.OnDocumentActiveContextChanged; _workspace.WorkspaceChanged -= this.OnWorkspaceChanged; _workspace = null; } @@ -169,7 +180,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) if (currentContextDocumentId != null && currentContextDocumentId.ProjectId == args.ProjectId) { - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } } @@ -181,87 +192,81 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) if (currentContextDocumentId != null && currentContextDocumentId == args.DocumentId) { // The context has changed, so update everything. - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } } - private void OnSubjectBufferPostChanged(object? sender, EventArgs e) + private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs args) { AssertIsForeground(); + if (args.Solution.Workspace != _workspace) + return; - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + var currentContextDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); + if (args.NewActiveContextDocumentId == currentContextDocumentId || + args.OldActiveContextDocumentId == currentContextDocumentId) + { + // if the active context changed, recompute the types/member as they may be changed as well. + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); + } } - private void OnCaretMoved(object? sender, EventArgs e) + private void OnSubjectBufferPostChanged(object? sender, EventArgs e) { AssertIsForeground(); - StartSelectedItemUpdateTask(delay: TaggerConstants.NearImmediateDelay, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay); } - private void OnViewFocused(object? sender, EventArgs e) + private void OnCaretMoved(object? sender, EventArgs e) { AssertIsForeground(); - StartSelectedItemUpdateTask(delay: TaggerConstants.ShortDelay, updateUIWhenDone: true); + StartSelectedItemUpdateTask(delay: TaggerConstants.NearImmediateDelay); } - private void OnDropDownFocused(object? sender, EventArgs e) + private void OnViewFocused(object? sender, EventArgs e) { AssertIsForeground(); - - // Refresh the drop downs to their full information - _waitIndicator.Wait( - EditorFeaturesResources.Navigation_Bars, - EditorFeaturesResources.Refreshing_navigation_bars, - allowCancel: true, - action: context => UpdateDropDownsSynchronously(context.CancellationToken)); + StartSelectedItemUpdateTask(delay: TaggerConstants.ShortDelay); } - private void UpdateDropDownsSynchronously(CancellationToken cancellationToken) + private void OnDropDownFocused(object? sender, EventArgs e) { AssertIsForeground(); - // If the presenter already has the full list and the model is already complete, then we - // don't have to do any further computation nor push anything to the presenter - if (PresenterAlreadyHaveUpToDateFullList(cancellationToken)) - { + var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) return; - } - - // We need to ensure that all the state computation is up to date, so cancel any - // previous work and ensure the model is up to date - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: false); - // Wait for the work to be complete. We'll wait with our cancellationToken, so if the - // user hits cancel we won't block them, but the computation can still continue - - using (Logger.LogBlock(FunctionId.NavigationBar_UpdateDropDownsSynchronously_WaitForModel, cancellationToken)) - { - _modelTask.Wait(cancellationToken); - } + // Grab and present whatever information we have at this point. + GetProjectItems(out var projectItems, out var selectedProjectItem); + var (model, selectedInfo) = _latestModelAndSelectedInfo_OnlyAccessOnUIThread; - using (Logger.LogBlock(FunctionId.NavigationBar_UpdateDropDownsSynchronously_WaitForSelectedItemInfo, cancellationToken)) + if (Equals(model, _lastPresentedInfo.model) && + Equals(selectedInfo, _lastPresentedInfo.selectedInfo) && + Equals(selectedProjectItem, _lastPresentedInfo.selectedProjectItem) && + projectItems.SequenceEqual(_lastPresentedInfo.projectItems)) { - _selectedItemInfoTask.Wait(cancellationToken); + // Nothing changed, so we can skip presenting these items. + return; } - GetProjectItems(out var projectItems, out var selectedProjectItem); - _presenter.PresentItems( projectItems, selectedProjectItem, - _modelTask.Result.Types, - _selectedItemInfoTask.Result.TypeItem, - _selectedItemInfoTask.Result.MemberItem); - _versionStampOfFullListPushedToPresenter = _modelTask.Result.SemanticVersionStamp; + model.Types, + selectedInfo.TypeItem, + selectedInfo.MemberItem); + + _lastPresentedInfo = (projectItems, selectedProjectItem, model, selectedInfo); } - private void GetProjectItems(out IList projectItems, out NavigationBarProjectItem? selectedProjectItem) + private void GetProjectItems(out ImmutableArray projectItems, out NavigationBarProjectItem? selectedProjectItem) { var documents = _subjectBuffer.CurrentSnapshot.GetRelatedDocumentsWithChanges(); if (!documents.Any()) { - projectItems = SpecializedCollections.EmptyList(); + projectItems = ImmutableArray.Empty; selectedProjectItem = null; return; } @@ -272,9 +277,7 @@ private void GetProjectItems(out IList projectItems, o d.Project.GetGlyph(), workspace: d.Project.Solution.Workspace, documentId: d.Id, - language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToList(); - - projectItems.Do(i => i.InitializeTrackingSpans(_subjectBuffer.CurrentSnapshot)); + language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToImmutableArray(); var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); selectedProjectItem = document != null @@ -282,29 +285,6 @@ private void GetProjectItems(out IList projectItems, o : projectItems.First(); } - /// - /// Check if the presenter has already been pushed the full model that corresponds to the - /// current buffer's project version stamp. - /// - private bool PresenterAlreadyHaveUpToDateFullList(CancellationToken cancellationToken) - { - AssertIsForeground(); - - // If it doesn't have a full list pushed, then of course not - if (_versionStampOfFullListPushedToPresenter == null) - { - return false; - } - - var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - { - return false; - } - - return document.Project.GetDependentSemanticVersionAsync(cancellationToken).WaitAndGetResult(cancellationToken) == _versionStampOfFullListPushedToPresenter; - } - private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember selectedItems) { AssertIsForeground(); @@ -314,24 +294,20 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel NavigationBarItem? newLeft = null; NavigationBarItem? newRight = null; - var listOfLeft = new List(); - var listOfRight = new List(); + using var _1 = ArrayBuilder.GetInstance(out var listOfLeft); + using var _2 = ArrayBuilder.GetInstance(out var listOfRight); if (oldRight != null) { - newRight = new NavigationBarPresentedItem(oldRight.Text, oldRight.Glyph, oldRight.Spans, oldRight.ChildItems, oldRight.Bolded, oldRight.Grayed || selectedItems.ShowMemberItemGrayed) - { - TrackingSpans = oldRight.TrackingSpans - }; + newRight = new NavigationBarPresentedItem( + oldRight.Text, oldRight.Glyph, oldRight.TrackingSpans, oldRight.NavigationTrackingSpan, oldRight.ChildItems, oldRight.Bolded, oldRight.Grayed || selectedItems.ShowMemberItemGrayed); listOfRight.Add(newRight); } if (oldLeft != null) { - newLeft = new NavigationBarPresentedItem(oldLeft.Text, oldLeft.Glyph, oldLeft.Spans, listOfRight, oldLeft.Bolded, oldLeft.Grayed || selectedItems.ShowTypeItemGrayed) - { - TrackingSpans = oldLeft.TrackingSpans - }; + newLeft = new NavigationBarPresentedItem( + oldLeft.Text, oldLeft.Glyph, oldLeft.TrackingSpans, oldLeft.NavigationTrackingSpan, listOfRight.ToImmutable(), oldLeft.Bolded, oldLeft.Grayed || selectedItems.ShowTypeItemGrayed); listOfLeft.Add(newLeft); } @@ -340,21 +316,38 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel _presenter.PresentItems( projectItems, selectedProjectItem, - listOfLeft, + listOfLeft.ToImmutable(), newLeft, newRight); - _versionStampOfFullListPushedToPresenter = null; } private void OnItemSelected(object? sender, NavigationBarItemSelectedEventArgs e) { AssertIsForeground(); + var token = _asyncListener.BeginAsyncOperation(nameof(OnItemSelected)); + var task = OnItemSelectedAsync(e.Item); + _ = task.CompletesAsyncOperation(token); + } - _waitIndicator.Wait( + private async Task OnItemSelectedAsync(NavigationBarItem item) + { + AssertIsForeground(); + using var waitContext = _uiThreadOperationExecutor.BeginExecute( EditorFeaturesResources.Navigation_Bars, EditorFeaturesResources.Refreshing_navigation_bars, - allowCancel: true, - action: context => ProcessItemSelectionSynchronously(e.Item, context.CancellationToken)); + allowCancellation: true, + showProgress: false); + + try + { + await ProcessItemSelectionAsync(item, waitContext.UserCancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } } /// @@ -362,10 +355,9 @@ private void OnItemSelected(object? sender, NavigationBarItemSelectedEventArgs e /// /// The selected item. /// A cancellation token from the wait context. - private void ProcessItemSelectionSynchronously(NavigationBarItem item, CancellationToken cancellationToken) + private async Task ProcessItemSelectionAsync(NavigationBarItem item, CancellationToken cancellationToken) { AssertIsForeground(); - if (item is NavigationBarPresentedItem) { // Presented items are not navigable, but they may be selected due to a race @@ -377,8 +369,6 @@ private void ProcessItemSelectionSynchronously(NavigationBarItem item, Cancellat if (item is NavigationBarProjectItem projectItem) { projectItem.SwitchToContext(); - - // TODO: navigate to document / focus text view } else { @@ -387,21 +377,25 @@ private void ProcessItemSelectionSynchronously(NavigationBarItem item, Cancellat var document = _subjectBuffer.CurrentSnapshot.AsText().GetDocumentWithFrozenPartialSemantics(cancellationToken); if (document != null) { - var languageService = document.GetRequiredLanguageService(); - - NavigateToItem(item, document, _subjectBuffer.CurrentSnapshot, languageService, cancellationToken); + var navBarService = document.GetRequiredLanguageService(); + var snapshot = _subjectBuffer.CurrentSnapshot; + var view = _presenter.TryGetCurrentView(); + + // ConfigureAwait(true) as we have to come back to UI thread in order to kick of the refresh task + // below. Note that we only want to refresh if selecting the item had an effect (either navigating + // or generating). If nothing happened to don't want to refresh. This is important as some items + // exist in the type list that are only there to show a set a particular set of items in the member + // list. So selecting such an item should only update the member list, and we do not want a refresh + // to wipe that out. + if (!await navBarService.TryNavigateToItemAsync(document, item, view, snapshot, cancellationToken).ConfigureAwait(true)) + return; } } - // Now that the edit has been done, refresh to make sure everything is up-to-date. At - // this point, we now use CancellationToken.None to ensure we're properly refreshed. - UpdateDropDownsSynchronously(CancellationToken.None); - } - - private void NavigateToItem(NavigationBarItem item, Document document, ITextSnapshot snapshot, INavigationBarItemService languageService, CancellationToken cancellationToken) - { - item.Spans = item.TrackingSpans.Select(ts => ts.GetSpan(snapshot).Span.ToTextSpan()).ToList(); - languageService.NavigateToItem(document, item, _presenter.TryGetCurrentView(), cancellationToken); + // Now that the edit has been done, refresh to make sure everything is up-to-date. + // Have to make sure we come back to the main thread for this. + AssertIsForeground(); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs index 3c2a76c632634..0c4460514dd88 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarControllerFactoryService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { @@ -18,18 +19,18 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar internal class NavigationBarControllerFactoryService : INavigationBarControllerFactoryService { private readonly IThreadingContext _threadingContext; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uIThreadOperationExecutor; private readonly IAsynchronousOperationListener _asyncListener; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public NavigationBarControllerFactoryService( IThreadingContext threadingContext, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uIThreadOperationExecutor, IAsynchronousOperationListenerProvider listenerProvider) { _threadingContext = threadingContext; - _waitIndicator = waitIndicator; + _uIThreadOperationExecutor = uIThreadOperationExecutor; _asyncListener = listenerProvider.GetListener(FeatureAttribute.NavigationBar); } @@ -39,7 +40,7 @@ public INavigationBarController CreateController(INavigationBarPresenter present _threadingContext, presenter, textBuffer, - _waitIndicator, + _uIThreadOperationExecutor, _asyncListener); } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 3ee35f2761d2c..cc6ddd26ee71f 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -4,18 +4,20 @@ #nullable disable +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; +using Microsoft.VisualStudio.Threading; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { @@ -25,13 +27,14 @@ internal partial class NavigationBarController /// The computation of the last model. /// private Task _modelTask; - private NavigationBarModel _lastCompletedModel; + private CancellationTokenSource _modelTaskCancellationSource = new(); + private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); /// /// Starts a new task to compute the model based on the current text. /// - private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, int selectedItemUpdateDelay, bool updateUIWhenDone) + private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay) { AssertIsForeground(); @@ -45,23 +48,47 @@ private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, in // Enqueue a new computation for the model var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartModelUpdateTask"); - _modelTask = - Task.Delay(modelUpdateDelay, cancellationToken) - .SafeContinueWithFromAsync( - _ => ComputeModelAsync(textSnapshot, cancellationToken), - cancellationToken, - TaskContinuationOptions.OnlyOnRanToCompletion, - TaskScheduler.Default); + _modelTask = ComputeModelAfterDelayAsync(_modelTask, textSnapshot, modelUpdateDelay, cancellationToken); _modelTask.CompletesAsyncOperation(asyncToken); - StartSelectedItemUpdateTask(selectedItemUpdateDelay, updateUIWhenDone); + StartSelectedItemUpdateTask(delay: 0); + } + + private static async Task ComputeModelAfterDelayAsync( + Task modelTask, ITextSnapshot textSnapshot, int modelUpdateDelay, CancellationToken cancellationToken) + { + var previousModel = await modelTask.ConfigureAwait(false); + if (!cancellationToken.IsCancellationRequested) + { + try + { + await Task.Delay(modelUpdateDelay, cancellationToken).ConfigureAwait(false); + return await ComputeModelAsync(previousModel, textSnapshot, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + // If we canceled, then just return along whatever we have computed so far. Note: this means the + // _modelTask task will never enter the canceled state. It always represents the last successfully + // computed model. + return previousModel; } /// /// Computes a model for the given snapshot. /// - private async Task ComputeModelAsync(ITextSnapshot snapshot, CancellationToken cancellationToken) + private static async Task ComputeModelAsync( + NavigationBarModel lastCompletedModel, ITextSnapshot snapshot, CancellationToken cancellationToken) { + // Ensure we switch to the threadpool before calling GetDocumentWithFrozenPartialSemantics. It ensures + // that any IO that performs is not potentially on the UI thread. + await TaskScheduler.Default; + // When computing items just get the partial semantics workspace. This will ensure we can get data for this // file, and hopefully have enough loaded to get data for other files in the case of partial types. In the // event the other files aren't available, then partial-type information won't be correct. That's ok though @@ -70,99 +97,82 @@ private async Task ComputeModelAsync(ITextSnapshot snapshot, // compilation data (like skeleton assemblies). var document = snapshot.AsText().GetDocumentWithFrozenPartialSemantics(cancellationToken); if (document == null) - { - _lastCompletedModel = null; - return _lastCompletedModel; - } + return null; - // TODO: remove .FirstOrDefault() var languageService = document.GetLanguageService(); if (languageService != null) { // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. // the model should be only updated here - if (_lastCompletedModel != null) + if (lastCompletedModel != null) { var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); - if (_lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(_lastCompletedModel, snapshot, cancellationToken)) + if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) { // it looks like we can re-use previous model - return _lastCompletedModel; + return lastCompletedModel; } } using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) { - var items = await languageService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); - if (items != null) - { - items.Do(i => i.InitializeTrackingSpans(snapshot)); - var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - - _lastCompletedModel = new NavigationBarModel(items, version, languageService); - return _lastCompletedModel; - } + var items = await languageService.GetItemsAsync(document, snapshot, cancellationToken).ConfigureAwait(false); + var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + return new NavigationBarModel(items, version, languageService); } } - _lastCompletedModel ??= new NavigationBarModel(SpecializedCollections.EmptyList(), new VersionStamp(), null); - return _lastCompletedModel; + return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } - private Task _selectedItemInfoTask; - private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); - /// /// Starts a new task to compute what item should be selected. /// - private void StartSelectedItemUpdateTask(int delay, bool updateUIWhenDone) + private void StartSelectedItemUpdateTask(int delay) { AssertIsForeground(); var currentView = _presenter.TryGetCurrentView(); - if (currentView == null) - { + var subjectBufferCaretPosition = currentView?.GetCaretPoint(_subjectBuffer); + if (!subjectBufferCaretPosition.HasValue) return; - } // Cancel off any existing work _selectedItemInfoTaskCancellationSource.Cancel(); _selectedItemInfoTaskCancellationSource = new CancellationTokenSource(); - var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; - var subjectBufferCaretPosition = currentView.GetCaretPoint(_subjectBuffer); - if (!subjectBufferCaretPosition.HasValue) - { + var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); + var selectedItemInfoTask = DetermineSelectedItemInfoAsync(_modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); + selectedItemInfoTask.CompletesAsyncOperation(asyncToken); + } + + private async Task DetermineSelectedItemInfoAsync( + Task lastModelTask, + int delay, + SnapshotPoint caretPosition, + CancellationToken cancellationToken) + { + // First wait the delay before doing any other work. That way if we get canceled due to other events (like + // the user moving around), we don't end up doing anything, and the next task can take over. + await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + + var lastModel = await lastModelTask.ConfigureAwait(false); + if (cancellationToken.IsCancellationRequested) return; - } - var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); + var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); - // Enqueue a new computation for the selected item - _selectedItemInfoTask = _modelTask.ContinueWithAfterDelay( - t => t.IsCanceled ? new NavigationBarSelectedTypeAndMember(null, null) - : ComputeSelectedTypeAndMember(t.Result, subjectBufferCaretPosition.Value, cancellationToken), - cancellationToken, - delay, - TaskContinuationOptions.None, - TaskScheduler.Default); - _selectedItemInfoTask.CompletesAsyncOperation(asyncToken); - - if (updateUIWhenDone) - { - asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask.UpdateUI"); - _selectedItemInfoTask.SafeContinueWithFromAsync( - async t => - { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - PushSelectedItemsToPresenter(t.Result); - }, - cancellationToken, - TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default).CompletesAsyncOperation(asyncToken); - } + AssertIsForeground(); + + // Update the UI to show *just* the type/member that was selected. We don't need it to know about all items + // as the user can only see one at a time as they're editing in a document. However, once we've done this, + // store the full list of items as well so that if the user expands the dropdown, we can take all those + // values and shove them in so it appears as if the lists were always fully realized. + _latestModelAndSelectedInfo_OnlyAccessOnUIThread = (lastModel, currentSelectedItem); + PushSelectedItemsToPresenter(currentSelectedItem); } internal static NavigationBarSelectedTypeAndMember ComputeSelectedTypeAndMember(NavigationBarModel model, SnapshotPoint caretPosition, CancellationToken cancellationToken) @@ -194,10 +204,11 @@ private static (T item, bool gray) GetMatchingItem(IEnumerable items, Snap foreach (var item in items) { - foreach (var span in item.TrackingSpans.Select(s => s.GetSpan(point.Snapshot))) + foreach (var trackingSpan in item.TrackingSpans) { cancellationToken.ThrowIfCancellationRequested(); + var span = trackingSpan.GetSpan(point.Snapshot); if (span.Contains(point) || span.End == point) { // This is the item we should show normally. We'll continue looking at other @@ -247,35 +258,41 @@ private static bool SpanStillValid(NavigationBarModel model, ITextSnapshot snaps // price soon or later to figure out selected item. foreach (var type in model.Types) { - if (!SpanStillValid(type.TrackingSpans, snapshot)) - { + if (!SpansStillValid(type, snapshot)) return false; - } foreach (var member in type.ChildItems) { cancellationToken.ThrowIfCancellationRequested(); - if (!SpanStillValid(member.TrackingSpans, snapshot)) - { + if (!SpansStillValid(member, snapshot)) return false; - } } } return true; } - private static bool SpanStillValid(IList spans, ITextSnapshot snapshot) + private static bool SpansStillValid(NavigationBarItem item, ITextSnapshot snapshot) { - for (var i = 0; i < spans.Count; i++) + if (item.NavigationTrackingSpan != null) + { + var currentSpan = item.NavigationTrackingSpan.GetSpan(snapshot); + if (currentSpan.IsEmpty) + return false; + } + + foreach (var span in item.TrackingSpans) { - var span = spans[i]; var currentSpan = span.GetSpan(snapshot); if (currentSpan.IsEmpty) - { return false; - } + } + + foreach (var childItem in item.ChildItems) + { + if (!SpansStillValid(childItem, snapshot)) + return false; } return true; diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs index e7739b569b41b..99faf5fad32cd 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.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.Collections.Generic; +using System.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { internal sealed class NavigationBarModel { - public IList Types { get; } + public ImmutableArray Types { get; } /// /// The VersionStamp of the project when this model was computed. @@ -20,7 +18,7 @@ internal sealed class NavigationBarModel public INavigationBarItemService ItemService { get; } - public NavigationBarModel(IList types, VersionStamp semanticVersionStamp, INavigationBarItemService itemService) + public NavigationBarModel(ImmutableArray types, VersionStamp semanticVersionStamp, INavigationBarItemService itemService) { Contract.ThrowIfNull(types); diff --git a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs index 58bc8a296ac00..a7c730d93ee17 100644 --- a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs @@ -5,8 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; @@ -14,6 +16,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Structure; +using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -40,16 +43,61 @@ internal abstract partial class AbstractStructureTaggerProvider : protected AbstractStructureTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, IEditorOptionsFactoryService editorOptionsFactoryService, IProjectionBufferFactoryService projectionBufferFactoryService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.Outlining), notificationService) + : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.Outlining)) { EditorOptionsFactoryService = editorOptionsFactoryService; ProjectionBufferFactoryService = projectionBufferFactoryService; } + protected override TaggerDelay EventChangeDelay => TaggerDelay.OnIdle; + + protected override bool ComputeInitialTagsSynchronously(ITextBuffer subjectBuffer) + { + // If we can't find this doc, or outlining is not enabled for it, no need to computed anything synchronously. + + var openDocument = subjectBuffer.AsTextContainer().GetRelatedDocuments().FirstOrDefault(); + if (openDocument == null) + return false; + + var workspace = openDocument.Project.Solution.Workspace; + if (!workspace.Options.GetOption(FeatureOnOffOptions.Outlining, openDocument.Project.Language)) + return false; + + // If we're a metadata-as-source doc, we need to compute the initial set of tags synchronously + // so that we can collapse all the .IsImplementation tags to keep the UI clean and condensed. + var isMetadataAsSource = workspace.Kind == WorkspaceKind.MetadataAsSource; + if (isMetadataAsSource) + return true; + + // If we contain any #region sections, we want to collapse those automatically on open the first + // time a doc is ever opened. So we need to compute the initial tags synchronously in order to + // do that. + if (ContainsRegionTag(subjectBuffer.CurrentSnapshot)) + return true; + + return false; + + static bool ContainsRegionTag(ITextSnapshot textSnapshot) + { + foreach (var line in textSnapshot.Lines) + { + if (StartsWithRegionTag(line)) + return true; + } + + return false; + + static bool StartsWithRegionTag(ITextSnapshotLine line) + { + var start = line.GetFirstNonWhitespacePosition(); + return start != null && line.StartsWith(start.Value, "#region", ignoreCase: true); + } + } + } + protected sealed override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) { // We listen to the following events: @@ -64,21 +112,18 @@ protected sealed override ITaggerEventSource CreateEventSource(ITextView textVie // the file will not have outline spans. When the workspace is created, we want to // then produce the right outlining spans. return TaggerEventSources.Compose( - TaggerEventSources.OnTextChanged(subjectBuffer, TaggerDelay.OnIdle), - TaggerEventSources.OnParseOptionChanged(subjectBuffer, TaggerDelay.OnIdle), - TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.OnIdle), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowBlockStructureGuidesForCodeLevelConstructs, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowBlockStructureGuidesForDeclarationLevelConstructs, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowOutliningForCodeLevelConstructs, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowOutliningForDeclarationLevelConstructs, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowOutliningForCommentsAndPreprocessorRegions, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.CollapseRegionsWhenCollapsingToDefinitions, TaggerDelay.NearImmediate)); + TaggerEventSources.OnTextChanged(subjectBuffer), + TaggerEventSources.OnParseOptionChanged(subjectBuffer), + TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowBlockStructureGuidesForCodeLevelConstructs), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowBlockStructureGuidesForDeclarationLevelConstructs), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowOutliningForCodeLevelConstructs), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowOutliningForDeclarationLevelConstructs), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.ShowOutliningForCommentsAndPreprocessorRegions), + TaggerEventSources.OnOptionChanged(subjectBuffer, BlockStructureOptions.CollapseRegionsWhenCollapsingToDefinitions)); } - /// - /// Keep this in sync with - /// protected sealed override async Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) { @@ -109,37 +154,6 @@ protected sealed override async Task ProduceTagsAsync( } } - /// - /// Keep this in sync with - /// - protected sealed override void ProduceTagsSynchronously( - TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) - { - try - { - var document = documentSnapshotSpan.Document; - if (document == null) - return; - - // Let LSP handle producing tags in the cloud scenario - if (documentSnapshotSpan.SnapshotSpan.Snapshot.TextBuffer.IsInLspEditorContext()) - return; - - var outliningService = BlockStructureService.GetService(document); - if (outliningService == null) - return; - - var blockStructure = outliningService.GetBlockStructure(document, context.CancellationToken); - ProcessSpans( - context, documentSnapshotSpan.SnapshotSpan, outliningService, - blockStructure.Spans); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) - { - throw ExceptionUtilities.Unreachable; - } - } - private void ProcessSpans( TaggerContext context, SnapshotSpan snapshotSpan, diff --git a/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.TextStructureNavigator.cs b/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.TextStructureNavigator.cs index 8cd8b4715d80b..66a4a3de22bd7 100644 --- a/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.TextStructureNavigator.cs +++ b/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.TextStructureNavigator.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -24,13 +23,13 @@ private class TextStructureNavigator : ITextStructureNavigator private readonly ITextBuffer _subjectBuffer; private readonly ITextStructureNavigator _naturalLanguageNavigator; private readonly AbstractTextStructureNavigatorProvider _provider; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; internal TextStructureNavigator( ITextBuffer subjectBuffer, ITextStructureNavigator naturalLanguageNavigator, AbstractTextStructureNavigatorProvider provider, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uIThreadOperationExecutor) { Contract.ThrowIfNull(subjectBuffer); Contract.ThrowIfNull(naturalLanguageNavigator); @@ -39,7 +38,7 @@ internal TextStructureNavigator( _subjectBuffer = subjectBuffer; _naturalLanguageNavigator = naturalLanguageNavigator; _provider = provider; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uIThreadOperationExecutor; } public IContentType ContentType => _subjectBuffer.ContentType; @@ -49,13 +48,14 @@ public TextExtent GetExtentOfWord(SnapshotPoint currentPosition) using (Logger.LogBlock(FunctionId.TextStructureNavigator_GetExtentOfWord, CancellationToken.None)) { var result = default(TextExtent); - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Text_Navigation, - message: EditorFeaturesResources.Finding_word_extent, - allowCancel: true, - action: waitContext => + defaultDescription: EditorFeaturesResources.Finding_word_extent, + allowCancellation: true, + showProgress: false, + action: context => { - result = GetExtentOfWordWorker(currentPosition, waitContext.CancellationToken); + result = GetExtentOfWordWorker(currentPosition, context.UserCancellationToken); }); return result; @@ -122,16 +122,17 @@ public SnapshotSpan GetSpanOfEnclosing(SnapshotSpan activeSpan) using (Logger.LogBlock(FunctionId.TextStructureNavigator_GetSpanOfEnclosing, CancellationToken.None)) { var span = default(SnapshotSpan); - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Text_Navigation, - message: EditorFeaturesResources.Finding_enclosing_span, - allowCancel: true, - action: waitContext => + defaultDescription: EditorFeaturesResources.Finding_enclosing_span, + allowCancellation: true, + showProgress: false, + action: context => { - span = GetSpanOfEnclosingWorker(activeSpan, waitContext.CancellationToken); + span = GetSpanOfEnclosingWorker(activeSpan, context.UserCancellationToken); }); - return result == WaitIndicatorResult.Completed ? span : activeSpan; + return result == UIThreadOperationStatus.Completed ? span : activeSpan; } } @@ -153,16 +154,17 @@ public SnapshotSpan GetSpanOfFirstChild(SnapshotSpan activeSpan) using (Logger.LogBlock(FunctionId.TextStructureNavigator_GetSpanOfFirstChild, CancellationToken.None)) { var span = default(SnapshotSpan); - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Text_Navigation, - message: EditorFeaturesResources.Finding_enclosing_span, - allowCancel: true, - action: waitContext => + defaultDescription: EditorFeaturesResources.Finding_enclosing_span, + allowCancellation: true, + showProgress: false, + action: context => { - span = GetSpanOfFirstChildWorker(activeSpan, waitContext.CancellationToken); + span = GetSpanOfFirstChildWorker(activeSpan, context.UserCancellationToken); }); - return result == WaitIndicatorResult.Completed ? span : activeSpan; + return result == UIThreadOperationStatus.Completed ? span : activeSpan; } } @@ -188,16 +190,17 @@ public SnapshotSpan GetSpanOfNextSibling(SnapshotSpan activeSpan) using (Logger.LogBlock(FunctionId.TextStructureNavigator_GetSpanOfNextSibling, CancellationToken.None)) { var span = default(SnapshotSpan); - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Text_Navigation, - message: EditorFeaturesResources.Finding_span_of_next_sibling, - allowCancel: true, - action: waitContext => + defaultDescription: EditorFeaturesResources.Finding_span_of_next_sibling, + allowCancellation: true, + showProgress: false, + action: context => { - span = GetSpanOfNextSiblingWorker(activeSpan, waitContext.CancellationToken); + span = GetSpanOfNextSiblingWorker(activeSpan, context.UserCancellationToken); }); - return result == WaitIndicatorResult.Completed ? span : activeSpan; + return result == UIThreadOperationStatus.Completed ? span : activeSpan; } } @@ -239,16 +242,17 @@ public SnapshotSpan GetSpanOfPreviousSibling(SnapshotSpan activeSpan) using (Logger.LogBlock(FunctionId.TextStructureNavigator_GetSpanOfPreviousSibling, CancellationToken.None)) { var span = default(SnapshotSpan); - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: EditorFeaturesResources.Text_Navigation, - message: EditorFeaturesResources.Finding_span_of_previous_sibling, - allowCancel: true, - action: waitContext => + defaultDescription: EditorFeaturesResources.Finding_span_of_previous_sibling, + allowCancellation: true, + showProgress: false, + action: context => { - span = GetSpanOfPreviousSiblingWorker(activeSpan, waitContext.CancellationToken); + span = GetSpanOfPreviousSiblingWorker(activeSpan, context.UserCancellationToken); }); - return result == WaitIndicatorResult.Completed ? span : activeSpan; + return result == UIThreadOperationStatus.Completed ? span : activeSpan; } } diff --git a/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.cs b/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.cs index 3a051b0b3eef8..64df1b0f87cf0 100644 --- a/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.cs +++ b/src/EditorFeatures/Core/Implementation/TextStructureNavigation/AbstractTextStructureNavigatorProvider.cs @@ -4,7 +4,6 @@ #nullable disable -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Operations; @@ -17,19 +16,19 @@ internal abstract partial class AbstractTextStructureNavigatorProvider : ITextSt { private readonly ITextStructureNavigatorSelectorService _selectorService; private readonly IContentTypeRegistryService _contentTypeService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; protected AbstractTextStructureNavigatorProvider( ITextStructureNavigatorSelectorService selectorService, IContentTypeRegistryService contentTypeService, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uIThreadOperationExecutor) { Contract.ThrowIfNull(selectorService); Contract.ThrowIfNull(contentTypeService); _selectorService = selectorService; _contentTypeService = contentTypeService; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uIThreadOperationExecutor; } protected abstract bool ShouldSelectEntireTriviaFromStart(SyntaxTrivia trivia); @@ -48,7 +47,7 @@ public ITextStructureNavigator CreateTextStructureNavigator(ITextBuffer subjectB subjectBuffer, naturalLanguageNavigator, this, - _waitIndicator); + _uiThreadOperationExecutor); } } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/UIThreadOperationContextProgressTracker.cs b/src/EditorFeatures/Core/Implementation/UIThreadOperationContextProgressTracker.cs similarity index 96% rename from src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/UIThreadOperationContextProgressTracker.cs rename to src/EditorFeatures/Core/Implementation/UIThreadOperationContextProgressTracker.cs index b6ccff47eb66a..d0c311152d15e 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/UIThreadOperationContextProgressTracker.cs +++ b/src/EditorFeatures/Core/Implementation/UIThreadOperationContextProgressTracker.cs @@ -5,7 +5,7 @@ using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.VisualStudio.Utilities; -namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions +namespace Microsoft.CodeAnalysis.Editor.Implementation { internal class UIThreadOperationContextProgressTracker : IProgressTracker { diff --git a/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs b/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs new file mode 100644 index 0000000000000..cd522588ef52f --- /dev/null +++ b/src/EditorFeatures/Core/InheritanceMargin/AbstractInheritanceMarginService.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.SymbolMapping; +using Microsoft.CodeAnalysis.Text; +using static Microsoft.CodeAnalysis.InheritanceMargin.InheritanceMarginServiceHelper; + +namespace Microsoft.CodeAnalysis.InheritanceMargin +{ + internal abstract class AbstractInheritanceMarginService : IInheritanceMarginService + { + /// + /// Given the syntax nodes to search, + /// get all the method, event, property and type declaration syntax nodes. + /// + protected abstract ImmutableArray GetMembers(IEnumerable nodesToSearch); + + /// + /// Get the token that represents declaration node. + /// e.g. Identifier for method/property/event and this keyword for indexer. + /// + protected abstract SyntaxToken GetDeclarationToken(SyntaxNode declarationNode); + + public async ValueTask> GetInheritanceMemberItemsAsync( + Document document, + TextSpan spanToSearch, + CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var allDeclarationNodes = GetMembers(root.DescendantNodes(spanToSearch)); + if (allDeclarationNodes.IsEmpty) + { + return ImmutableArray.Empty; + } + + var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var mappingService = document.Project.Solution.Workspace.Services.GetRequiredService(); + using var _ = ArrayBuilder<(SymbolKey symbolKey, int lineNumber)>.GetInstance(out var builder); + + Project? project = null; + + foreach (var memberDeclarationNode in allDeclarationNodes) + { + var member = semanticModel.GetDeclaredSymbol(memberDeclarationNode, cancellationToken); + if (member == null || !CanHaveInheritanceTarget(member)) + { + continue; + } + + // Use mapping service to find correct solution & symbol. (e.g. metadata symbol) + var mappingResult = await mappingService.MapSymbolAsync(document, member, cancellationToken).ConfigureAwait(false); + if (mappingResult == null) + { + continue; + } + + // All the symbols here are declared in the same document, they should belong to the same project. + // So here it is enough to get the project once. + project ??= mappingResult.Project; + builder.Add((mappingResult.Symbol.GetSymbolKey(cancellationToken), sourceText.Lines.GetLineFromPosition(GetDeclarationToken(memberDeclarationNode).SpanStart).LineNumber)); + } + + var symbolKeyAndLineNumbers = builder.ToImmutable(); + if (symbolKeyAndLineNumbers.IsEmpty || project == null) + { + return ImmutableArray.Empty; + } + + var solution = project.Solution; + var serializedInheritanceMarginItems = await GetInheritanceMemberItemAsync( + solution, + project.Id, + symbolKeyAndLineNumbers, + cancellationToken).ConfigureAwait(false); + return await serializedInheritanceMarginItems.SelectAsArrayAsync( + (serializedItem, _) => InheritanceMarginItem.ConvertAsync(solution, serializedItem, cancellationToken), cancellationToken).ConfigureAwait(false); + } + + private static bool CanHaveInheritanceTarget(ISymbol symbol) + { + if (symbol.IsStatic) + { + return false; + } + + if (symbol is INamedTypeSymbol or IEventSymbol or IPropertySymbol || + symbol.IsOrdinaryMethod()) + { + return true; + } + + return false; + } + } +} diff --git a/src/EditorFeatures/Core/InheritanceMargin/IInheritanceMarginService.cs b/src/EditorFeatures/Core/InheritanceMargin/IInheritanceMarginService.cs new file mode 100644 index 0000000000000..123b71ecf7aad --- /dev/null +++ b/src/EditorFeatures/Core/InheritanceMargin/IInheritanceMarginService.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.InheritanceMargin +{ + internal interface IInheritanceMarginService : ILanguageService + { + /// + /// Get the lines need to be have a margin and the member's information on that line. + /// + ValueTask> GetInheritanceMemberItemsAsync( + Document document, + TextSpan spanToSearch, + CancellationToken cancellationToken); + } +} diff --git a/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginItem.cs b/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginItem.cs new file mode 100644 index 0000000000000..a2eda3d3ff472 --- /dev/null +++ b/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginItem.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.InheritanceMargin +{ + internal readonly struct InheritanceMarginItem + { + /// + /// Line number used to show the margin for the member. + /// + public readonly int LineNumber; + + /// + /// Display texts for this member. + /// + public readonly ImmutableArray DisplayTexts; + + /// + /// Member's glyph. + /// + public readonly Glyph Glyph; + + /// + /// An array of the implementing/implemented/overriding/overridden targets for this member. + /// + public readonly ImmutableArray TargetItems; + + public InheritanceMarginItem( + int lineNumber, + ImmutableArray displayTexts, + Glyph glyph, + ImmutableArray targetItems) + { + LineNumber = lineNumber; + DisplayTexts = displayTexts; + Glyph = glyph; + TargetItems = targetItems; + } + + public static async ValueTask ConvertAsync( + Solution solution, + SerializableInheritanceMarginItem serializableItem, + CancellationToken cancellationToken) + { + var targetItems = await serializableItem.TargetItems.SelectAsArrayAsync( + (item, _) => InheritanceTargetItem.ConvertAsync(solution, item, cancellationToken), cancellationToken).ConfigureAwait(false); + return new InheritanceMarginItem(serializableItem.LineNumber, serializableItem.DisplayTexts, serializableItem.Glyph, targetItems); + } + } +} diff --git a/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs b/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs new file mode 100644 index 0000000000000..02279bca1ab74 --- /dev/null +++ b/src/EditorFeatures/Core/InheritanceMargin/InheritanceMarginServiceHelpers.cs @@ -0,0 +1,406 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindSymbols.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.InheritanceMargin +{ + internal static class InheritanceMarginServiceHelper + { + private static readonly SymbolDisplayFormat s_displayFormat = new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + memberOptions: + SymbolDisplayMemberOptions.IncludeContainingType | + SymbolDisplayMemberOptions.IncludeExplicitInterface, + propertyStyle: SymbolDisplayPropertyStyle.NameOnly, + miscellaneousOptions: + SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | + SymbolDisplayMiscellaneousOptions.UseSpecialTypes | + SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName | + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + public static async ValueTask> GetInheritanceMemberItemAsync( + Solution solution, + ProjectId projectId, + ImmutableArray<(SymbolKey symbolKey, int lineNumber)> symbolKeyAndLineNumbers, + CancellationToken cancellationToken) + { + var remoteClient = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); + if (remoteClient != null) + { + // Here the line number is also passed to the remote process. It is done in this way because + // when a set of symbols is passed to remote process, those without inheritance targets would not be returned. + // To match the returned inheritance targets to the line number, we need set an 'Id' when calling the remote process, + // however, given the line number is just an int, setting up an int 'Id' for an int is quite useless, so just passed it to the remote process. + var result = await remoteClient.TryInvokeAsync>( + solution, + (remoteInheritanceMarginService, solutionInfo, cancellationToken) => + remoteInheritanceMarginService.GetInheritanceMarginItemsAsync(solutionInfo, projectId, symbolKeyAndLineNumbers, cancellationToken), + cancellationToken).ConfigureAwait(false); + + if (!result.HasValue) + { + return ImmutableArray.Empty; + } + + return result.Value; + } + else + { + return await GetInheritanceMemberItemInProcAsync(solution, projectId, symbolKeyAndLineNumbers, cancellationToken).ConfigureAwait(false); + } + } + + private static async ValueTask> GetInheritanceMemberItemInProcAsync( + Solution solution, + ProjectId projectId, + ImmutableArray<(SymbolKey symbolKey, int lineNumber)> symbolKeyAndLineNumbers, + CancellationToken cancellationToken) + { + var project = solution.GetRequiredProject(projectId); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var (symbolKey, lineNumber) in symbolKeyAndLineNumbers) + { + var symbol = symbolKey.Resolve(compilation, cancellationToken: cancellationToken).Symbol; + if (symbol is INamedTypeSymbol namedTypeSymbol) + { + await AddInheritanceMemberItemsForNamedTypeAsync(solution, namedTypeSymbol, lineNumber, builder, cancellationToken).ConfigureAwait(false); + } + + if (symbol is IEventSymbol or IPropertySymbol or IMethodSymbol) + { + await AddInheritanceMemberItemsForTypeMembersAsync(solution, symbol, lineNumber, builder, cancellationToken).ConfigureAwait(false); + } + } + + return builder.ToImmutable(); + } + + private static async ValueTask AddInheritanceMemberItemsForNamedTypeAsync( + Solution solution, + INamedTypeSymbol memberSymbol, + int lineNumber, + ArrayBuilder builder, + CancellationToken cancellationToken) + { + // Get all base types. + var allBaseSymbols = BaseTypeFinder.FindBaseTypesAndInterfaces(memberSymbol); + + // Filter out + // 1. System.Object. (otherwise margin would be shown for all classes) + // 2. System.ValueType. (otherwise margin would be shown for all structs) + // 3. System.Enum. (otherwise margin would be shown for all enum) + // 4. Error type. + // For example, if user has code like this, + // class Bar : ISomethingIsNotDone { } + // The interface has not been declared yet, so don't show this error type to user. + var baseSymbols = allBaseSymbols + .WhereAsArray(symbol => !symbol.IsErrorType() && symbol.SpecialType is not (SpecialType.System_Object or SpecialType.System_ValueType or SpecialType.System_Enum)); + + // Get all derived types + var allDerivedSymbols = await GetDerivedTypesAndImplementationsAsync( + solution, + memberSymbol, + cancellationToken).ConfigureAwait(false); + + // Ensure the user won't be able to see symbol outside the solution for derived symbols. + // For example, if user is viewing 'IEnumerable interface' from metadata, we don't want to tell + // the user all the derived types under System.Collections + var derivedSymbols = allDerivedSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); + + if (baseSymbols.Any() || derivedSymbols.Any()) + { + var item = await CreateInheritanceMemberItemAsync( + solution, + memberSymbol, + lineNumber, + baseSymbols: baseSymbols.CastArray(), + derivedTypesSymbols: derivedSymbols.CastArray(), + cancellationToken).ConfigureAwait(false); + builder.AddIfNotNull(item); + } + } + + private static async ValueTask AddInheritanceMemberItemsForTypeMembersAsync( + Solution solution, + ISymbol memberSymbol, + int lineNumber, + ArrayBuilder builder, + CancellationToken cancellationToken) + { + // For a given member symbol (method, property and event), its base and derived symbols are classified into 4 cases. + // The mapping between images + // Implemented : I↓ + // Implementing : I↑ + // Overridden: O↓ + // Overriding: O↑ + + // Go down the inheritance chain to find all the overrides targets. + var allOverriddenSymbols = await SymbolFinder.FindOverridesArrayAsync(memberSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + + // Go up the inheritance chain to find all overriding targets + var overridingSymbols = GetOverridingSymbols(memberSymbol); + + // Go up the inheritance chain to find all the implemented targets. + var implementingSymbols = GetImplementingSymbolsForTypeMember(memberSymbol, overridingSymbols); + + // Go down the inheritance chain to find all the implementing targets. + var allImplementedSymbols = await GetImplementedSymbolsForTypeMemberAsync(solution, memberSymbol, cancellationToken).ConfigureAwait(false); + + // For all overriden & implemented symbols, make sure it is in source. + // For example, if the user is viewing System.Threading.SynchronizationContext from metadata, + // then don't show the derived overriden & implemented method in the default implementation for System.Threading.SynchronizationContext in metadata + var overriddenSymbols = allOverriddenSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); + var implementedSymbols = allImplementedSymbols.WhereAsArray(symbol => symbol.Locations.Any(l => l.IsInSource)); + + if (overriddenSymbols.Any() || overridingSymbols.Any() || implementingSymbols.Any() || implementedSymbols.Any()) + { + var item = await CreateInheritanceMemberInfoForMemberAsync( + solution, + memberSymbol, + lineNumber, + implementingMembers: implementingSymbols, + implementedMembers: implementedSymbols, + overridenMembers: overriddenSymbols, + overridingMembers: overridingSymbols, + cancellationToken).ConfigureAwait(false); + + builder.AddIfNotNull(item); + } + } + + private static async ValueTask CreateInheritanceMemberItemAsync( + Solution solution, + INamedTypeSymbol memberSymbol, + int lineNumber, + ImmutableArray baseSymbols, + ImmutableArray derivedTypesSymbols, + CancellationToken cancellationToken) + { + var baseSymbolItems = await baseSymbols + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implementing, cancellationToken), cancellationToken) + .ConfigureAwait(false); + + var derivedTypeItems = await derivedTypesSymbols + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implemented, cancellationToken), cancellationToken) + .ConfigureAwait(false); + + return new SerializableInheritanceMarginItem( + lineNumber, + FindUsagesHelpers.GetDisplayParts(memberSymbol), + memberSymbol.GetGlyph(), + baseSymbolItems.Concat(derivedTypeItems)); + } + + private static async ValueTask CreateInheritanceItemAsync( + Solution solution, + ISymbol targetSymbol, + InheritanceRelationship inheritanceRelationship, + CancellationToken cancellationToken) + { + var symbolInSource = await SymbolFinder.FindSourceDefinitionAsync(targetSymbol, solution, cancellationToken).ConfigureAwait(false); + targetSymbol = symbolInSource ?? targetSymbol; + + // Right now the targets are not shown in a classified way. + var definition = await targetSymbol.ToNonClassifiedDefinitionItemAsync( + solution, + includeHiddenLocations: false, + cancellationToken: cancellationToken).ConfigureAwait(false); + + var displayName = targetSymbol.ToDisplayString(s_displayFormat); + + return new SerializableInheritanceTargetItem( + inheritanceRelationship, + // Id is used by FAR service for caching, it is not used in inheritance margin + SerializableDefinitionItem.Dehydrate(id: 0, definition), + targetSymbol.GetGlyph(), + displayName); + } + + private static async ValueTask CreateInheritanceMemberInfoForMemberAsync( + Solution solution, + ISymbol memberSymbol, + int lineNumber, + ImmutableArray implementingMembers, + ImmutableArray implementedMembers, + ImmutableArray overridenMembers, + ImmutableArray overridingMembers, + CancellationToken cancellationToken) + { + var implementingMemberItems = await implementingMembers + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implementing, cancellationToken), cancellationToken).ConfigureAwait(false); + + var implementedMemberItems = await implementedMembers + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Implemented, cancellationToken), cancellationToken).ConfigureAwait(false); + + var overridenMemberItems = await overridenMembers + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Overridden, cancellationToken), cancellationToken).ConfigureAwait(false); + + var overridingMemberItems = await overridingMembers + .SelectAsArray(symbol => symbol.OriginalDefinition) + .Distinct() + .SelectAsArrayAsync((symbol, _) => CreateInheritanceItemAsync(solution, symbol, InheritanceRelationship.Overriding, cancellationToken), cancellationToken).ConfigureAwait(false); + + return new SerializableInheritanceMarginItem( + lineNumber, + FindUsagesHelpers.GetDisplayParts(memberSymbol), + memberSymbol.GetGlyph(), + implementingMemberItems.Concat(implementedMemberItems) + .Concat(overridenMemberItems) + .Concat(overridingMemberItems)); + } + + private static ImmutableArray GetImplementingSymbolsForTypeMember( + ISymbol memberSymbol, + ImmutableArray overridingSymbols) + { + if (memberSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol) + { + using var _ = ArrayBuilder.GetInstance(out var builder); + + // 1. Get the direct implementing symbols in interfaces. + var directImplementingSymbols = memberSymbol.ExplicitOrImplicitInterfaceImplementations(); + builder.AddRange(directImplementingSymbols); + + // 2. Also add the direct implementing symbols for the overriding symbols. + // For example: + // interface IBar { void Foo(); } + // class Bar : IBar { public override void Foo() { } } + // class Bar2 : Bar { public override void Foo() { } } + // For 'Bar2.Foo()', we need to find 'IBar.Foo()' + foreach (var symbol in overridingSymbols) + { + builder.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); + } + + return builder.ToImmutableArray(); + } + + return ImmutableArray.Empty; + } + + /// + /// For the , get all the implemented symbols. + /// Table for the mapping between images and inheritanceRelationship + /// Implemented : I↓ + /// Implementing : I↑ + /// Overridden: O↓ + /// Overriding: O↑ + /// + private static async Task> GetImplementedSymbolsForTypeMemberAsync( + Solution solution, + ISymbol memberSymbol, + CancellationToken cancellationToken) + { + if (memberSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol + && memberSymbol.ContainingSymbol.IsInterfaceType()) + { + using var _ = ArrayBuilder.GetInstance(out var builder); + // 1. Find all direct implementations for this member + var implementationSymbols = await SymbolFinder.FindMemberImplementationsArrayAsync( + memberSymbol, + solution, + cancellationToken: cancellationToken).ConfigureAwait(false); + builder.AddRange(implementationSymbols); + + // 2. Continue searching the overriden symbols. For example: + // interface IBar { void Foo(); } + // class Bar : IBar { public virtual void Foo() { } } + // class Bar2 : IBar { public override void Foo() { } } + // For 'IBar.Foo()', we need to find 'Bar2.Foo()' + foreach (var implementationSymbol in implementationSymbols) + { + builder.AddRange(await SymbolFinder.FindOverridesArrayAsync(implementationSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false)); + } + + return builder.ToImmutableArray(); + } + + return ImmutableArray.Empty; + } + + /// + /// Get members overriding the + /// Table for the mapping between images and inheritanceRelationship + /// Implemented : I↓ + /// Implementing : I↑ + /// Overridden: O↓ + /// Overriding: O↑ + /// + private static ImmutableArray GetOverridingSymbols(ISymbol memberSymbol) + { + if (memberSymbol is INamedTypeSymbol) + { + return ImmutableArray.Empty; + } + else + { + using var _ = ArrayBuilder.GetInstance(out var builder); + for (var overridenMember = memberSymbol.GetOverriddenMember(); + overridenMember != null; + overridenMember = overridenMember.GetOverriddenMember()) + { + builder.Add(overridenMember.OriginalDefinition); + } + + return builder.ToImmutableArray(); + } + } + + /// + /// Get the derived interfaces and derived classes for . + /// + private static async Task> GetDerivedTypesAndImplementationsAsync( + Solution solution, + INamedTypeSymbol typeSymbol, + CancellationToken cancellationToken) + { + if (typeSymbol.IsInterfaceType()) + { + var allDerivedInterfaces = await SymbolFinder.FindDerivedInterfacesArrayAsync( + typeSymbol, + solution, + transitive: true, + cancellationToken: cancellationToken).ConfigureAwait(false); + var allImplementations = await SymbolFinder.FindImplementationsArrayAsync( + typeSymbol, + solution, + transitive: true, + cancellationToken: cancellationToken).ConfigureAwait(false); + return allDerivedInterfaces.Concat(allImplementations); + } + else + { + return await SymbolFinder.FindDerivedClassesArrayAsync( + typeSymbol, + solution, + transitive: true, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/EditorFeatures/Core/InheritanceMargin/InheritanceTargetItem.cs b/src/EditorFeatures/Core/InheritanceMargin/InheritanceTargetItem.cs new file mode 100644 index 0000000000000..4cc6859a70e61 --- /dev/null +++ b/src/EditorFeatures/Core/InheritanceMargin/InheritanceTargetItem.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; + +namespace Microsoft.CodeAnalysis.InheritanceMargin +{ + /// + /// Information used to decided the margin image and responsible for performing navigations + /// + internal readonly struct InheritanceTargetItem + { + /// + /// Indicate the inheritance relationship between the target and member. + /// + public readonly InheritanceRelationship RelationToMember; + + /// + /// DefinitionItem used to display the additional information and performs navigation. + /// + public readonly DefinitionItem DefinitionItem; + + /// + /// The glyph for this target. + /// + public readonly Glyph Glyph; + + /// + /// The display name used in margin. + /// + public readonly string DisplayName; + + public InheritanceTargetItem( + InheritanceRelationship relationToMember, + DefinitionItem definitionItem, + Glyph glyph, + string displayName) + { + RelationToMember = relationToMember; + DefinitionItem = definitionItem; + Glyph = glyph; + DisplayName = displayName; + } + + public static async ValueTask ConvertAsync( + Solution solution, + SerializableInheritanceTargetItem serializableItem, + CancellationToken cancellationToken) + { + var definitionItem = await serializableItem.DefinitionItem.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + return new InheritanceTargetItem( + serializableItem.RelationToMember, + definitionItem, + serializableItem.Glyph, + serializableItem.DisplayName); + } + } +} diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index 2d458d5c2e27f..f31048d1adae7 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -49,29 +49,30 @@ internal class InlineHintsDataTaggerProvider : AsynchronousViewTaggerProvider TaggerDelay.Short; + protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) { return TaggerEventSources.Compose( - TaggerEventSources.OnViewSpanChanged(ThreadingContext, textViewOpt, textChangeDelay: TaggerDelay.Short, scrollChangeDelay: TaggerDelay.NearImmediate), - TaggerEventSources.OnWorkspaceChanged(subjectBuffer, TaggerDelay.NearImmediate, _listener), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.DisplayAllOverride, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.EnabledForParameters, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForLiteralParameters, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForObjectCreationParameters, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForOtherParameters, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.SuppressForParametersThatMatchMethodIntent, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.SuppressForParametersThatDifferOnlyBySuffix, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.EnabledForTypes, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForImplicitVariableTypes, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForLambdaParameterTypes, TaggerDelay.NearImmediate), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForImplicitObjectCreation, TaggerDelay.NearImmediate)); + TaggerEventSources.OnViewSpanChanged(ThreadingContext, textViewOpt), + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, _listener), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.DisplayAllOverride), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.EnabledForParameters), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForLiteralParameters), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForObjectCreationParameters), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForOtherParameters), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.SuppressForParametersThatMatchMethodIntent), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.SuppressForParametersThatDifferOnlyBySuffix), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.EnabledForTypes), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForImplicitVariableTypes), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForLambdaParameterTypes), + TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForImplicitObjectCreation)); } protected override IEnumerable GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer) diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index 57a5ebbdd94bb..d75f775bae394 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -30,9 +30,9 @@ - - - + + diff --git a/src/EditorFeatures/Core/PublicAPI.Shipped.txt b/src/EditorFeatures/Core/PublicAPI.Shipped.txt index 4f57d92fb997d..8b581ef31f58f 100644 --- a/src/EditorFeatures/Core/PublicAPI.Shipped.txt +++ b/src/EditorFeatures/Core/PublicAPI.Shipped.txt @@ -1,8 +1,2 @@ Microsoft.CodeAnalysis.Editor.Peek.IPeekableItemFactory Microsoft.CodeAnalysis.Editor.Peek.IPeekableItemFactory.GetPeekableItemsAsync(Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.Project project, Microsoft.VisualStudio.Language.Intellisense.IPeekResultFactory peekResultFactory, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -Microsoft.CodeAnalysis.Editor.Tags.ExportImageMonikerServiceAttribute -Microsoft.CodeAnalysis.Editor.Tags.ExportImageMonikerServiceAttribute.ExportImageMonikerServiceAttribute() -> void -Microsoft.CodeAnalysis.Editor.Tags.ExportImageMonikerServiceAttribute.Name.get -> string -Microsoft.CodeAnalysis.Editor.Tags.ExportImageMonikerServiceAttribute.Name.set -> void -Microsoft.CodeAnalysis.Editor.Tags.IImageMonikerService -Microsoft.CodeAnalysis.Editor.Tags.IImageMonikerService.TryGetImageMoniker(System.Collections.Immutable.ImmutableArray tags, out Microsoft.VisualStudio.Imaging.Interop.ImageMoniker imageMoniker) -> bool diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs index 8823fd2bd9403..3313cbbff9531 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs @@ -18,7 +18,6 @@ using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -49,20 +48,21 @@ internal partial class ReferenceHighlightingViewTaggerProvider : AsynchronousVie [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public ReferenceHighlightingViewTaggerProvider( IThreadingContext threadingContext, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.ReferenceHighlighting), notificationService) + : base(threadingContext, listenerProvider.GetListener(FeatureAttribute.ReferenceHighlighting)) { } + protected override TaggerDelay EventChangeDelay => TaggerDelay.Medium; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { // Note: we don't listen for OnTextChanged. Text changes to this buffer will get // reported by OnSemanticChanged. return TaggerEventSources.Compose( - TaggerEventSources.OnCaretPositionChanged(textView, textView.TextBuffer, TaggerDelay.Short), - TaggerEventSources.OnWorkspaceChanged(subjectBuffer, TaggerDelay.OnIdle, AsyncListener), - TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer, TaggerDelay.Short)); + TaggerEventSources.OnCaretPositionChanged(textView, textView.TextBuffer), + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, AsyncListener), + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer)); } protected override SnapshotPoint? GetCaretPoint(ITextView textViewOpt, ITextBuffer subjectBuffer) @@ -158,7 +158,7 @@ internal static async Task ProduceTagsAsync( { foreach (var documentHighlights in documentHighlightsList) { - await AddTagSpansAsync(context, documentHighlights).ConfigureAwait(false); + AddTagSpans(context, documentHighlights); } } } @@ -166,15 +166,14 @@ internal static async Task ProduceTagsAsync( } } - private static async Task AddTagSpansAsync( + private static void AddTagSpans( TaggerContext context, DocumentHighlights documentHighlights) { var cancellationToken = context.CancellationToken; var document = documentHighlights.Document; - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var textSnapshot = text.FindCorrespondingEditorTextSnapshot(); + var textSnapshot = context.SpansToTag.FirstOrDefault(s => s.Document == document).SnapshotSpan.Snapshot; if (textSnapshot == null) { // There is no longer an editor snapshot for this document, so we can't care about the diff --git a/src/EditorFeatures/Core/Shared/Extensions/GlyphExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/GlyphExtensions.cs index 0cc3ea9c74517..cbac0acd01236 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/GlyphExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/GlyphExtensions.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Tags; using Microsoft.VisualStudio.Core.Imaging; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Text.Adornments; @@ -26,6 +24,8 @@ public static ImageId GetImageId(this Glyph glyph) // VS for mac cannot refer to ImageMoniker // so we need to expose ImageId instead of ImageMoniker here // and expose ImageMoniker in the EditorFeatures.wpf.dll + // The use of constants here is okay because the compiler inlines their values, so no runtime reference is needed. + // There are tests in src\EditorFeatures\Test\AssemblyReferenceTests.cs to ensure we don't regress that. switch (glyph) { case Glyph.None: diff --git a/src/EditorFeatures/Core/Shared/Extensions/IWaitIndicatorExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/IWaitIndicatorExtensions.cs deleted file mode 100644 index a3641ba1ece77..0000000000000 --- a/src/EditorFeatures/Core/Shared/Extensions/IWaitIndicatorExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.CodeAnalysis.Editor.Host; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions -{ - internal static class IWaitIndicatorExtensions - { - public static WaitIndicatorResult Wait(this IWaitIndicator waitIndicator, string titleAndMessage, bool allowCancel, Action action) - => waitIndicator.Wait(titleAndMessage, titleAndMessage, allowCancel, action); - - public static WaitIndicatorResult Wait(this IWaitIndicator waitIndicator, string titleAndMessage, bool allowCancel, bool showProgress, Action action) - => waitIndicator.Wait(titleAndMessage, titleAndMessage, allowCancel, showProgress, action); - } -} diff --git a/src/EditorFeatures/Core/Shared/Extensions/InferredIndentationExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/InferredIndentationExtensions.cs deleted file mode 100644 index c8885b85f1bda..0000000000000 --- a/src/EditorFeatures/Core/Shared/Extensions/InferredIndentationExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions -{ - internal static class InferredIndentationOptions - { - public static async Task GetDocumentOptionsWithInferredIndentationAsync( - this Document document, - bool explicitFormat, - IIndentationManagerService indentationManagerService, - CancellationToken cancellationToken) - { - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var snapshot = text.FindCorrespondingEditorTextSnapshot(); - - if (snapshot != null) - { - indentationManagerService.GetIndentation(snapshot.TextBuffer, explicitFormat, out var convertTabsToSpaces, out var tabSize, out var indentSize); - - options = options.WithChangedOption(FormattingOptions.UseTabs, !convertTabsToSpaces) - .WithChangedOption(FormattingOptions.IndentationSize, indentSize) - .WithChangedOption(FormattingOptions.TabSize, tabSize); - } - - return options; - } - } -} diff --git a/src/EditorFeatures/Core/Shared/Extensions/TelemetryExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/TelemetryExtensions.cs index 2f722ddd1d455..5c5c3aa018bf4 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/TelemetryExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/TelemetryExtensions.cs @@ -19,26 +19,15 @@ public static Guid GetTelemetryId(this Type type, short scope = 0) // AssemblyQualifiedName will change across version numbers, FullName won't - // GetHashCode on string is not stable. From documentation: - // The hash code itself is not guaranteed to be stable. - // Hash codes for identical strings can differ across .NET implementations, across .NET versions, - // and across .NET platforms (such as 32-bit and 64-bit) for a single version of .NET. In some cases, - // they can even differ by application domain. - // This implies that two subsequent runs of the same program may return different hash codes. - // - // As such, we keep the original prefix that was being used for legacy purposes, but - // use a stable hashing algorithm (FNV) that doesn't depend on platform - // or .NET implementation. We can map the prefix across legacy versions, but - // as we support more platforms and variations of builds the suffix will be constant - // and usable - var prefix = type.FullName.GetHashCode(); + // Use a stable hashing algorithm (FNV) that doesn't depend on platform + // or .NET implementation. var suffix = Roslyn.Utilities.Hash.GetFNVHashCode(type.FullName); - // Suffix is the remaining 8 bytes, and the hash code only makes up 4. Pad + // Suffix is the remaining 8 bytes, and the hash code only makes up 4. Pad // the remainder with an empty byte array var suffixBytes = BitConverter.GetBytes(suffix).Concat(new byte[4]).ToArray(); - return new Guid(prefix, scope, 0, suffixBytes); + return new Guid(0, scope, 0, suffixBytes); } public static Type GetTypeForTelemetry(this Type type) diff --git a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs index a9b67d3afb5ce..beaeec8532aae 100644 --- a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs +++ b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.InheritanceMargin; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Options.Providers; @@ -32,23 +33,12 @@ internal static class FeatureOnOffOptions public static readonly PerLanguageOption2 ReferenceHighlighting = new(nameof(FeatureOnOffOptions), nameof(ReferenceHighlighting), defaultValue: true, storageLocations: new RoamingProfileStorageLocation(language => language == LanguageNames.VisualBasic ? "TextEditor.%LANGUAGE%.Specific.EnableHighlightReferences" : "TextEditor.%LANGUAGE%.Specific.Reference Highlighting")); - public static readonly PerLanguageOption2 FormatOnPaste = new(nameof(FeatureOnOffOptions), nameof(FormatOnPaste), defaultValue: true, - storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.FormatOnPaste")); - public static readonly PerLanguageOption2 AutoInsertBlockCommentStartString = new(nameof(FeatureOnOffOptions), nameof(AutoInsertBlockCommentStartString), defaultValue: true, storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.Auto Insert Block Comment Start String")); public static readonly PerLanguageOption2 PrettyListing = new(nameof(FeatureOnOffOptions), nameof(PrettyListing), defaultValue: true, storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PrettyListing")); - public static readonly PerLanguageOption2 AutoFormattingOnTyping = new( - nameof(FeatureOnOffOptions), nameof(AutoFormattingOnTyping), defaultValue: true, - storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Typing")); - - public static readonly PerLanguageOption2 AutoFormattingOnSemicolon = new( - nameof(FeatureOnOffOptions), nameof(AutoFormattingOnSemicolon), defaultValue: true, - storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Semicolon")); - public static readonly PerLanguageOption2 RenameTrackingPreview = new(nameof(FeatureOnOffOptions), nameof(RenameTrackingPreview), defaultValue: true, storageLocations: new RoamingProfileStorageLocation(language => language == LanguageNames.VisualBasic ? "TextEditor.%LANGUAGE%.Specific.RenameTrackingPreview" : "TextEditor.%LANGUAGE%.Specific.Rename Tracking Preview")); @@ -88,6 +78,12 @@ internal static class FeatureOnOffOptions nameof(FeatureOnOffOptions), nameof(OfferRemoveUnusedReferences), defaultValue: true, storageLocations: new RoamingProfileStorageLocation($"TextEditor.{nameof(OfferRemoveUnusedReferences)}")); + public static readonly PerLanguageOption2 ShowInheritanceMargin = + new(nameof(FeatureOnOffOptions), + nameof(ShowInheritanceMargin), + defaultValue: false, + new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.ShowInheritanceMargin")); + public static readonly Option2 AutomaticallyCompleteStatementOnSemicolon = new( nameof(FeatureOnOffOptions), nameof(AutomaticallyCompleteStatementOnSemicolon), defaultValue: true, storageLocations: new RoamingProfileStorageLocation($"TextEditor.{nameof(AutomaticallyCompleteStatementOnSemicolon)}")); @@ -110,11 +106,8 @@ public FeatureOnOffOptionsProvider() FeatureOnOffOptions.Outlining, FeatureOnOffOptions.KeywordHighlighting, FeatureOnOffOptions.ReferenceHighlighting, - FeatureOnOffOptions.FormatOnPaste, FeatureOnOffOptions.AutoInsertBlockCommentStartString, FeatureOnOffOptions.PrettyListing, - FeatureOnOffOptions.AutoFormattingOnTyping, - FeatureOnOffOptions.AutoFormattingOnSemicolon, FeatureOnOffOptions.RenameTrackingPreview, FeatureOnOffOptions.RenameTracking, FeatureOnOffOptions.RefactoringVerification, @@ -123,6 +116,7 @@ public FeatureOnOffOptionsProvider() FeatureOnOffOptions.UseEnhancedColors, FeatureOnOffOptions.AddImportsOnPaste, FeatureOnOffOptions.OfferRemoveUnusedReferences, + FeatureOnOffOptions.ShowInheritanceMargin, FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon); } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs index 756c399850226..526e50d568d43 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs @@ -9,25 +9,16 @@ namespace Microsoft.CodeAnalysis.Editor.Shared.Tagging { internal abstract class AbstractTaggerEventSource : ITaggerEventSource { - private readonly TaggerDelay _delay; - - protected AbstractTaggerEventSource(TaggerDelay delay) - => _delay = delay; + protected AbstractTaggerEventSource() + { + } public abstract void Connect(); public abstract void Disconnect(); public event EventHandler? Changed; - public event EventHandler? UIUpdatesPaused; - public event EventHandler? UIUpdatesResumed; protected virtual void RaiseChanged() - => this.Changed?.Invoke(this, new TaggerEventArgs(_delay)); - - protected virtual void RaiseUIUpdatesPaused() - => this.UIUpdatesPaused?.Invoke(this, EventArgs.Empty); - - protected virtual void RaiseUIUpdatesResumed() - => this.UIUpdatesResumed?.Invoke(this, EventArgs.Empty); + => this.Changed?.Invoke(this, new TaggerEventArgs()); } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractWorkspaceTrackingTaggerEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractWorkspaceTrackingTaggerEventSource.cs index 6dd01e52b2bf6..996ae71f83ef0 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractWorkspaceTrackingTaggerEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractWorkspaceTrackingTaggerEventSource.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; @@ -20,7 +19,7 @@ internal abstract class AbstractWorkspaceTrackingTaggerEventSource : AbstractTag protected ITextBuffer SubjectBuffer { get; } protected Workspace? CurrentWorkspace { get; private set; } - protected AbstractWorkspaceTrackingTaggerEventSource(ITextBuffer subjectBuffer, TaggerDelay delay) : base(delay) + protected AbstractWorkspaceTrackingTaggerEventSource(ITextBuffer subjectBuffer) { this.SubjectBuffer = subjectBuffer; _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer()); diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs index 4e65651ba0120..8ace9eb3e26a0 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs @@ -29,19 +29,12 @@ internal static TimeSpan ComputeTimeDelay(this TaggerDelay behavior, ITextBuffer } internal static TimeSpan ComputeTimeDelay(this TaggerDelay behavior) - { - switch (behavior) + => behavior switch { - case TaggerDelay.NearImmediate: - return TimeSpan.FromMilliseconds(NearImmediateDelay); - case TaggerDelay.Short: - return TimeSpan.FromMilliseconds(ShortDelay); - case TaggerDelay.Medium: - return TimeSpan.FromMilliseconds(MediumDelay); - case TaggerDelay.OnIdle: - default: - return TimeSpan.FromMilliseconds(IdleDelay); - } - } + TaggerDelay.NearImmediate => TimeSpan.FromMilliseconds(NearImmediateDelay), + TaggerDelay.Short => TimeSpan.FromMilliseconds(ShortDelay), + TaggerDelay.Medium => TimeSpan.FromMilliseconds(MediumDelay), + _ => TimeSpan.FromMilliseconds(IdleDelay), + }; } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CaretPositionChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CaretPositionChangedEventSource.cs index d4923a5053bf3..f97921bbf63a0 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CaretPositionChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CaretPositionChangedEventSource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; @@ -15,8 +14,7 @@ private class CaretPositionChangedEventSource : AbstractTaggerEventSource { private readonly ITextView _textView; - public CaretPositionChangedEventSource(ITextView textView, ITextBuffer subjectBuffer, TaggerDelay delay) - : base(delay) + public CaretPositionChangedEventSource(ITextView textView, ITextBuffer subjectBuffer) { Contract.ThrowIfNull(textView); Contract.ThrowIfNull(subjectBuffer); diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs index 1de988ab25d25..8bbba9286191a 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs @@ -29,41 +29,8 @@ public void Disconnect() public event EventHandler Changed { - add - { - _providers.Do(p => p.Changed += value); - } - - remove - { - _providers.Do(p => p.Changed -= value); - } - } - - public event EventHandler UIUpdatesPaused - { - add - { - _providers.Do(p => p.UIUpdatesPaused += value); - } - - remove - { - _providers.Do(p => p.UIUpdatesPaused -= value); - } - } - - public event EventHandler UIUpdatesResumed - { - add - { - _providers.Do(p => p.UIUpdatesResumed += value); - } - - remove - { - _providers.Do(p => p.UIUpdatesResumed -= value); - } + add => _providers.Do(p => p.Changed += value); + remove => _providers.Do(p => p.Changed -= value); } } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DiagnosticsChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DiagnosticsChangedEventSource.cs index faa9fdce68586..20d2a9f9bd8e8 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DiagnosticsChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DiagnosticsChangedEventSource.cs @@ -16,8 +16,7 @@ private class DiagnosticsChangedEventSource : AbstractTaggerEventSource private readonly ITextBuffer _subjectBuffer; private readonly IDiagnosticService _service; - public DiagnosticsChangedEventSource(ITextBuffer subjectBuffer, IDiagnosticService service, TaggerDelay delay) - : base(delay) + public DiagnosticsChangedEventSource(ITextBuffer subjectBuffer, IDiagnosticService service) { _subjectBuffer = subjectBuffer; _service = service; diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs index bcc94a26784e0..7dfab13111ff8 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs @@ -12,8 +12,8 @@ internal partial class TaggerEventSources { private class DocumentActiveContextChangedEventSource : AbstractWorkspaceTrackingTaggerEventSource { - public DocumentActiveContextChangedEventSource(ITextBuffer subjectBuffer, TaggerDelay delay) - : base(subjectBuffer, delay) + public DocumentActiveContextChangedEventSource(ITextBuffer subjectBuffer) + : base(subjectBuffer) { } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.OptionChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.OptionChangedEventSource.cs index dadf5dd9fe70f..904eff0a63752 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.OptionChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.OptionChangedEventSource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Text; @@ -15,7 +14,7 @@ private class OptionChangedEventSource : AbstractWorkspaceTrackingTaggerEventSou private readonly IOption _option; private IOptionService? _optionService; - public OptionChangedEventSource(ITextBuffer subjectBuffer, IOption option, TaggerDelay delay) : base(subjectBuffer, delay) + public OptionChangedEventSource(ITextBuffer subjectBuffer, IOption option) : base(subjectBuffer) => _option = option; protected override void ConnectToWorkspace(Workspace workspace) diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs index 2485d39be4f00..6f63a0521334c 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs @@ -15,8 +15,8 @@ internal partial class TaggerEventSources { private class ParseOptionChangedEventSource : AbstractWorkspaceTrackingTaggerEventSource { - public ParseOptionChangedEventSource(ITextBuffer subjectBuffer, TaggerDelay delay) - : base(subjectBuffer, delay) + public ParseOptionChangedEventSource(ITextBuffer subjectBuffer) + : base(subjectBuffer) { } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ReadOnlyRegionsChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ReadOnlyRegionsChangedEventSource.cs index 90b2f936e685d..bbbd576c42a92 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ReadOnlyRegionsChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ReadOnlyRegionsChangedEventSource.cs @@ -14,11 +14,9 @@ private class ReadOnlyRegionsChangedEventSource : AbstractTaggerEventSource { private readonly ITextBuffer _subjectBuffer; - public ReadOnlyRegionsChangedEventSource(ITextBuffer subjectBuffer, TaggerDelay delay) - : base(delay) + public ReadOnlyRegionsChangedEventSource(ITextBuffer subjectBuffer) { Contract.ThrowIfNull(subjectBuffer); - _subjectBuffer = subjectBuffer; } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.SelectionChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.SelectionChangedEventSource.cs index f99942e2af879..fedc29c6c49e1 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.SelectionChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.SelectionChangedEventSource.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.CodeAnalysis.Editor.Shared.Tagging @@ -14,11 +13,8 @@ private class SelectionChangedEventSource : AbstractTaggerEventSource { private readonly ITextView _textView; - public SelectionChangedEventSource(ITextView textView, TaggerDelay delay) - : base(delay) - { - _textView = textView; - } + public SelectionChangedEventSource(ITextView textView) + => _textView = textView; public override void Connect() => _textView.Selection.SelectionChanged += OnSelectionChanged; diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs index 48480bc62332e..dc423555936a0 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs @@ -14,11 +14,9 @@ private class TextChangedEventSource : AbstractTaggerEventSource { private readonly ITextBuffer _subjectBuffer; - public TextChangedEventSource(ITextBuffer subjectBuffer, TaggerDelay delay) - : base(delay) + public TextChangedEventSource(ITextBuffer subjectBuffer) { Contract.ThrowIfNull(subjectBuffer); - _subjectBuffer = subjectBuffer; } @@ -31,9 +29,7 @@ public override void Disconnect() private void OnTextBufferChanged(object? sender, TextContentChangedEventArgs e) { if (e.Changes.Count == 0) - { return; - } this.RaiseChanged(); } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs index 9bc675e8b7cc6..94eae05f09340 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs @@ -18,24 +18,16 @@ private class ViewSpanChangedEventSource : ITaggerEventSource private readonly ForegroundThreadAffinitizedObject _foregroundObject; private readonly ITextView _textView; - private readonly TaggerDelay _textChangeDelay; - private readonly TaggerDelay _scrollChangeDelay; private Span? _span; - private ITextSnapshot? _viewTextSnapshot; - private ITextSnapshot? _viewVisualSnapshot; public event EventHandler? Changed; - public event EventHandler UIUpdatesPaused { add { } remove { } } - public event EventHandler UIUpdatesResumed { add { } remove { } } - public ViewSpanChangedEventSource(IThreadingContext threadingContext, ITextView textView, TaggerDelay textChangeDelay, TaggerDelay scrollChangeDelay) + public ViewSpanChangedEventSource(IThreadingContext threadingContext, ITextView textView) { Debug.Assert(textView != null); _foregroundObject = new ForegroundThreadAffinitizedObject(threadingContext); _textView = textView; - _textChangeDelay = textChangeDelay; - _scrollChangeDelay = scrollChangeDelay; } public void Connect() @@ -63,34 +55,18 @@ private void OnLayoutChanged(object? sender, TextViewLayoutChangedEventArgs e) // caused by the user collapsing an outlining region. var lastSpan = _span; - var lastViewTextSnapshot = _viewTextSnapshot; - var lastViewVisualSnapshot = _viewVisualSnapshot; - _span = _textView.TextViewLines.FormattedSpan.Span; - _viewTextSnapshot = _textView.TextSnapshot; - _viewVisualSnapshot = _textView.VisualSnapshot; if (_span != lastSpan) { // The span changed. This could have happened for a few different reasons. // If none of the view's text snapshots changed, then it was because of scrolling. - - if (_viewTextSnapshot == lastViewTextSnapshot && - _viewVisualSnapshot == lastViewVisualSnapshot) - { - // We scrolled. - RaiseChanged(_scrollChangeDelay); - } - else - { - // text changed in some way. - RaiseChanged(_textChangeDelay); - } + RaiseChanged(); } } - private void RaiseChanged(TaggerDelay delay) - => this.Changed?.Invoke(this, new TaggerEventArgs(delay)); + private void RaiseChanged() + => this.Changed?.Invoke(this, new TaggerEventArgs()); } } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs index aacce5d25d292..63dca1866fc32 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs @@ -5,7 +5,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; @@ -20,9 +19,8 @@ private class WorkspaceChangedEventSource : AbstractWorkspaceTrackingTaggerEvent public WorkspaceChangedEventSource( ITextBuffer subjectBuffer, - TaggerDelay delay, IAsynchronousOperationListener asyncListener) - : base(subjectBuffer, delay) + : base(subjectBuffer) { // That will ensure that even if we get a flurry of workspace events that we // only process a tag change once. diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceRegistrationChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceRegistrationChangedEventSource.cs index 9251ea980103a..0ece5cbfa5ca5 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceRegistrationChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceRegistrationChangedEventSource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor.Shared.Tagging @@ -11,8 +10,8 @@ internal partial class TaggerEventSources { private class WorkspaceRegistrationChangedEventSource : AbstractWorkspaceTrackingTaggerEventSource { - public WorkspaceRegistrationChangedEventSource(ITextBuffer subjectBuffer, TaggerDelay delay) - : base(subjectBuffer, delay) + public WorkspaceRegistrationChangedEventSource(ITextBuffer subjectBuffer) + : base(subjectBuffer) { } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.cs index 75dd55a7ab717..2f2651ac31e87 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.cs @@ -7,12 +7,9 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; -using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; @@ -29,69 +26,40 @@ public static ITaggerEventSource Compose( public static ITaggerEventSource Compose(IEnumerable eventSources) => new CompositionEventSource(eventSources.ToArray()); - public static ITaggerEventSource OnCaretPositionChanged(ITextView textView, ITextBuffer subjectBuffer, TaggerDelay delay) - => new CaretPositionChangedEventSource(textView, subjectBuffer, delay); + public static ITaggerEventSource OnCaretPositionChanged(ITextView textView, ITextBuffer subjectBuffer) + => new CaretPositionChangedEventSource(textView, subjectBuffer); - public static ITaggerEventSource OnTextChanged(ITextBuffer subjectBuffer, TaggerDelay delay) - { - Contract.ThrowIfNull(subjectBuffer); - - return new TextChangedEventSource(subjectBuffer, delay); - } + public static ITaggerEventSource OnTextChanged(ITextBuffer subjectBuffer) + => new TextChangedEventSource(subjectBuffer); /// /// Reports an event any time the workspace changes. /// - public static ITaggerEventSource OnWorkspaceChanged( - ITextBuffer subjectBuffer, TaggerDelay delay, IAsynchronousOperationListener listener) - { - return new WorkspaceChangedEventSource(subjectBuffer, delay, listener); - } - - public static ITaggerEventSource OnDocumentActiveContextChanged(ITextBuffer subjectBuffer, TaggerDelay delay) - => new DocumentActiveContextChangedEventSource(subjectBuffer, delay); + public static ITaggerEventSource OnWorkspaceChanged(ITextBuffer subjectBuffer, IAsynchronousOperationListener listener) + => new WorkspaceChangedEventSource(subjectBuffer, listener); - public static ITaggerEventSource OnSelectionChanged( - ITextView textView, - TaggerDelay delay) - { - return new SelectionChangedEventSource(textView, delay); - } + public static ITaggerEventSource OnDocumentActiveContextChanged(ITextBuffer subjectBuffer) + => new DocumentActiveContextChangedEventSource(subjectBuffer); - public static ITaggerEventSource OnReadOnlyRegionsChanged(ITextBuffer subjectBuffer, TaggerDelay delay) - { - Contract.ThrowIfNull(subjectBuffer); + public static ITaggerEventSource OnSelectionChanged(ITextView textView) + => new SelectionChangedEventSource(textView); - return new ReadOnlyRegionsChangedEventSource(subjectBuffer, delay); - } + public static ITaggerEventSource OnReadOnlyRegionsChanged(ITextBuffer subjectBuffer) + => new ReadOnlyRegionsChangedEventSource(subjectBuffer); - public static ITaggerEventSource OnOptionChanged( - ITextBuffer subjectBuffer, - IOption option, - TaggerDelay delay) - { - return new OptionChangedEventSource(subjectBuffer, option, delay); - } + public static ITaggerEventSource OnOptionChanged(ITextBuffer subjectBuffer, IOption option) + => new OptionChangedEventSource(subjectBuffer, option); - public static ITaggerEventSource OnDiagnosticsChanged( - ITextBuffer subjectBuffer, - IDiagnosticService service, - TaggerDelay delay) - { - return new DiagnosticsChangedEventSource(subjectBuffer, service, delay); - } + public static ITaggerEventSource OnDiagnosticsChanged(ITextBuffer subjectBuffer, IDiagnosticService service) + => new DiagnosticsChangedEventSource(subjectBuffer, service); - public static ITaggerEventSource OnParseOptionChanged( - ITextBuffer subjectBuffer, - TaggerDelay delay) - { - return new ParseOptionChangedEventSource(subjectBuffer, delay); - } + public static ITaggerEventSource OnParseOptionChanged(ITextBuffer subjectBuffer) + => new ParseOptionChangedEventSource(subjectBuffer); - public static ITaggerEventSource OnWorkspaceRegistrationChanged(ITextBuffer subjectBuffer, TaggerDelay delay) - => new WorkspaceRegistrationChangedEventSource(subjectBuffer, delay); + public static ITaggerEventSource OnWorkspaceRegistrationChanged(ITextBuffer subjectBuffer) + => new WorkspaceRegistrationChangedEventSource(subjectBuffer); - public static ITaggerEventSource OnViewSpanChanged(IThreadingContext threadingContext, ITextView textView, TaggerDelay textChangeDelay, TaggerDelay scrollChangeDelay) - => new ViewSpanChangedEventSource(threadingContext, textView, textChangeDelay, scrollChangeDelay); + public static ITaggerEventSource OnViewSpanChanged(IThreadingContext threadingContext, ITextView textView) + => new ViewSpanChangedEventSource(threadingContext, textView); } } diff --git a/src/EditorFeatures/Core/Shared/Threading/AsynchronousSerialWorkQueue.cs b/src/EditorFeatures/Core/Shared/Threading/AsynchronousSerialWorkQueue.cs deleted file mode 100644 index b2992fedfd0fa..0000000000000 --- a/src/EditorFeatures/Core/Shared/Threading/AsynchronousSerialWorkQueue.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Threading -{ - // A helper class primarily used for the AsynchronousTagger that can handle the job of - // scheduling work to be done on the UI thread and on a background thread. This class wraps the - // TPL and implements things in special ways to provide certain nice bits of functionality. - // Specifically: - // - // 1) Background actions are run serially. This allows you to enqueue a whole host of background - // work to do, without having to worry about those same background tasks running simultaneously - // and colliding with each other. - // - // 2) You can start a 'chain' of actions starting with an action that fires after a delay. After - // that point you can continue adding actions to that chain. You can then ask to wait until that - // chain of actions completes (very useful for testing purposes). This chain can also be - // cancelled very simply. - internal class AsynchronousSerialWorkQueue : ForegroundThreadAffinitizedObject - { - #region Fields that can be accessed from either thread - - // The task schedulers that we can use to schedule work on the UI thread. - private readonly IAsynchronousOperationListener _asyncListener; - - // Lock for serializing access to these objects. - private readonly object _gate = new(); - - // The current task we are executing on the background. Kept around so we can serialize - // background tasks by continually calling 'SafeContinueWith' on this task. - private Task _currentBackgroundTask; - - // The cancellation source for the current chain of work. - private CancellationTokenSource _cancellationTokenSource = new(); - - #endregion - - public AsynchronousSerialWorkQueue(IThreadingContext threadingContext, IAsynchronousOperationListener asyncListener) - : base(threadingContext, assertIsForeground: false) - { - Contract.ThrowIfNull(asyncListener); - _asyncListener = asyncListener; - - // Initialize so we don't have to check for null below. Force the background task to run - // on the threadpool. - _currentBackgroundTask = Task.CompletedTask; - } - - public CancellationToken CancellationToken => _cancellationTokenSource.Token; - - public void CancelCurrentWork() - => CancelCurrentWork(remainCancelled: false); - - public void CancelCurrentWork(bool remainCancelled) - { - lock (_gate) - { - remainCancelled |= _cancellationTokenSource.IsCancellationRequested; - _cancellationTokenSource.Cancel(); - if (!remainCancelled) - { - _cancellationTokenSource = new CancellationTokenSource(); - } - } - } - - public void EnqueueBackgroundWork(Action action, string name, CancellationToken cancellationToken) - => EnqueueBackgroundWork(action, name, afterDelay: 0, cancellationToken: cancellationToken); - - public void EnqueueBackgroundWork(Action action, string name, int afterDelay, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(action); - - var asyncToken = _asyncListener.BeginAsyncOperation(name); - - lock (_gate) - { - if (afterDelay == 0) - { - // Note that we serialized background tasks so that consumers of this type can issue - // multiple background tasks without having to worry about them running - // simultaneously. - _currentBackgroundTask = _currentBackgroundTask.SafeContinueWith( - _ => action(), - cancellationToken, - TaskContinuationOptions.None, - TaskScheduler.Default); - } - else - { - _currentBackgroundTask = _currentBackgroundTask.ContinueWithAfterDelay( - action, cancellationToken, afterDelay, TaskContinuationOptions.None, TaskScheduler.Default); - } - - _currentBackgroundTask.CompletesAsyncOperation(asyncToken); - } - } - - public void EnqueueBackgroundTask( - Func taskGeneratingFunctionAsync, - string name, - CancellationToken cancellationToken) - { - EnqueueBackgroundTask(taskGeneratingFunctionAsync, name, afterDelay: 0, cancellationToken: cancellationToken); - } - - public void EnqueueBackgroundTask( - Func taskGeneratingFunctionAsync, - string name, int afterDelay, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(taskGeneratingFunctionAsync); - - var asyncToken = _asyncListener.BeginAsyncOperation(name); - - lock (_gate) - { - if (afterDelay == 0) - { - // Note that we serialized background tasks so that consumers of this type can issue - // multiple background tasks without having to worry about them running - // simultaneously. - _currentBackgroundTask = _currentBackgroundTask.SafeContinueWithFromAsync( - _ => taskGeneratingFunctionAsync(cancellationToken), - cancellationToken, - TaskContinuationOptions.None, - TaskScheduler.Default); - } - else - { - _currentBackgroundTask = _currentBackgroundTask.ContinueWithAfterDelayFromAsync( - _ => taskGeneratingFunctionAsync(cancellationToken), - cancellationToken, - afterDelay, - TaskContinuationOptions.None, - TaskScheduler.Default); - } - - _currentBackgroundTask.CompletesAsyncOperation(asyncToken); - } - } - - /// - /// Wait until all queued background tasks have been completed. NOTE: This will NOT pump, - /// and it won't wait for any timer foreground tasks to actually enqueue their respective - /// background tasks - it just waits for the already enqueued background tasks to finish. - /// - public void WaitForPendingBackgroundWork() - { - AssertIsForeground(); - - _currentBackgroundTask.Wait(); - } - - internal TestAccessor GetTestAccessor() - { - return new TestAccessor(this); - } - - internal readonly struct TestAccessor - { - private readonly AsynchronousSerialWorkQueue _asynchronousSerialWorkQueue; - - internal TestAccessor(AsynchronousSerialWorkQueue asynchronousSerialWorkQueue) - { - _asynchronousSerialWorkQueue = asynchronousSerialWorkQueue; - } - - /// - /// Wait until all tasks have been completed. NOTE that this will do a pumping wait if - /// called on the UI thread. Also, it isn't guaranteed to be stable in the case of tasks - /// enqueuing other tasks in arbitrary orders, though it does support our common pattern of - /// "timer task->background task->foreground task with results" - /// - /// Use this method very judiciously. Most of the time, we should be able to just use - /// IAsynchronousOperationListener for tests. - /// - public void WaitUntilCompletion() - { - _asynchronousSerialWorkQueue.AssertIsForeground(); - - _asynchronousSerialWorkQueue.WaitForPendingBackgroundWork(); - } - } - } -} diff --git a/src/EditorFeatures/Core/Shared/Utilities/ProgressTrackerAdapter.cs b/src/EditorFeatures/Core/Shared/Utilities/ProgressTrackerAdapter.cs deleted file mode 100644 index 17a7cb54d1f6e..0000000000000 --- a/src/EditorFeatures/Core/Shared/Utilities/ProgressTrackerAdapter.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.VisualStudio.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities -{ - /// - /// An adapter between editor's (which supports reporting - /// progress) and . - /// - internal class ProgressTrackerAdapter : IProgressTracker - { - private readonly IUIThreadOperationScope _uiThreadOperationScope; - private int _completedItems; - private int _totalItems; - private string? _description; - - public ProgressTrackerAdapter(IUIThreadOperationScope uiThreadOperationScope) - { - Requires.NotNull(uiThreadOperationScope, nameof(uiThreadOperationScope)); - _uiThreadOperationScope = uiThreadOperationScope; - } - - public string? Description - { - get => _description; - set - { - _description = value; - _uiThreadOperationScope.Description = value; - } - } - - public int CompletedItems => _completedItems; - - public int TotalItems => _totalItems; - - public void AddItems(int count) - { - Interlocked.Add(ref _totalItems, count); - ReportProgress(); - } - - public void Clear() - { - Interlocked.Exchange(ref _completedItems, 0); - Interlocked.Exchange(ref _totalItems, 0); - ReportProgress(); - } - - public void ItemCompleted() - { - Interlocked.Increment(ref _completedItems); - ReportProgress(); - } - - private void ReportProgress() - => _uiThreadOperationScope.Progress.Report(new ProgressInfo(_completedItems, _totalItems)); - } -} diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs deleted file mode 100644 index f3ea8f0c36ee4..0000000000000 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs +++ /dev/null @@ -1,208 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Tagging -{ - internal abstract partial class AbstractAsynchronousTaggerProvider - { - /// - /// Handles the job of batching up change notifications so that don't spam the editor with too - /// many update requests at a time. Updating the editor can even be paused and resumed at a - /// later point if some feature doesn't want the editor changing while it performs some bit of - /// work. - /// - private class BatchChangeNotifier : ForegroundThreadAffinitizedObject - { - private readonly ITextBuffer _subjectBuffer; - - /// - /// The worker we use to do work on the appropriate background or foreground thread. - /// - private readonly IAsynchronousOperationListener _listener; - private readonly IForegroundNotificationService _notificationService; - private readonly CancellationToken _cancellationToken; - - /// - /// We keep track of the last time we reported a span, so that if things have been idle for - /// a while, we don't unnecessarily delay the reporting, but if things are busy, we'll start - /// to throttle the notifications. - /// - private long _lastReportTick; - - // In general, we want IDE services to avoid reporting changes to the editor too rapidly. - // When we do, we diminish performance by choking the UI thread with lots of update - // operations. To help alleviate that, we don't immediately report changes to the UI. We - // instead create a timer that will report the changes and we enqueue any pending updates to - // a list that will be updated all at once the timer actually runs. - private bool _notificationRequestEnqueued; - private readonly SortedDictionary _snapshotVersionToSpansMap = - new(); - - /// - /// True if we are currently suppressing UI updates. While suppressed we still continue - /// doing everything as normal, except we do not update the UI. Then, when we are no longer - /// suppressed we will issue all pending UI notifications to the editor. During the time - /// that we're suppressed we will respond to all GetTags requests with the tags we had - /// before we were paused. - /// - public bool IsPaused { get; private set; } - private int _lastPausedTime; - - private readonly Action _notifyEditorNow; - - public BatchChangeNotifier( - IThreadingContext threadingContext, - ITextBuffer subjectBuffer, - IAsynchronousOperationListener listener, - IForegroundNotificationService notificationService, - Action notifyEditorNow, - CancellationToken cancellationToken) - : base(threadingContext) - { - Contract.ThrowIfNull(notifyEditorNow); - _subjectBuffer = subjectBuffer; - _listener = listener; - _notificationService = notificationService; - _cancellationToken = cancellationToken; - _notifyEditorNow = notifyEditorNow; - } - - public void Pause() - { - AssertIsForeground(); - - _lastPausedTime = Environment.TickCount; - this.IsPaused = true; - } - - public void Resume() - { - AssertIsForeground(); - _lastPausedTime = Environment.TickCount; - this.IsPaused = false; - } - - private static readonly Func s_addFunction = - _ => new NormalizedSnapshotSpanCollection(); - - internal void EnqueueChanges( - NormalizedSnapshotSpanCollection changedSpans) - { - AssertIsForeground(); - if (changedSpans.Count == 0) - { - return; - } - - var snapshot = changedSpans.First().Snapshot; - - var version = snapshot.Version.VersionNumber; - var currentSpans = _snapshotVersionToSpansMap.GetOrAdd(version, s_addFunction); - var allSpans = NormalizedSnapshotSpanCollection.Union(currentSpans, changedSpans); - _snapshotVersionToSpansMap[version] = allSpans; - - EnqueueNotificationRequest(TaggerDelay.NearImmediate); - } - - // We may get a flurry of 'Notify' calls if we've enqueued a lot of work and it's now just - // completed. Batch up all the notifications so we can tell the editor about them at the - // same time. - private void EnqueueNotificationRequest( - TaggerDelay delay) - { - AssertIsForeground(); - - if (_notificationRequestEnqueued) - { - // we already have a pending task to update the UI. No need to do anything at this - // point. - return; - } - - var currentTick = Environment.TickCount; - if (Math.Abs(currentTick - _lastReportTick) > TaggerDelay.NearImmediate.ComputeTimeDelay(_subjectBuffer).TotalMilliseconds) - { - _lastReportTick = currentTick; - this.NotifyEditor(); - } - else - { - // enqueue a task to update the UI with all the changed spans at some time in the - // future. - _notificationRequestEnqueued = true; - - // Note: this operation is uncancellable. We already updated our internal state in - // RecomputeTags. We must eventually notify the editor about these changes so that the - // UI reaches parity with our internal model. Also, if we cancel it, then - // 'reportTagsScheduled' will stay 'true' forever and we'll never notify the UI. - _notificationService.RegisterNotification( - () => - { - AssertIsForeground(); - - // First, clear the flag. That way any new changes we hear about will enqueue a task - // to run at a later point. - _notificationRequestEnqueued = false; - this.NotifyEditor(); - }, - (int)delay.ComputeTimeDelay(_subjectBuffer).TotalMilliseconds, - _listener.BeginAsyncOperation("EnqueueNotificationRequest"), - _cancellationToken); - } - } - - private void NotifyEditor() - { - AssertIsForeground(); - - // If we're currently suppressed, then just re-enqueue a request to update in the - // future. - if (this.IsPaused) - { - // TODO(cyrusn): Do we need to make this delay customizable? I don't think we do. - // Pausing is only used for features we don't want to spam the user with (like - // squiggles while the completion list is up. It's ok to have them appear 1.5 - // seconds later once we become un-paused. - if ((Environment.TickCount - _lastPausedTime) < TaggerConstants.IdleDelay) - { - EnqueueNotificationRequest(TaggerDelay.OnIdle); - return; - } - } - - using (Logger.LogBlock(FunctionId.Tagger_BatchChangeNotifier_NotifyEditor, CancellationToken.None)) - { - // Go through and report the snapshots from oldest to newest. - foreach (var snapshotAndSpans in _snapshotVersionToSpansMap) - { - var snapshot = snapshotAndSpans.Key; - var normalizedSpans = snapshotAndSpans.Value; - - _notifyEditorNow(normalizedSpans); - } - } - - // Finally, clear out the collection so that we don't re-report spans. - _snapshotVersionToSpansMap.Clear(); - _lastReportTick = Environment.TickCount; - - // reset paused time - _lastPausedTime = Environment.TickCount; - } - } - } -} diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index b0fcd709f7094..36086a86c84df 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -2,15 +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 System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Shared.Threading; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -24,22 +23,35 @@ namespace Microsoft.CodeAnalysis.Editor.Tagging { internal partial class AbstractAsynchronousTaggerProvider { + /// + /// The is the core part of our asynchronous + /// tagging infrastructure. It is the coordinator between s, + /// s, and s. + /// + /// The is the type that actually owns the + /// list of cached tags. When an says tags need to be recomputed, + /// the tag source starts the computation and calls to build + /// the new list of tags. When that's done, the tags are stored in . The + /// tagger, when asked for tags from the editor, then returns the tags that are stored in + /// + /// + /// There is a one-to-many relationship between s + /// and s. Special cases, like reference highlighting (which processes multiple + /// subject buffers at once) have their own providers and tag source derivations. + /// private sealed partial class TagSource : ForegroundThreadAffinitizedObject { - #region Fields that can be accessed from either thread - /// - /// The async worker we defer to handle foreground/background thread management for this - /// tagger. Note: some operations we perform on this must be uncancellable. Specifically, - /// once we've updated our internal state we need to *ensure* that the UI eventually gets in - /// sync with it. As such, we allow cancellation of our tasks *until* we update our state. - /// From that point on, we must proceed and execute the tasks. + /// If we get more than this many differences, then we just issue it as a single change + /// notification. The number has been completely made up without any data to support it. + /// + /// Internal for testing purposes. /// - private readonly AsynchronousSerialWorkQueue _workQueue; + private const int CoalesceDifferenceCount = 10; - private readonly AbstractAsynchronousTaggerProvider _dataSource; + #region Fields that can be accessed from either thread - private readonly IEqualityComparer> _tagSpanComparer; + private readonly AbstractAsynchronousTaggerProvider _dataSource; /// /// async operation notifier @@ -47,9 +59,16 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject private readonly IAsynchronousOperationListener _asyncListener; /// - /// foreground notification service + /// Work queue that collects high priority requests to call TagsChanged with. + /// + private readonly AsyncBatchingWorkQueue _highPriTagsChangedQueue; + + /// + /// Work queue that collects normal priority requests to call TagsChanged with. /// - private readonly IForegroundNotificationService _notificationService; + private readonly AsyncBatchingWorkQueue _normalPriTagsChangedQueue; + + private readonly ReferenceCountedDisposable _tagSourceState = new(new TagSourceState()); #endregion @@ -64,80 +83,129 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject /// private readonly ITaggerEventSource _eventSource; - /// - /// During the time that we are paused from updating the UI, we will use these tags instead. - /// - private ImmutableDictionary> _previousCachedTagTrees; - /// /// accumulated text changes since last tag calculation /// private TextChangeRange? _accumulatedTextChanges_doNotAccessDirectly; - private ImmutableDictionary> _cachedTagTrees_doNotAccessDirectly; - private object _state_doNotAccessDirecty; - private bool _upToDate_doNotAccessDirectly = false; - - #endregion - - public event Action>, bool> TagsChangedForBuffer; - - public event EventHandler Paused; - public event EventHandler Resumed; + private ImmutableDictionary> _cachedTagTrees_doNotAccessDirectly = ImmutableDictionary.Create>(); + private object? _state_doNotAccessDirecty; /// - /// A cancellation source we use for the initial tagging computation. We only cancel - /// if our ref count actually reaches 0. Otherwise, we always try to compute the initial - /// set of tags for our view/buffer. + /// Keep track of if we are processing the first request. If our provider returns + /// for , + /// then we'll want to synchronously block then and only then for tags. /// - private readonly CancellationTokenSource _initialComputationCancellationTokenSource = new(); + private bool _firstTagsRequest = true; - public TaggerDelay AddedTagNotificationDelay => _dataSource.AddedTagNotificationDelay; - public TaggerDelay RemovedTagNotificationDelay => _dataSource.RemovedTagNotificationDelay; + #endregion public TagSource( ITextView textViewOpt, ITextBuffer subjectBuffer, AbstractAsynchronousTaggerProvider dataSource, - IAsynchronousOperationListener asyncListener, - IForegroundNotificationService notificationService) + IAsynchronousOperationListener asyncListener) : base(dataSource.ThreadingContext) { + this.AssertIsForeground(); if (dataSource.SpanTrackingMode == SpanTrackingMode.Custom) - { throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode"); - } _subjectBuffer = subjectBuffer; _textViewOpt = textViewOpt; _dataSource = dataSource; _asyncListener = asyncListener; - _notificationService = notificationService; - _tagSpanComparer = new TagSpanComparer(_dataSource.TagComparer); - DebugRecordInitialStackTrace(); + _highPriTagsChangedQueue = new AsyncBatchingWorkQueue( + TaggerDelay.NearImmediate.ComputeTimeDelay(), + ProcessTagsChangedAsync, + equalityComparer: null, + asyncListener, + _tagSourceState.Target.DisposalToken); + + if (_dataSource.AddedTagNotificationDelay == TaggerDelay.NearImmediate) + { + // if the tagger wants "added tags" to be reported "NearImmediate"ly, then just reuse + // the "high pri" queue as that already reports things at that cadence. + _normalPriTagsChangedQueue = _highPriTagsChangedQueue; + } + else + { + _normalPriTagsChangedQueue = new AsyncBatchingWorkQueue( + _dataSource.AddedTagNotificationDelay.ComputeTimeDelay(), + ProcessTagsChangedAsync, + equalityComparer: null, + asyncListener, + _tagSourceState.Target.DisposalToken); + } - _workQueue = new AsynchronousSerialWorkQueue(ThreadingContext, asyncListener); - this.CachedTagTrees = ImmutableDictionary.Create>(); + DebugRecordInitialStackTrace(); _eventSource = CreateEventSource(); - Connect(); // Start computing the initial set of tags immediately. We want to get the UI // to a complete state as soon as possible. - ComputeInitialTags(); + EnqueueWork(initialTags: true); + + return; + + void Connect() + { + this.AssertIsForeground(); + + _eventSource.Changed += OnEventSourceChanged; + + if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges)) + { + _subjectBuffer.Changed += OnSubjectBufferChanged; + } + + if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag)) + { + if (_textViewOpt == null) + { + throw new ArgumentException( + nameof(_dataSource.CaretChangeBehavior) + " can only be specified for an " + nameof(IViewTaggerProvider)); + } + + _textViewOpt.Caret.PositionChanged += OnCaretPositionChanged; + } + + // Tell the interaction object to start issuing events. + _eventSource.Connect(); + } } - private void ComputeInitialTags() + private void Dispose() { - // Note: we always kick this off to the new UI pump instead of computing tags right - // on this thread. The reason for that is that we may be getting created at a time - // when the view itself is initializing. As such the view is not in a state where - // we want code touching it. - RegisterNotification( - () => RecomputeTagsForeground(initialTags: true), - delay: 0, - cancellationToken: GetCancellationToken(initialTags: true)); + _tagSourceState.Dispose(); + + _dataSource.RemoveTagSource(_textViewOpt, _subjectBuffer); + GC.SuppressFinalize(this); + + Disconnect(); + + return; + + void Disconnect() + { + this.AssertIsForeground(); + + // Tell the interaction object to stop issuing events. + _eventSource.Disconnect(); + + if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag)) + { + _textViewOpt.Caret.PositionChanged -= OnCaretPositionChanged; + } + + if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges)) + { + _subjectBuffer.Changed -= OnSubjectBufferChanged; + } + + _eventSource.Changed -= OnEventSourceChanged; + } } private ITaggerEventSource CreateEventSource() @@ -148,7 +216,7 @@ private ITaggerEventSource CreateEventSource() // notifications for when those options change. var optionChangedEventSources = _dataSource.Options.Concat(_dataSource.PerLanguageOptions) - .Select(o => TaggerEventSources.OnOptionChanged(_subjectBuffer, o, TaggerDelay.NearImmediate)).ToList(); + .Select(o => TaggerEventSources.OnOptionChanged(_subjectBuffer, o)).ToList(); if (optionChangedEventSources.Count == 0) { @@ -164,13 +232,13 @@ private TextChangeRange? AccumulatedTextChanges { get { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); return _accumulatedTextChanges_doNotAccessDirectly; } set { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); _accumulatedTextChanges_doNotAccessDirectly = value; } } @@ -179,101 +247,32 @@ private ImmutableDictionary> CachedTagTre { get { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); return _cachedTagTrees_doNotAccessDirectly; } set { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); _cachedTagTrees_doNotAccessDirectly = value; } } - private object State + private object? State { get { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); return _state_doNotAccessDirecty; } set { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); _state_doNotAccessDirecty = value; } } - private bool UpToDate - { - get - { - _workQueue.AssertIsForeground(); - return _upToDate_doNotAccessDirectly; - } - - set - { - _workQueue.AssertIsForeground(); - _upToDate_doNotAccessDirectly = value; - } - } - - public void RegisterNotification(Action action, int delay, CancellationToken cancellationToken) - => _notificationService.RegisterNotification(action, delay, _asyncListener.BeginAsyncOperation(typeof(TTag).Name), cancellationToken); - - private void Connect() - { - _workQueue.AssertIsForeground(); - - _eventSource.Changed += OnEventSourceChanged; - _eventSource.UIUpdatesResumed += OnUIUpdatesResumed; - _eventSource.UIUpdatesPaused += OnUIUpdatesPaused; - - if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges)) - { - _subjectBuffer.Changed += OnSubjectBufferChanged; - } - - if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag)) - { - if (_textViewOpt == null) - { - throw new ArgumentException( - nameof(_dataSource.CaretChangeBehavior) + " can only be specified for an " + nameof(IViewTaggerProvider)); - } - - _textViewOpt.Caret.PositionChanged += OnCaretPositionChanged; - } - - // Tell the interaction object to start issuing events. - _eventSource.Connect(); - } - - public void Disconnect() - { - _workQueue.AssertIsForeground(); - _workQueue.CancelCurrentWork(remainCancelled: true); - - // Tell the interaction object to stop issuing events. - _eventSource.Disconnect(); - - if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag)) - { - _textViewOpt.Caret.PositionChanged -= OnCaretPositionChanged; - } - - if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges)) - { - _subjectBuffer.Changed -= OnSubjectBufferChanged; - } - - _eventSource.UIUpdatesPaused -= OnUIUpdatesPaused; - _eventSource.UIUpdatesResumed -= OnUIUpdatesResumed; - _eventSource.Changed -= OnEventSourceChanged; - } - private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference) { this.AssertIsForeground(); @@ -283,99 +282,10 @@ private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference) return; } - RaiseTagsChanged(SpecializedCollections.SingletonCollection( + OnTagsChangedForBuffer(SpecializedCollections.SingletonCollection( new KeyValuePair(buffer, difference)), initialTags: false); } - - private void RaiseTagsChanged( - ICollection> collection, bool initialTags) - { - TagsChangedForBuffer?.Invoke(collection, initialTags); - } - - private void RaisePaused() - => this.Paused?.Invoke(this, EventArgs.Empty); - - private void RaiseResumed() - => this.Resumed?.Invoke(this, EventArgs.Empty); - - private static T NextOrDefault(IEnumerator enumerator) - => enumerator.MoveNext() ? enumerator.Current : default; - - /// - /// Return all the spans that appear in only one of "latestSpans" or "previousSpans". - /// - private static DiffResult Difference(IEnumerable> latestSpans, IEnumerable> previousSpans, IEqualityComparer comparer) - where T : ITag - { - using var addedPool = SharedPools.Default>().GetPooledObject(); - using var removedPool = SharedPools.Default>().GetPooledObject(); - using var latestEnumerator = latestSpans.GetEnumerator(); - using var previousEnumerator = previousSpans.GetEnumerator(); - - var added = addedPool.Object; - var removed = removedPool.Object; - - var latest = NextOrDefault(latestEnumerator); - var previous = NextOrDefault(previousEnumerator); - - while (latest != null && previous != null) - { - var latestSpan = latest.Span; - var previousSpan = previous.Span; - - if (latestSpan.Start < previousSpan.Start) - { - added.Add(latestSpan); - latest = NextOrDefault(latestEnumerator); - } - else if (previousSpan.Start < latestSpan.Start) - { - removed.Add(previousSpan); - previous = NextOrDefault(previousEnumerator); - } - else - { - // If the starts are the same, but the ends are different, report the larger - // region to be conservative. - if (previousSpan.End > latestSpan.End) - { - removed.Add(previousSpan); - latest = NextOrDefault(latestEnumerator); - } - else if (latestSpan.End > previousSpan.End) - { - added.Add(latestSpan); - previous = NextOrDefault(previousEnumerator); - } - else - { - if (!comparer.Equals(latest.Tag, previous.Tag)) - { - added.Add(latestSpan); - } - - latest = NextOrDefault(latestEnumerator); - previous = NextOrDefault(previousEnumerator); - } - } - } - - while (latest != null) - { - added.Add(latest.Span); - latest = NextOrDefault(latestEnumerator); - } - - while (previous != null) - { - removed.Add(previous.Span); - previous = NextOrDefault(previousEnumerator); - } - - return new DiffResult(added, removed); - } } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs new file mode 100644 index 0000000000000..c13b91768cc9a --- /dev/null +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSourceState.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Tagging; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Tagging +{ + internal partial class AbstractAsynchronousTaggerProvider + { + private sealed partial class TagSource + { + private class TagSourceState : IDisposable + { + /// + /// Token that is triggered when we are disposed. Used to ensure that the initial work we + /// kick off still gets stopped if the created tagger is disposed before that finishes. + /// + private readonly CancellationTokenSource _disposalTokenSource; + + /// + /// Series of tokens used to cancel previous outstanding work when new work comes in. + /// + private readonly CancellationSeries _cancellationSeries; + + /// + /// Work queue that collects event notifications and kicks off the work to process them. + /// + private Task _eventWorkQueue; + + public TagSourceState() + { + _disposalTokenSource = new(); + _cancellationSeries = new CancellationSeries(_disposalTokenSource.Token); + _eventWorkQueue = Task.CompletedTask; + } + + void IDisposable.Dispose() + { + // Stop computing any initial tags if we've been asked for them. + _disposalTokenSource.Cancel(); + _disposalTokenSource.Dispose(); + _cancellationSeries.Dispose(); + } + + public CancellationToken DisposalToken => _disposalTokenSource.Token; + + /// + /// Gets the appropriate cancellation token for this current piece of work. This will cancel the last + /// piece of computation work and enqueue the next. That behavior doesn't apply for the very first + /// (i.e. ) tag request we make. We don't want that to be cancellable as + /// we want that result to be shown as soon as possible. + /// + public CancellationToken GetCancellationToken(bool initialTags) + => initialTags ? _disposalTokenSource.Token : _cancellationSeries.CreateNext(); + + public void EnqueueWork( + Func workAsync, + TaggerDelay delay, + IAsyncToken asyncToken, + CancellationToken cancellationToken) + { + lock (this) + { + _eventWorkQueue = _eventWorkQueue.ContinueWithAfterDelayFromAsync( + _ => workAsync(), + cancellationToken, + (int)delay.ComputeTimeDelay().TotalMilliseconds, + TaskContinuationOptions.None, + TaskScheduler.Default).CompletesAsyncOperation(asyncToken); + } + } + } + } + } +} diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSpanComparer.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs similarity index 52% rename from src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSpanComparer.cs rename to src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs index 243a2f1ebc6df..d46ec6f98deb9 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSpanComparer.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs @@ -6,30 +6,19 @@ using System.Collections.Generic; using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Tagging { internal abstract partial class AbstractAsynchronousTaggerProvider { - private class TagSpanComparer : IEqualityComparer> + private partial class TagSource : IEqualityComparer> { - private readonly IEqualityComparer _tagComparer; - - public TagSpanComparer(IEqualityComparer tagComparer) - => _tagComparer = tagComparer; - public bool Equals(ITagSpan x, ITagSpan y) - { - if (x.Span != y.Span) - { - return false; - } - - return _tagComparer.Equals(x.Tag, y.Tag); - } + => x.Span == y.Span && EqualityComparer.Default.Equals(x.Tag, y.Tag); public int GetHashCode(ITagSpan obj) - => obj.Span.GetHashCode() ^ _tagComparer.GetHashCode(obj.Tag); + => Hash.Combine(obj.Span.GetHashCode(), EqualityComparer.Default.GetHashCode(obj.Tag)); } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index bc682bd43152b..b871b598e8846 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.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.Collections.Generic; using System.Collections.Immutable; @@ -15,63 +13,22 @@ using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Tagging { internal partial class AbstractAsynchronousTaggerProvider { - /// - /// The is the core part of our asynchronous - /// tagging infrastructure. It is the coordinator between s, - /// s, and s. - /// - /// The is the type that actually owns the - /// list of cached tags. When an says tags need to be recomputed, - /// the tag source starts the computation and calls to build - /// the new list of tags. When that's done, the tags are stored in . The - /// tagger, when asked for tags from the editor, then returns the tags that are stored in - /// - /// - /// There is a one-to-many relationship between s - /// and s. Special cases, like reference highlighting (which processes multiple - /// subject buffers at once) have their own providers and tag source derivations. - /// private partial class TagSource { - private void OnUIUpdatesPaused(object sender, EventArgs e) - { - _workQueue.AssertIsForeground(); - _previousCachedTagTrees = CachedTagTrees; - - RaisePaused(); - } - - private void OnUIUpdatesResumed(object sender, EventArgs e) - { - _workQueue.AssertIsForeground(); - _previousCachedTagTrees = null; - - RaiseResumed(); - } - - private void OnEventSourceChanged(object sender, TaggerEventArgs e) - { - // First, cancel any previous requests (either still queued, or started). We no longer - // want to continue it if new changes have come in. - _workQueue.CancelCurrentWork(); - RegisterNotification( - () => RecomputeTagsForeground(initialTags: false), - (int)e.Delay.ComputeTimeDelay().TotalMilliseconds, - GetCancellationToken(initialTags: false)); - } - - private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) + private void OnCaretPositionChanged(object? _, CaretPositionChangedEventArgs e) { this.AssertIsForeground(); @@ -103,19 +60,19 @@ private void RemoveAllTags() var oldTagTree = GetTagTree(snapshot, oldTagTrees); // everything from old tree is removed. - RaiseTagsChanged(snapshot.TextBuffer, new DiffResult(added: null, removed: oldTagTree.GetSpans(snapshot).Select(s => s.Span))); + RaiseTagsChanged(snapshot.TextBuffer, new DiffResult(added: null, removed: new(oldTagTree.GetSpans(snapshot).Select(s => s.Span)))); } - private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e) + private void OnSubjectBufferChanged(object? _, TextContentChangedEventArgs e) { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); UpdateTagsForTextChange(e); AccumulateTextChanges(e); } private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged) { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); var contentChanges = contentChanged.Changes; var count = contentChanges.Count; @@ -137,21 +94,20 @@ private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged) break; default: - var textChangeRanges = new TextChangeRange[count]; - for (var i = 0; i < count; i++) { - var c = contentChanges[i]; - textChangeRanges[i] = new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength); - } + using var _ = ArrayBuilder.GetInstance(count, out var textChangeRanges); + foreach (var c in contentChanges) + textChangeRanges.Add(new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength)); - this.AccumulatedTextChanges = this.AccumulatedTextChanges.Accumulate(textChangeRanges); - break; + this.AccumulatedTextChanges = this.AccumulatedTextChanges.Accumulate(textChangeRanges); + break; + } } } private void UpdateTagsForTextChange(TextContentChangedEventArgs e) { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.RemoveAllTags)) { @@ -169,34 +125,22 @@ private void UpdateTagsForTextChange(TextContentChangedEventArgs e) private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e) { - if (!e.Changes.Any()) - { + if (e.Changes.Count == 0) return; - } - - // We might be able to steal the cached tags from another tag source - if (TryStealTagsFromRelatedTagSource(e)) - { - return; - } var buffer = e.After.TextBuffer; if (!this.CachedTagTrees.TryGetValue(buffer, out var treeForBuffer)) - { return; - } var tagsToRemove = e.Changes.SelectMany(c => treeForBuffer.GetIntersectingSpans(new SnapshotSpan(e.After, c.NewSpan))); if (!tagsToRemove.Any()) - { return; - } var allTags = treeForBuffer.GetSpans(e.After).ToList(); var newTagTree = new TagSpanIntervalTree( buffer, treeForBuffer.SpanTrackingMode, - allTags.Except(tagsToRemove, _tagSpanComparer)); + allTags.Except(tagsToRemove, comparer: this)); var snapshot = e.After; @@ -219,59 +163,32 @@ private TagSpanIntervalTree GetTagTree(ITextSnapshot snapshot, ImmutableDi : new TagSpanIntervalTree(snapshot.TextBuffer, _dataSource.SpanTrackingMode); } - private static bool TryStealTagsFromRelatedTagSource(TextContentChangedEventArgs e) + private void OnEventSourceChanged(object? _1, TaggerEventArgs _2) { - // see bug 778731 -#if INTERACTIVE - // If we don't have a way to find the related buffer, we're done immediately - if (bufferToRelatedTagSource == null) - { - return false; + EnqueueWork(initialTags: false); } - // We can only steal tags if we know where the edit came from, so do we? - var editTag = e.EditTag as RestoreHistoryEditTag; - - if (editTag == null) + private void EnqueueWork(bool initialTags) { - return false; - } + using var stateRef = _tagSourceState.TryAddReference(); - var originalSpan = editTag.OriginalSpan; - - var relatedTagSource = bufferToRelatedTagSource(originalSpan.Snapshot.TextBuffer); - if (relatedTagSource == null) - { - return false; - } + // No point proceeding if we've been disposed. + if (stateRef is null) + return; - // Reading the other tag source's cached tags is safe, since this field is allowed to be - // accessed from multiple threads and is immutable. We still need to have a local copy - // though to play it safe and be a good citizen (well, as good as a citizen that's about - // to steal something can be...) - var relatedCachedTags = relatedTagSource.cachedTags; - TagSpanIntervalTree relatedIntervalTree; + var state = stateRef.Target; - if (!relatedCachedTags.TryGetValue(originalSpan.Snapshot.TextBuffer, out relatedIntervalTree)) - { - return false; - } + var cancellationToken = state.GetCancellationToken(initialTags); - // Excellent! Let's build a new interval tree with these tags mapped to our buffer - // instead - var tagsForThisBuffer = from tagSpan in relatedIntervalTree.GetSpans(originalSpan.Snapshot) - where tagSpan.Span.IntersectsWith(originalSpan) - let snapshotSpan = new SnapshotSpan(e.After, tagSpan.SpanStart - originalSpan.Start, tagSpan.Span.Length) - select new TagSpan(snapshotSpan, tagSpan.Tag); - - var intervalTreeForThisBuffer = new TagSpanIntervalTree(e.After.TextBuffer, relatedIntervalTree.SpanTrackingMode, tagsForThisBuffer); - - // Update our cached tags - UpdateCachedTagsForBuffer(e.After, intervalTreeForThisBuffer); - return true; -#else - return false; -#endif + // Continue after the preceeding task unilaterally. Note that we pass LazyCancellation so that + // we still wait for that task to complete even if cancelled before we proceed. This is necessary + // as that prior task may mutate state (even if cancelled) so we cannot proceed until we know it + // is completely done. + state.EnqueueWork(async () => + { + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await RecomputeTagsForegroundAsync(initialTags, cancellationToken).ConfigureAwait(false); + }, _dataSource.EventChangeDelay, _asyncListener.BeginAsyncOperation(nameof(EnqueueWork)), cancellationToken); } /// @@ -283,63 +200,64 @@ where tagSpan.Span.IntersectsWith(originalSpan) /// complete almost immediately. Once open though, our normal delays come into play /// so as to not cause a flashy experience. /// - private void RecomputeTagsForeground(bool initialTags) + private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationToken cancellationToken) { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); + if (cancellationToken.IsCancellationRequested) + return; - using (Logger.LogBlock(FunctionId.Tagger_TagSource_RecomputeTags, CancellationToken.None)) + using (Logger.LogBlock(FunctionId.Tagger_TagSource_RecomputeTags, cancellationToken)) { - // Stop any existing work we're currently engaged in - _workQueue.CancelCurrentWork(); - - // Mark that we're not up to date. We'll remain in that state until the next - // tag production stage finally completes. - this.UpToDate = false; - - var cancellationToken = GetCancellationToken(initialTags); + // Make a copy of all the data we need while we're on the foreground. Then switch to a threadpool + // thread to do the computation. Finally, once new tags have been computed, then we update our state + // again on the foreground. var spansToTag = GetSpansAndDocumentsToTag(); - - // Make a copy of all the data we need while we're on the foreground. Then - // pass it along everywhere needed. Finally, once new tags have been computed, - // then we update our state again on the foreground. var caretPosition = _dataSource.GetCaretPoint(_textViewOpt, _subjectBuffer); - var textChangeRange = this.AccumulatedTextChanges; var oldTagTrees = this.CachedTagTrees; var oldState = this.State; - _workQueue.EnqueueBackgroundTask( - ct => this.RecomputeTagsAsync( - oldState, caretPosition, textChangeRange, spansToTag, oldTagTrees, initialTags, ct), - GetType().Name + ".RecomputeTags", cancellationToken); + var textChangeRange = this.AccumulatedTextChanges; + this.AccumulatedTextChanges = null; + + await TaskScheduler.Default; + + cancellationToken.ThrowIfCancellationRequested(); + + // Create a context to store pass the information along and collect the results. + var context = new TaggerContext( + oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken); + await ProduceTagsAsync(context).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + // Process the result to determine what changed. + var newTagTrees = ComputeNewTagTrees(oldTagTrees, context); + var bufferToChanges = ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, cancellationToken); + + // Then switch back to the UI thread to update our state and kick off the work to notify the editor. + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + // Once we assign our state, we're uncancellable. We must report the changed information + // to the editor. The only case where it's ok not to is if the tagger itself is disposed. + cancellationToken = CancellationToken.None; + + this.CachedTagTrees = newTagTrees; + this.State = context.State; + + OnTagsChangedForBuffer(bufferToChanges, initialTags); } } - /// - /// Get's the cancellation token that will control the processing of this set of - /// tags. If this is the initial set of tags, we have a single cancellation token - /// that can't be interrupted *unless* the entire tagger is shut down. If this - /// is anything after the initial set of tags, then we'll control things with a - /// cancellation token that is triggered every time we hear about new changes. - /// - /// This is a 'kick the can down the road' approach whereby we keep delaying - /// producing tags (and updating the UI) until a reasonable pause has happened. - /// This approach helps prevent flashing in the UI. - /// - private CancellationToken GetCancellationToken(bool initialTags) - => initialTags - ? _initialComputationCancellationTokenSource.Token - : _workQueue.CancellationToken; - private ImmutableArray GetSpansAndDocumentsToTag() { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); // TODO: Update to tag spans from all related documents. - var snapshotToDocumentMap = new Dictionary(); + using var _ = PooledDictionary.GetInstance(out var snapshotToDocumentMap); var spansToTag = _dataSource.GetSpansToTag(_textViewOpt, _subjectBuffer); - var spansAndDocumentsToTag = spansToTag.Select(span => + var spansAndDocumentsToTag = spansToTag.SelectAsArray(span => { if (!snapshotToDocumentMap.TryGetValue(span.Snapshot, out var document)) { @@ -351,7 +269,7 @@ private ImmutableArray GetSpansAndDocumentsToTag() // document can be null if the buffer the given span is part of is not part of our workspace. return new DocumentSnapshotSpan(document, span); - }).ToImmutableArray(); + }); return spansAndDocumentsToTag; } @@ -367,12 +285,19 @@ private static void CheckSnapshot(ITextSnapshot snapshot) } } - private ImmutableDictionary> ConvertToTagTrees( + private ImmutableDictionary> ComputeNewTagTrees( ImmutableDictionary> oldTagTrees, - ISet buffersToTag, - ILookup> newTagsByBuffer, - IEnumerable spansTagged) + TaggerContext context) { + // Ignore any tag spans reported for any buffers we weren't interested in. + + var spansToTag = context.SpansToTag; + var buffersToTag = spansToTag.Select(dss => dss.SnapshotSpan.Snapshot.TextBuffer).ToSet(); + var newTagsByBuffer = + context.tagSpans.Where(ts => buffersToTag.Contains(ts.Span.Snapshot.TextBuffer)) + .ToLookup(t => t.Span.Snapshot.TextBuffer); + var spansTagged = context._spansTagged; + var spansToInvalidateByBuffer = spansTagged.ToLookup( keySelector: span => span.SnapshotSpan.Snapshot.TextBuffer, elementSelector: span => span.SnapshotSpan); @@ -386,15 +311,13 @@ private ImmutableDictionary> ConvertToTag { var newTagTree = ComputeNewTagTree(oldTagTrees, buffer, newTagsByBuffer[buffer], spansToInvalidateByBuffer[buffer]); if (newTagTree != null) - { newTagTrees = newTagTrees.Add(buffer, newTagTree); - } } return newTagTrees; } - private TagSpanIntervalTree ComputeNewTagTree( + private TagSpanIntervalTree? ComputeNewTagTree( ImmutableDictionary> oldTagTrees, ITextBuffer textBuffer, IEnumerable> newTags, @@ -443,25 +366,7 @@ private IEnumerable> GetNonIntersectingTagSpans(IEnumerable>( spansToInvalidate.SelectMany(ss => oldTagTree.GetIntersectingSpans(ss))); - return oldTagTree.GetSpans(snapshot).Except(tagSpansToInvalidate, _tagSpanComparer); - } - - private async Task RecomputeTagsAsync( - object oldState, - SnapshotPoint? caretPosition, - TextChangeRange? textChangeRange, - ImmutableArray spansToTag, - ImmutableDictionary> oldTagTrees, - bool initialTags, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var context = new TaggerContext( - oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken); - await ProduceTagsAsync(context).ConfigureAwait(false); - - ProcessContext(oldTagTrees, context, initialTags); + return oldTagTree.GetSpans(snapshot).Except(tagSpansToInvalidate, comparer: this); } private bool ShouldSkipTagProduction() @@ -475,53 +380,22 @@ private bool ShouldSkipTagProduction() private Task ProduceTagsAsync(TaggerContext context) { - if (ShouldSkipTagProduction()) - { - // If the feature is disabled, then just produce no tags. - return Task.CompletedTask; - } - - return _dataSource.ProduceTagsAsync(context); - } - - private void ProduceTagsSynchronously(TaggerContext context) - { - if (ShouldSkipTagProduction()) - { - return; - } - - _dataSource.ProduceTagsSynchronously(context); + // If the feature is disabled, then just produce no tags. + return ShouldSkipTagProduction() + ? Task.CompletedTask + : _dataSource.ProduceTagsAsync(context); } - private void ProcessContext( - ImmutableDictionary> oldTagTrees, - TaggerContext context, - bool initialTags) - { - var buffersToTag = context.SpansToTag.Select(dss => dss.SnapshotSpan.Snapshot.TextBuffer).ToSet(); - - // Ignore any tag spans reported for any buffers we weren't interested in. - var newTagsByBuffer = context.tagSpans.Where(ts => buffersToTag.Contains(ts.Span.Snapshot.TextBuffer)) - .ToLookup(t => t.Span.Snapshot.TextBuffer); - - var newTagTrees = ConvertToTagTrees(oldTagTrees, buffersToTag, newTagsByBuffer, context._spansTagged); - ProcessNewTagTrees( - context.SpansToTag, oldTagTrees, newTagTrees, - context.State, initialTags, context.CancellationToken); - } - - private void ProcessNewTagTrees( + private static Dictionary ProcessNewTagTrees( ImmutableArray spansToTag, ImmutableDictionary> oldTagTrees, ImmutableDictionary> newTagTrees, - object newState, - bool initialTags, CancellationToken cancellationToken) { - var bufferToChanges = new Dictionary(); using (Logger.LogBlock(FunctionId.Tagger_TagSource_ProcessNewTags, cancellationToken)) { + var bufferToChanges = new Dictionary(); + foreach (var (latestBuffer, latestSpans) in newTagTrees) { var snapshot = spansToTag.First(s => s.SnapshotSpan.Snapshot.TextBuffer == latestBuffer).SnapshotSpan.Snapshot; @@ -534,7 +408,7 @@ private void ProcessNewTagTrees( else { // It's a new buffer, so report all spans are changed - bufferToChanges[latestBuffer] = new DiffResult(added: latestSpans.GetSpans(snapshot).Select(t => t.Span), removed: null); + bufferToChanges[latestBuffer] = new DiffResult(added: new(latestSpans.GetSpans(snapshot).Select(t => t.Span)), removed: null); } } @@ -543,141 +417,137 @@ private void ProcessNewTagTrees( if (!newTagTrees.ContainsKey(oldBuffer)) { // This buffer disappeared, so let's notify that the old tags are gone - bufferToChanges[oldBuffer] = new DiffResult(added: null, removed: previousSpans.GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span)); + bufferToChanges[oldBuffer] = new DiffResult(added: null, removed: new(previousSpans.GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span))); } } + + return bufferToChanges; } + } - if (_workQueue.IsForeground()) + /// + /// Return all the spans that appear in only one of or . + /// + private static DiffResult ComputeDifference( + ITextSnapshot snapshot, + TagSpanIntervalTree latestTree, + TagSpanIntervalTree previousTree) + { + var latestSpans = latestTree.GetSpans(snapshot); + var previousSpans = previousTree.GetSpans(snapshot); + + using var _1 = ArrayBuilder.GetInstance(out var added); + using var _2 = ArrayBuilder.GetInstance(out var removed); + using var latestEnumerator = latestSpans.GetEnumerator(); + using var previousEnumerator = previousSpans.GetEnumerator(); + + var latest = NextOrNull(latestEnumerator); + var previous = NextOrNull(previousEnumerator); + + while (latest != null && previous != null) { - // If we're on the foreground already, we can just update our internal state directly. - UpdateStateAndReportChanges(newTagTrees, bufferToChanges, newState, initialTags); + var latestSpan = latest.Span; + var previousSpan = previous.Span; + + if (latestSpan.Start < previousSpan.Start) + { + added.Add(latestSpan); + latest = NextOrNull(latestEnumerator); + } + else if (previousSpan.Start < latestSpan.Start) + { + removed.Add(previousSpan); + previous = NextOrNull(previousEnumerator); + } + else + { + // If the starts are the same, but the ends are different, report the larger + // region to be conservative. + if (previousSpan.End > latestSpan.End) + { + removed.Add(previousSpan); + latest = NextOrNull(latestEnumerator); + } + else if (latestSpan.End > previousSpan.End) + { + added.Add(latestSpan); + previous = NextOrNull(previousEnumerator); + } + else + { + if (!EqualityComparer.Default.Equals(latest.Tag, previous.Tag)) + added.Add(latestSpan); + + latest = NextOrNull(latestEnumerator); + previous = NextOrNull(previousEnumerator); + } + } } - else if (initialTags) + + while (latest != null) { - // If this is the initial set of tags, we fast-track a notification about whatever initial tags we - // computed. This way the UI is updated quickly for that initial set, and we don't have to wait a - // potentially very long time as the foreground-thread-queue makes it way to our notification. - // - // Do this in a fire and forget manner, but ensure we notify the test harness of this so that it - // doesn't try to acquire tag results prior to this work finishing. - var asyncToken = this._asyncListener.BeginAsyncOperation(nameof(ProcessNewTagTrees)); - Task.Run(async () => - { - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - UpdateStateAndReportChanges(newTagTrees, bufferToChanges, newState, initialTags); - }, CancellationToken.None).CompletesAsyncOperation(asyncToken); // TODO: What should the cancellation behavior be here? passing CancellationToken.None for now + added.Add(latest.Span); + latest = NextOrNull(latestEnumerator); } - else + + while (previous != null) { - // Otherwise report back on the foreground to update the state and let our clients know about the - // change. This will go to the end of the foreground processing queue. This will normally process - // quickly once VS is loaded, but it may take some time initially when VS is loading and the UI - // thread is highly occupied. This helps ensure that we don't oversaturate the UI during a very - // contended period of time. - RegisterNotification(() => UpdateStateAndReportChanges( - newTagTrees, bufferToChanges, newState, initialTags), - delay: 0, - cancellationToken: cancellationToken); + removed.Add(previous.Span); + previous = NextOrNull(previousEnumerator); } - } - private void UpdateStateAndReportChanges( - ImmutableDictionary> newTagTrees, - Dictionary bufferToChanges, - object newState, - bool initialTags) - { - _workQueue.AssertIsForeground(); - - // Now that we're back on the UI thread, we can safely update our state with - // what we've computed. There is no concern with race conditions now. For - // example, say that another change happened between the time when we - // registered for UpdateStateAndReportChanges and now. If we processed that - // notification (on the UI thread) first, then our cancellation token would - // have been triggered, and the foreground notification service would not - // call into this method. - // - // If, instead, we did get called into, then we will update our instance state. - // Then when the foreground notification service runs RecomputeTagsForeground - // it will see that state and use it as the new basis on which to compute diffs - // and whatnot. - this.CachedTagTrees = newTagTrees; - this.AccumulatedTextChanges = null; - this.State = newState; - - // Mark that we're up to date. If any accurate taggers come along, they can use our - // cached information. - this.UpToDate = true; - - // Note: we're raising changes here on the UI thread. However, this doesn't actually - // mean we'll be notifying the editor. Instead, these will be batched up in the - // AsynchronousTagger's BatchChangeNotifier. If we tell it about enough changes - // to a file, it will coalesce them into one large change to keep chattiness with - // the editor down. - RaiseTagsChanged(bufferToChanges, initialTags); - } + return new DiffResult(new(added), new(removed)); - private DiffResult ComputeDifference( - ITextSnapshot snapshot, - TagSpanIntervalTree latestSpans, - TagSpanIntervalTree previousSpans) - { - return Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer); + static ITagSpan? NextOrNull(IEnumerator> enumerator) + => enumerator.MoveNext() ? enumerator.Current : null; } /// /// Returns the TagSpanIntervalTree containing the tags for the given buffer. If no tags /// exist for the buffer at all, null is returned. /// - public TagSpanIntervalTree TryGetTagIntervalTreeForBuffer(ITextBuffer buffer) - { - _workQueue.AssertIsForeground(); - - // If we're currently pausing updates to the UI, then just use the tags we had before we - // were paused so that nothing changes. - // - // We're on the UI thread, so it's safe to access these variables. - var map = _previousCachedTagTrees ?? this.CachedTagTrees; - map.TryGetValue(buffer, out var tags); - return tags; - } - - public TagSpanIntervalTree GetAccurateTagIntervalTreeForBuffer(ITextBuffer buffer, CancellationToken cancellationToken) + private TagSpanIntervalTree? TryGetTagIntervalTreeForBuffer(ITextBuffer buffer) { - _workQueue.AssertIsForeground(); + this.AssertIsForeground(); - if (!this.UpToDate) + // If this is the first time we're being asked for tags, and we're a tagger that + // requires the initial tags be available synchronously on this call, and the + // computation of tags hasn't completed yet, then force the tags to be computed + // now on this thread. The singular use case for this is Outlining which needs + // those tags synchronously computed for things like Metadata-as-Source collapsing. + if (_firstTagsRequest && + _dataSource.ComputeInitialTagsSynchronously(buffer) && + !this.CachedTagTrees.TryGetValue(buffer, out _)) { - // We're not up to date. That means we have an outstanding update that we're - // currently processing. Unfortunately we have no way to track the progress of - // that update (i.e. a Task). Also, even if we did, we'd have the problem that - // we have delays coded into the normal tagging process. So waiting on that Task - // could take a long time. - // - // So, instead, we just cancel whatever work we're currently doing, and we just - // compute the results synchronously in this call. - - // We can cancel any background computations currently happening - _workQueue.CancelCurrentWork(); + using var stateRef = _tagSourceState.TryAddReference(); + if (stateRef != null) + { + var disposalToken = stateRef.Target.DisposalToken; - var spansToTag = GetSpansAndDocumentsToTag(); + this.ThreadingContext.JoinableTaskFactory.Run(() => + this.RecomputeTagsForegroundAsync(initialTags: true, disposalToken)); + } + } - // Safe to access _cachedTagTrees here. We're on the UI thread. - var oldTagTrees = this.CachedTagTrees; - var caretPoint = _dataSource.GetCaretPoint(_textViewOpt, _subjectBuffer); + _firstTagsRequest = false; - var context = new TaggerContext( - this.State, spansToTag, caretPoint, this.AccumulatedTextChanges, oldTagTrees, cancellationToken); + // We're on the UI thread, so it's safe to access these variables. + this.CachedTagTrees.TryGetValue(buffer, out var tags); + return tags; + } - ProduceTagsSynchronously(context); + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection requestedSpans) + { + this.AssertIsForeground(); + if (requestedSpans.Count == 0) + return SpecializedCollections.EmptyEnumerable>(); - ProcessContext(oldTagTrees, context, initialTags: false); - } + var buffer = requestedSpans.First().Snapshot.TextBuffer; + var tags = this.TryGetTagIntervalTreeForBuffer(buffer); - Debug.Assert(this.UpToDate); - this.CachedTagTrees.TryGetValue(buffer, out var tags); - return tags; + return tags == null + ? SpecializedCollections.EmptyEnumerable>() + : tags.GetIntersectingTagSpans(requestedSpans); } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ReferenceCounting.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ReferenceCounting.cs index 93b741fb8606c..bd7974d1f375e 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ReferenceCounting.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ReferenceCounting.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.Diagnostics; using System.Threading; @@ -17,7 +15,6 @@ private partial class TagSource { /// How many taggers are currently using us. private int _taggers = 0; - private bool _disposed = false; ~TagSource() { @@ -36,30 +33,10 @@ private partial class TagSource } } - public event EventHandler Disposed = (s, e) => { }; - - private void Dispose() - { - if (_disposed) - { - Debug.Fail("Tagger already disposed"); - return; - } - - // Stop computing any initial tags if we've been asked for them. - _initialComputationCancellationTokenSource.Cancel(); - _disposed = true; - this.Disposed(this, EventArgs.Empty); - GC.SuppressFinalize(this); - - this.Disconnect(); - } - internal void OnTaggerAdded(Tagger _) { // this should be only called from UI thread. // in unit test, must be called from same thread as OnTaggerDisposed - Contract.ThrowIfTrue(_disposed); Contract.ThrowIfFalse(_taggers >= 0); _taggers++; @@ -87,8 +64,8 @@ internal void TestOnly_Dispose() => Dispose(); #if DEBUG - private Thread _thread; - private string _stackTrace; + private Thread? _thread; + private string? _stackTrace; private void DebugRecordInitialStackTrace() => _stackTrace = new StackTrace().ToString(); diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs new file mode 100644 index 0000000000000..3437033b7809f --- /dev/null +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_TagsChanged.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Tagging +{ + internal partial class AbstractAsynchronousTaggerProvider + { + private partial class TagSource + { + public event EventHandler? TagsChanged; + + private void OnTagsChangedForBuffer( + ICollection> changes, bool initialTags) + { + this.AssertIsForeground(); + + foreach (var change in changes) + { + if (change.Key != _subjectBuffer) + continue; + + // Removed tags are always treated as high pri, so we can clean their stale + // data out from the ui immediately. + _highPriTagsChangedQueue.AddWork(change.Value.Removed); + + // Added tags are run at normal priority, except in the case where this is the + // initial batch of tags. We want those to appear immediately to make the UI + // show up quickly. + var addedTagsQueue = initialTags ? _highPriTagsChangedQueue : _normalPriTagsChangedQueue; + addedTagsQueue.AddWork(change.Value.Added); + } + } + + private Task ProcessTagsChangedAsync( + ImmutableArray snapshotSpans, CancellationToken cancellationToken) + { + var tagsChanged = this.TagsChanged; + if (tagsChanged == null) + return Task.CompletedTask; + + foreach (var collection in snapshotSpans) + { + if (collection.Count == 0) + continue; + + var snapshot = collection.First().Snapshot; + + // Coalesce the spans if there are a lot of them. + var coalesced = collection.Count > CoalesceDifferenceCount + ? new NormalizedSnapshotSpanCollection(snapshot.GetSpanFromBounds(collection.First().Start, collection.Last().End)) + : collection; + + foreach (var span in coalesced) + tagsChanged(this, new SnapshotSpanEventArgs(span)); + } + + return Task.CompletedTask; + } + } + } +} diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs index 83acf6f63d9d5..6c9a968f71635 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs @@ -2,248 +2,41 @@ // 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 System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Tagging; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Tagging { internal partial class AbstractAsynchronousTaggerProvider { - private sealed partial class Tagger : IAccurateTagger, IDisposable + /// + /// is a thin wrapper we create around the single shared . + /// Clients can request and dispose these at will. Once the last wrapper is disposed, the underlying + /// will finally be disposed as well. + /// + private sealed partial class Tagger : ITagger, IDisposable { - /// - /// If we get more than this many differences, then we just issue it as a single change - /// notification. The number has been completely made up without any data to support it. - /// - /// Internal for testing purposes. - /// - private const int CoalesceDifferenceCount = 10; - - #region Fields that can be accessed from either thread - - private readonly ITextBuffer _subjectBuffer; - - private readonly CancellationTokenSource _cancellationTokenSource; - private readonly TagSource _tagSource; - #endregion - - #region Fields that can only be accessed from the foreground thread - - /// - /// The batch change notifier that we use to throttle update to the UI. - /// - private readonly BatchChangeNotifier _batchChangeNotifier; - - #endregion - - public event EventHandler TagsChanged; - - public Tagger( - IThreadingContext threadingContext, - IAsynchronousOperationListener listener, - IForegroundNotificationService notificationService, - TagSource tagSource, - ITextBuffer subjectBuffer) + public Tagger(TagSource tagSource) { - Contract.ThrowIfNull(subjectBuffer); - - _subjectBuffer = subjectBuffer; - _cancellationTokenSource = new CancellationTokenSource(); - - _batchChangeNotifier = new BatchChangeNotifier( - threadingContext, - subjectBuffer, listener, notificationService, NotifyEditorNow, _cancellationTokenSource.Token); - _tagSource = tagSource; - _tagSource.OnTaggerAdded(this); - _tagSource.TagsChangedForBuffer += OnTagsChangedForBuffer; - _tagSource.Paused += OnPaused; - _tagSource.Resumed += OnResumed; - - // There is a many-to-one relationship between Taggers and TagSources. i.e. one - // tag-source can be used by many Taggers. As such, we may be a tagger that is - // wrapping a tag-source that has already produced tags and had sent out the - // notifications about those tags. - // - // However, we still want to notify the code consuming us that we have tags to - // display. That way, tags can display as soon as possible when someone creates - // a new tagger for a view/buffer. - // - // Note: we have to do this in the future instead of right now because we haven't - // even been returned to the caller for them to hook up to change notifications - // from us. - notificationService.RegisterNotification( - () => - { - if (this.TagsChanged == null) - { - // don't bother reporting tags if no one is listening. - return; - } - - var tags = _tagSource.TryGetTagIntervalTreeForBuffer(_subjectBuffer); - if (tags != null) - { - var collection = new NormalizedSnapshotSpanCollection( - tags.GetSpans(_subjectBuffer.CurrentSnapshot).Select(ts => ts.Span)); - this.NotifyEditorNow(collection); - } - }, - listener.BeginAsyncOperation(GetType().FullName + ".ctor-ReportInitialTags"), - _cancellationTokenSource.Token); - } - - public void Dispose() - { - _cancellationTokenSource.Cancel(); - _cancellationTokenSource.Dispose(); - - _tagSource.Resumed -= OnResumed; - _tagSource.Paused -= OnPaused; - _tagSource.TagsChangedForBuffer -= OnTagsChangedForBuffer; - _tagSource.OnTaggerDisposed(this); - } - - private void OnPaused(object sender, EventArgs e) - => _batchChangeNotifier.Pause(); - - private void OnResumed(object sender, EventArgs e) - => _batchChangeNotifier.Resume(); - - private void OnTagsChangedForBuffer( - ICollection> changes, bool initialTags) - { - _tagSource.AssertIsForeground(); - - // Note: This operation is uncancellable. Once we've been notified here, our cached tags - // in the tag source are new. If we don't update the UI of the editor then we will end - // up in an inconsistent state between us and the editor where we have new tags but the - // editor will never know. - - foreach (var change in changes) - { - if (change.Key != _subjectBuffer) - { - continue; - } - - // Now report them back to the UI on the main thread. - - // We ask to update UI immediately for removed tags - NotifyEditors(change.Value.Removed, initialTags ? TaggerDelay.NearImmediate : _tagSource.RemovedTagNotificationDelay); - NotifyEditors(change.Value.Added, initialTags ? TaggerDelay.NearImmediate : _tagSource.AddedTagNotificationDelay); - } } - private void NotifyEditors(NormalizedSnapshotSpanCollection changes, TaggerDelay delay) + public event EventHandler TagsChanged { - _tagSource.AssertIsForeground(); - - if (changes.Count == 0) - { - // nothing to do. - return; - } - - if (delay == TaggerDelay.NearImmediate) - { - // if delay is immediate, we let notifier knows about the change right away - _batchChangeNotifier.EnqueueChanges(changes); - return; - } - - // if delay is anything more than that, we let notifier knows about the change after given delay - // event notification is only cancellable when disposing of the tagger. - _tagSource.RegisterNotification(() => _batchChangeNotifier.EnqueueChanges(changes), (int)delay.ComputeTimeDelay(_subjectBuffer).TotalMilliseconds, _cancellationTokenSource.Token); - } - - private void NotifyEditorNow(NormalizedSnapshotSpanCollection normalizedSpans) - { - _batchChangeNotifier.AssertIsForeground(); - - using (Logger.LogBlock(FunctionId.Tagger_BatchChangeNotifier_NotifyEditorNow, CancellationToken.None)) - { - if (normalizedSpans.Count == 0) - { - return; - } - - var tagsChanged = this.TagsChanged; - if (tagsChanged == null) - { - return; - } - - normalizedSpans = CoalesceSpans(normalizedSpans); - - // Don't use linq here. It's a hotspot. - foreach (var span in normalizedSpans) - { - tagsChanged(this, new SnapshotSpanEventArgs(span)); - } - } + add => _tagSource.TagsChanged += value; + remove => _tagSource.TagsChanged -= value; } - internal static NormalizedSnapshotSpanCollection CoalesceSpans(NormalizedSnapshotSpanCollection normalizedSpans) - { - var snapshot = normalizedSpans.First().Snapshot; - - // Coalesce the spans if there are a lot of them. - if (normalizedSpans.Count > CoalesceDifferenceCount) - { - // Spans are normalized. So to find the whole span we just go from the - // start of the first span to the end of the last span. - normalizedSpans = new NormalizedSnapshotSpanCollection(snapshot.GetSpanFromBounds( - normalizedSpans.First().Start, - normalizedSpans.Last().End)); - } - - return normalizedSpans; - } + public void Dispose() + => _tagSource.OnTaggerDisposed(this); public IEnumerable> GetTags(NormalizedSnapshotSpanCollection requestedSpans) - => GetTagsWorker(requestedSpans, accurate: false, cancellationToken: CancellationToken.None); - - public IEnumerable> GetAllTags(NormalizedSnapshotSpanCollection requestedSpans, CancellationToken cancellationToken) - => GetTagsWorker(requestedSpans, accurate: true, cancellationToken: cancellationToken); - - private IEnumerable> GetTagsWorker( - NormalizedSnapshotSpanCollection requestedSpans, - bool accurate, - CancellationToken cancellationToken) - { - if (requestedSpans.Count == 0) - { - return SpecializedCollections.EmptyEnumerable>(); - } - - var buffer = requestedSpans.First().Snapshot.TextBuffer; - var tags = accurate - ? _tagSource.GetAccurateTagIntervalTreeForBuffer(buffer, cancellationToken) - : _tagSource.TryGetTagIntervalTreeForBuffer(buffer); - - if (tags == null) - { - return SpecializedCollections.EmptyEnumerable>(); - } - - return tags.GetIntersectingTagSpans(requestedSpans); - } + => _tagSource.GetTags(requestedSpans); } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 5ed3a6f193719..e139089ef2d74 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -27,7 +27,6 @@ namespace Microsoft.CodeAnalysis.Editor.Tagging internal abstract partial class AbstractAsynchronousTaggerProvider : ForegroundThreadAffinitizedObject where TTag : ITag { private readonly object _uniqueKey = new(); - private readonly IForegroundNotificationService _notificationService; protected readonly IAsynchronousOperationListener AsyncListener; @@ -55,15 +54,6 @@ internal abstract partial class AbstractAsynchronousTaggerProvider : Foreg /// protected virtual SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive; - /// - /// Comparer used to determine if two s are the same. This is used by - /// the to determine if a previous set of - /// computed tags and a current set of computed tags should be considered the same or not. - /// If they are the same, then the UI will not be updated. If they are different then - /// the UI will be updated for sets of tags that have been removed or added. - /// - protected virtual IEqualityComparer TagComparer => EqualityComparer.Default; - /// /// Options controlling this tagger. The tagger infrastructure will check this option /// against the buffer it is associated with to see if it should tag or not. @@ -74,15 +64,17 @@ internal abstract partial class AbstractAsynchronousTaggerProvider : Foreg protected virtual IEnumerable> Options => SpecializedCollections.EmptyEnumerable>(); protected virtual IEnumerable> PerLanguageOptions => SpecializedCollections.EmptyEnumerable>(); + protected virtual bool ComputeInitialTagsSynchronously(ITextBuffer subjectBuffer) => false; + /// - /// This controls what delay tagger will use to let editor know about newly inserted tags + /// How long the tagger should wait after hearing about an event before recomputing tags. /// - protected virtual TaggerDelay AddedTagNotificationDelay => TaggerDelay.NearImmediate; + protected abstract TaggerDelay EventChangeDelay { get; } /// - /// This controls what delay tagger will use to let editor know about just deleted tags. + /// This controls what delay tagger will use to let editor know about newly inserted tags /// - protected virtual TaggerDelay RemovedTagNotificationDelay => TaggerDelay.NearImmediate; + protected virtual TaggerDelay AddedTagNotificationDelay => TaggerDelay.NearImmediate; #if DEBUG public readonly string StackTrace; @@ -90,37 +82,42 @@ internal abstract partial class AbstractAsynchronousTaggerProvider : Foreg protected AbstractAsynchronousTaggerProvider( IThreadingContext threadingContext, - IAsynchronousOperationListener asyncListener, - IForegroundNotificationService notificationService) + IAsynchronousOperationListener asyncListener) : base(threadingContext) { AsyncListener = asyncListener; - _notificationService = notificationService; #if DEBUG StackTrace = new StackTrace().ToString(); #endif } - internal IAccurateTagger? CreateTaggerWorker(ITextView textViewOpt, ITextBuffer subjectBuffer) where T : ITag + protected ITagger? CreateTaggerWorker(ITextView textViewOpt, ITextBuffer subjectBuffer) where T : ITag { if (!subjectBuffer.GetFeatureOnOffOption(EditorComponentOnOffOptions.Tagger)) + return null; + + var tagSource = GetOrCreateTagSource(textViewOpt, subjectBuffer); + var tagger = new Tagger(tagSource); + + // If we're not able to convert the tagger we instantiated to the type the caller wants, then make sure we + // dispose of it now. The tagger will have added a ref to the underlying tagsource, and we have to make + // sure we return that to the property starting value. + if (tagger is not ITagger result) { + tagger.Dispose(); return null; } - var tagSource = GetOrCreateTagSource(textViewOpt, subjectBuffer); - return new Tagger(ThreadingContext, AsyncListener, _notificationService, tagSource, subjectBuffer) as IAccurateTagger; + return result; } private TagSource GetOrCreateTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer) { if (!this.TryRetrieveTagSource(textViewOpt, subjectBuffer, out var tagSource)) { - tagSource = new TagSource(textViewOpt, subjectBuffer, this, AsyncListener, _notificationService); - + tagSource = new TagSource(textViewOpt, subjectBuffer, this, AsyncListener); this.StoreTagSource(textViewOpt, subjectBuffer, tagSource); - tagSource.Disposed += (s, e) => this.RemoveTagSource(textViewOpt, subjectBuffer); } return tagSource; @@ -189,7 +186,6 @@ protected virtual IEnumerable GetSpansToTag(ITextView textViewOpt, /// /// Produce tags for the given context. - /// Keep in sync with /// protected virtual async Task ProduceTagsAsync(TaggerContext context) { @@ -202,21 +198,6 @@ await ProduceTagsAsync( } } - /// - /// Produce tags for the given context. - /// Keep in sync with - /// - protected void ProduceTagsSynchronously(TaggerContext context) - { - foreach (var spanToTag in context.SpansToTag) - { - context.CancellationToken.ThrowIfCancellationRequested(); - ProduceTagsSynchronously( - context, spanToTag, - GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan)); - } - } - private static int? GetCaretPosition(SnapshotPoint? caretPosition, SnapshotSpan snapshotSpan) { return caretPosition.HasValue && caretPosition.Value.Snapshot == snapshotSpan.Snapshot @@ -226,38 +207,18 @@ protected void ProduceTagsSynchronously(TaggerContext context) protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition) => Task.CompletedTask; - protected virtual void ProduceTagsSynchronously(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition) - { - // By default we implement the sync version of this by blocking on the async version. - // - // The benefit of this is that all taggers can implicitly be used as IAccurateTaggers - // without any code changes. - // - // However, the drawback is that it means the UI thread might be blocked waiting for - // tasks to be scheduled and run on the threadpool. - // - // Taggers that need to be called accurately should override this method to produce - // results quickly if possible. - ProduceTagsAsync(context, spanToTag, caretPosition).Wait(context.CancellationToken); - } - internal TestAccessor GetTestAccessor() => new(this); - private struct DiffResult + private readonly struct DiffResult { - public NormalizedSnapshotSpanCollection Added { get; } - public NormalizedSnapshotSpanCollection Removed { get; } - - public DiffResult(List added, List removed) - : this(added?.Count == 0 ? null : (IEnumerable?)added, removed?.Count == 0 ? null : (IEnumerable?)removed) - { - } + public readonly NormalizedSnapshotSpanCollection Added; + public readonly NormalizedSnapshotSpanCollection Removed; - public DiffResult(IEnumerable? added, IEnumerable? removed) + public DiffResult(NormalizedSnapshotSpanCollection? added, NormalizedSnapshotSpanCollection? removed) { - Added = added != null ? new NormalizedSnapshotSpanCollection(added) : NormalizedSnapshotSpanCollection.Empty; - Removed = removed != null ? new NormalizedSnapshotSpanCollection(removed) : NormalizedSnapshotSpanCollection.Empty; + Added = added ?? NormalizedSnapshotSpanCollection.Empty; + Removed = removed ?? NormalizedSnapshotSpanCollection.Empty; } public int Count => Added.Count + Removed.Count; diff --git a/src/EditorFeatures/Core/Tagging/AsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AsynchronousTaggerProvider.cs index 692c0cc467912..89463df9b1def 100644 --- a/src/EditorFeatures/Core/Tagging/AsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AsynchronousTaggerProvider.cs @@ -17,18 +17,15 @@ internal abstract class AsynchronousTaggerProvider : AbstractAsynchronousT { protected AsynchronousTaggerProvider( IThreadingContext threadingContext, - IAsynchronousOperationListener asyncListener, - IForegroundNotificationService notificationService) - : base(threadingContext, asyncListener, notificationService) + IAsynchronousOperationListener asyncListener) + : base(threadingContext, asyncListener) { } - public IAccurateTagger CreateTagger(ITextBuffer subjectBuffer) where T : ITag + public ITagger CreateTagger(ITextBuffer subjectBuffer) where T : ITag { if (subjectBuffer == null) - { throw new ArgumentNullException(nameof(subjectBuffer)); - } return this.CreateTaggerWorker(null, subjectBuffer); } diff --git a/src/EditorFeatures/Core/Tagging/AsynchronousViewTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AsynchronousViewTaggerProvider.cs index 0b91d36ce4c74..6c5af89d184fa 100644 --- a/src/EditorFeatures/Core/Tagging/AsynchronousViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AsynchronousViewTaggerProvider.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 Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -13,44 +11,26 @@ namespace Microsoft.CodeAnalysis.Editor.Tagging { - internal abstract class AsynchronousViewTaggerProvider : AbstractAsynchronousTaggerProvider, - IViewTaggerProvider + internal abstract class AsynchronousViewTaggerProvider : AbstractAsynchronousTaggerProvider, IViewTaggerProvider where TTag : ITag { - protected AsynchronousViewTaggerProvider( - IThreadingContext threadingContext, - IAsynchronousOperationListener asyncListener, - IForegroundNotificationService notificationService) - : base(threadingContext, asyncListener, notificationService) - { - } - - // TypeScript still is moving to calling the new constructor that takes an IThreadingContext. Until then, we can fetch one from another service of ours that - // already does. When TypeScript moves calling the new constructor, this should be deleted. - [Obsolete("This overload exists for TypeScript compatibility only and should not be used in new code.")] - protected AsynchronousViewTaggerProvider( - IAsynchronousOperationListener asyncListener, - IForegroundNotificationService notificationService) - : this(((Implementation.ForegroundNotification.ForegroundNotificationService)notificationService).ThreadingContext, asyncListener, notificationService) + protected AsynchronousViewTaggerProvider(IThreadingContext threadingContext, IAsynchronousOperationListener asyncListener) + : base(threadingContext, asyncListener) { } - public IAccurateTagger CreateTagger(ITextView textView, ITextBuffer subjectBuffer) where T : ITag + public ITagger? CreateTagger(ITextView textView, ITextBuffer subjectBuffer) where T : ITag { if (textView == null) - { throw new ArgumentNullException(nameof(subjectBuffer)); - } if (subjectBuffer == null) - { throw new ArgumentNullException(nameof(subjectBuffer)); - } return this.CreateTaggerWorker(textView, subjectBuffer); } - ITagger IViewTaggerProvider.CreateTagger(ITextView textView, ITextBuffer buffer) + ITagger? IViewTaggerProvider.CreateTagger(ITextView textView, ITextBuffer buffer) => CreateTagger(textView, buffer); } } diff --git a/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs b/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs index 5675089f5c344..213693081c93c 100644 --- a/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs +++ b/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs @@ -34,15 +34,5 @@ internal interface ITaggerEventSource /// recompute tags. /// event EventHandler Changed; - - /// - /// The tagger should stop updating the UI with the tags it's produced. - /// - event EventHandler UIUpdatesPaused; - - /// - /// The tagger can start notifying the UI about its tags again. - /// - event EventHandler UIUpdatesResumed; } } diff --git a/src/EditorFeatures/Core/Tagging/TaggerEventArgs.cs b/src/EditorFeatures/Core/Tagging/TaggerEventArgs.cs index 05b985c53759f..1c0b6d42e87e8 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerEventArgs.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerEventArgs.cs @@ -14,16 +14,8 @@ namespace Microsoft.CodeAnalysis.Editor.Tagging /// internal class TaggerEventArgs : EventArgs { - /// - /// They amount of time to wait before the - /// checks for new tags and updates the user interface. - /// - public TaggerDelay Delay { get; } - - /// - /// Creates a new - /// - public TaggerEventArgs(TaggerDelay delay) - => this.Delay = delay; + public TaggerEventArgs() + { + } } } diff --git a/src/EditorFeatures/Core/Tags/ExportImageMonikerServiceAttribute.cs b/src/EditorFeatures/Core/Tags/ExportImageIdServiceAttribute.cs similarity index 64% rename from src/EditorFeatures/Core/Tags/ExportImageMonikerServiceAttribute.cs rename to src/EditorFeatures/Core/Tags/ExportImageIdServiceAttribute.cs index 67b175efac5ce..391dde502f6fe 100644 --- a/src/EditorFeatures/Core/Tags/ExportImageMonikerServiceAttribute.cs +++ b/src/EditorFeatures/Core/Tags/ExportImageIdServiceAttribute.cs @@ -10,20 +10,20 @@ namespace Microsoft.CodeAnalysis.Editor.Tags { /// - /// Use this attribute to declare an implementation + /// Use this attribute to declare an implementation /// so that it can be discovered by the host. /// [MetadataAttribute] [AttributeUsage(AttributeTargets.Class)] - public sealed class ExportImageMonikerServiceAttribute : ExportAttribute + internal sealed class ExportImageIdServiceAttribute : ExportAttribute { /// - /// The name of the . + /// The name of the . /// public string Name { get; set; } - public ExportImageMonikerServiceAttribute() - : base(typeof(IImageMonikerService)) + public ExportImageIdServiceAttribute() + : base(typeof(IImageIdService)) { } } diff --git a/src/EditorFeatures/Core/Tags/IImageMonikerService.cs b/src/EditorFeatures/Core/Tags/IImageIdService.cs similarity index 66% rename from src/EditorFeatures/Core/Tags/IImageMonikerService.cs rename to src/EditorFeatures/Core/Tags/IImageIdService.cs index 4ac3833305d66..191ff3f4eda49 100644 --- a/src/EditorFeatures/Core/Tags/IImageMonikerService.cs +++ b/src/EditorFeatures/Core/Tags/IImageIdService.cs @@ -5,15 +5,15 @@ #nullable disable using System.Collections.Immutable; -using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Core.Imaging; namespace Microsoft.CodeAnalysis.Editor.Tags { /// - /// Extensibility point for hosts to display s for items with Tags. + /// Extensibility point for hosts to display s for items with Tags. /// - public interface IImageMonikerService + internal interface IImageIdService { - bool TryGetImageMoniker(ImmutableArray tags, out ImageMoniker imageMoniker); + bool TryGetImageId(ImmutableArray tags, out ImageId imageId); } } diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf index 6539fa8dcfff7..e0c690efa8669 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Všechny metody Always for clarity - Always for clarity + Vždy kvůli srozumitelnosti @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Vyhněte se nepoužitým parametrům. @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + Nepreferovat this. ani Me. @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Pro místní proměnné, parametry a členy For member access expressions - For member access expressions + Pro výrazy přístupu členů @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Získat nápovědu pro: {0} Get help for '{0}' from Bing - Get help for '{0}' from Bing + Získat nápovědu pro: {0} z Bingu Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Shromažďují se návrhy – {0}. Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Shromažďují se návrhy – čeká se na úplné načtení řešení. @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + V aritmetických operátorech In other binary operators - In other binary operators + V jiných binárních operátorech In other operators - In other operators + V jiných operátorech In relational operators - In relational operators + V relačních operátorech Indentation Size - Indentation Size + Velikost odsazení @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Vložit poslední nový řádek @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Nikdy, pokud jsou nadbytečné New Line - New Line + Nový řádek No - No + Ne Non-public methods - Non-public methods + Neveřejné metody @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Upřednostňovat System.HashCode v GetHashCode Prefer coalesce expression - Prefer coalesce expression + Upřednostňovat sloučený výraz Prefer collection initializer - Prefer collection initializer + Upřednostňovat inicializátor kolekce Prefer compound assignments - Prefer compound assignments + Preferovat složená přiřazení Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Upřednostnit podmíněný výraz před if s přiřazeními Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Upřednostnit podmíněný výraz před if s vrácenými hodnotami Prefer explicit tuple name - Prefer explicit tuple name + Preferovat explicitní název řazené kolekce členů Prefer framework type - Prefer framework type + Upřednostňovat typ architektury Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Preferovat odvozené názvy členů anonymních typů Prefer inferred tuple element names - Prefer inferred tuple element names + Preferovat odvozené názvy elementů řazené kolekce členů Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + U kontrol rovnosti odkazů dávat přednost možnosti is null Prefer null propagation - Prefer null propagation + Upřednostňovat šíření hodnoty null Prefer object initializer - Prefer object initializer + Upřednostňovat inicializátor objektu Prefer predefined type - Prefer predefined type + Upřednostňovat předem definovaný typ Prefer readonly fields - Prefer readonly fields + Preferovat pole s modifikátorem readonly Prefer simplified boolean expressions - Prefer simplified boolean expressions + Upřednostňovat zjednodušené logické výrazy Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Preferovat this. nebo Me. @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Kvalifikovat přístup k události pomocí this nebo Me Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Kvalifikovat přístup k poli pomocí this nebo Me Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Kvalifikovat přístup k metodě pomocí this nebo Me Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Kvalifikovat přístup k vlastnosti pomocí this nebo Me + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Velikost tabulátoru @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Používat tabulátory @@ -457,6 +462,11 @@ Uživatelské typy – rozhraní + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Typy uživatelů – Záznamy @@ -609,7 +619,7 @@ Yes - Yes + Ano @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Preferovat automatické vlastnosti diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf index 7dab8aa18dd1f..4a175429d0fca 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Alle Methoden Always for clarity - Always for clarity + Immer zur besseren Unterscheidung @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Nicht verwendete Parameter vermeiden @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + "this." oder "Me." nicht bevorzugen @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Für lokale Elemente, Parameter und Member For member access expressions - For member access expressions + Für Memberzugriffsausdrücke @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Hilfe zu "{0}" abrufen Get help for '{0}' from Bing - Get help for '{0}' from Bing + Hilfe zu "{0}" von Bing abrufen Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Vorschläge werden gesammelt: "{0}" Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Vorschläge werden gesammelt. Es wird darauf gewartet, dass die Lösung vollständig geladen wird. @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + In arithmetischen Operatoren In other binary operators - In other binary operators + In anderen binären Operatoren In other operators - In other operators + In anderen Operatoren In relational operators - In relational operators + In relationalen Operatoren Indentation Size - Indentation Size + Einzugsgröße @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Abschließenden Zeilenumbruch einfügen @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Nie, wenn nicht erforderlich New Line - New Line + Zeilenumbruch No - No + Nein Non-public methods - Non-public methods + Nicht öffentliche Methoden @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + "System.HashCode" in "GetHashCode" bevorzugen Prefer coalesce expression - Prefer coalesce expression + COALESCE-Ausdruck vorziehen Prefer collection initializer - Prefer collection initializer + Auflistungsinitialisierer vorziehen Prefer compound assignments - Prefer compound assignments + Zusammengesetzte Zuweisungen bevorzugen Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Bei Zuweisungen bedingten Ausdruck gegenüber "if" bevorzugen Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Bei Rückgaben bedingten Ausdruck gegenüber "if" bevorzugen Prefer explicit tuple name - Prefer explicit tuple name + Expliziten Tupelnamen bevorzugen Prefer framework type - Prefer framework type + Frameworktyp vorziehen Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Abgeleitete Membernamen vom anonymen Typ bevorzugen Prefer inferred tuple element names - Prefer inferred tuple element names + Abgeleitete Tupelelementnamen bevorzugen Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + "is null" für Verweisübereinstimmungsprüfungen vorziehen Prefer null propagation - Prefer null propagation + NULL-Weitergabe vorziehen Prefer object initializer - Prefer object initializer + Objektinitialisierer vorziehen Prefer predefined type - Prefer predefined type + Vordefinierten Typ vorziehen Prefer readonly fields - Prefer readonly fields + readonly-Felder bevorzugen Prefer simplified boolean expressions - Prefer simplified boolean expressions + Vereinfachte boolesche Ausdrücke bevorzugen Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + "this." oder "Me." bevorzugen @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Ereigniszugriff mit "this" oder "Me" qualifizieren Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Feldzugriff mit "this" oder "Me" qualifizieren Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Methodenzugriff mit "this" oder "Me" qualifizieren Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Eigenschaftenzugriff mit "this" oder "Me" qualifizieren + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Tabstoppgröße @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Tabstopps verwenden @@ -457,6 +462,11 @@ Benutzertypen - Schnittstellen + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Benutzertypen – Datensätze @@ -609,7 +619,7 @@ Yes - Yes + Ja @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Automatische Eigenschaften bevorzugen diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf index 91f0299c0d109..cee1643c80cea 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Todos los métodos Always for clarity - Always for clarity + Siempre por claridad @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Evitar parámetros sin usar @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + No preferir "this." ni "Me." @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Para variables locales, parámetros y miembros For member access expressions - For member access expressions + Para expresiones de acceso a miembro @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Obtener ayuda para "{0}" Get help for '{0}' from Bing - Get help for '{0}' from Bing + Obtener ayuda para "{0}" en Bing Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Recopilando sugerencias: "{0}" Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Recopilando sugerencias: esperando a que la solución se cargue por completo @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + En los operadores aritméticos In other binary operators - In other binary operators + En otros operadores binarios In other operators - In other operators + En otros operadores In relational operators - In relational operators + En los operadores relacionales Indentation Size - Indentation Size + Tamaño de sangría @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Insertar nueva línea final @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Nunca si es innecesario New Line - New Line + Nueva línea No - No + No Non-public methods - Non-public methods + Miembros no públicos @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Preferir "System.HashCode" en "GetHashCode" Prefer coalesce expression - Prefer coalesce expression + Preferir expresión de fusión Prefer collection initializer - Prefer collection initializer + Preferir inicializador de colección Prefer compound assignments - Prefer compound assignments + Preferir asignaciones compuestas Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Preferir expresión condicional sobre "if" con asignaciones Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Preferir expresión condicional sobre "if" con devoluciones Prefer explicit tuple name - Prefer explicit tuple name + Preferir nombre de tupla explícito Prefer framework type - Prefer framework type + Preferir tipo de marco de trabajo Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Preferir nombres de miembro de tipo anónimo inferidos Prefer inferred tuple element names - Prefer inferred tuple element names + Preferir nombres de elementos de tupla inferidos Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Preferir “is null” para comprobaciones de igualdad de referencias Prefer null propagation - Prefer null propagation + Preferir propagación nula Prefer object initializer - Prefer object initializer + Preferir inicializador de objeto Prefer predefined type - Prefer predefined type + Preferir tipo predefinido Prefer readonly fields - Prefer readonly fields + Preferir campos de solo lectura Prefer simplified boolean expressions - Prefer simplified boolean expressions + Preferir expresiones booleanas simplificadas Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Preferir "this." o "Me." @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Calificar acceso a evento con "this" o "Me" Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Calificar acceso a campo con "this" o "Me" Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Calificar acceso a método con "this" o "Me" Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Calificar acceso a propiedad con "this" o "Me" + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Tamaño de tabulación @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Usar tabulaciones @@ -457,6 +462,11 @@ Tipos de usuario: interfaces + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Tipos de usuario: registros @@ -609,7 +619,7 @@ Yes - Yes + @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Preferir propiedades automáticas diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf index 734ca3ccf60db..318f9a9c49eb1 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Toutes les méthodes Always for clarity - Always for clarity + Toujours pour plus de clarté @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Éviter les paramètres inutilisés @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + Ne pas préférer 'this.' ou 'Me.' @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Pour les variables locales, les paramètres et les membres For member access expressions - For member access expressions + Pour les expressions d'accès de membre @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Obtenir de l'aide pour '{0}' Get help for '{0}' from Bing - Get help for '{0}' from Bing + Obtenir de l'aide pour '{0}' à partir de Bing Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Collecte des suggestions - '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Collecte des suggestions - Attente du chargement complet de la solution @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + Dans les opérateurs arithmétiques In other binary operators - In other binary operators + Dans d'autres opérateurs binaires In other operators - In other operators + Dans les autres opérateurs In relational operators - In relational operators + Dans les opérateurs relationnels Indentation Size - Indentation Size + Taille de la mise en retrait @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Insérer une nouvelle ligne finale @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Jamais si ce n'est pas nécessaire New Line - New Line + Nouvelle ligne No - No + Non Non-public methods - Non-public methods + Méthodes non publiques @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Préférer 'System.HashCode' dans 'GetHashCode' Prefer coalesce expression - Prefer coalesce expression + Préférer l'expression coalesce Prefer collection initializer - Prefer collection initializer + Préférer l'initialiseur de collection Prefer compound assignments - Prefer compound assignments + Préférer les affectations composées Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Préférer une expression conditionnelle à 'if' avec des affectations Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Préférer une expression conditionnelle à 'if' avec des retours Prefer explicit tuple name - Prefer explicit tuple name + Préférer un nom de tuple explicite Prefer framework type - Prefer framework type + Préférer le type d'infrastructure Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Préférer les noms de membres de type anonyme déduits Prefer inferred tuple element names - Prefer inferred tuple element names + Préférer les noms d'éléments de tuple déduits Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Préférer 'is nul' pour les vérifications d'égalité de référence Prefer null propagation - Prefer null propagation + Préférer la propagation nulle Prefer object initializer - Prefer object initializer + Préférer l'initialiseur d'objet Prefer predefined type - Prefer predefined type + Préférer le type prédéfini Prefer readonly fields - Prefer readonly fields + Préférer les champs readonly Prefer simplified boolean expressions - Prefer simplified boolean expressions + Préférer les expressions booléennes simplifiées Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Préférer 'this.' ou 'Me.' @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Qualifier l'accès à l'événement avec 'this' ou 'Me' Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Qualifier l'accès au champ avec 'this' ou 'Me' Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Qualifier l'accès à la méthode avec 'this' ou 'Me' Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Qualifier l'accès à la propriété avec 'this' ou 'Me' + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Taille des tabulations @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Utiliser les tabulations @@ -457,6 +462,11 @@ Types d'utilisateurs - Interfaces + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Types d'utilisateur - Enregistrements @@ -609,7 +619,7 @@ Yes - Yes + Oui @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Préférer les propriétés automatiques diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf index ca26b60ca9ecd..ee5d43b05036d 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Tutti i metodi Always for clarity - Always for clarity + Sempre per chiarezza @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Evita i parametri inutilizzati @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + Non preferire 'this.' o 'Me.' @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Per variabili locali, parametri e membri For member access expressions - For member access expressions + Per espressioni di accesso ai membri @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Visualizza la Guida per '{0}' Get help for '{0}' from Bing - Get help for '{0}' from Bing + Visualizza la Guida per '{0}' disponibile in Bing Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Raccolta dei suggerimenti - '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Raccolta dei suggerimenti - In attesa del completamento del caricamento della soluzione @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + In operatori aritmetici In other binary operators - In other binary operators + In altri operatori binari In other operators - In other operators + In altri operatori In relational operators - In relational operators + In operatori relazionali Indentation Size - Indentation Size + Dimensione rientro @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Inserisci carattere di nuova riga finale @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Mai se non necessario New Line - New Line + Nuova riga No - No + No Non-public methods - Non-public methods + Metodi non pubblici @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Preferisci 'System.HashCode' in 'GetHashCode' Prefer coalesce expression - Prefer coalesce expression + Preferisci espressione COALESCE Prefer collection initializer - Prefer collection initializer + Preferisci inizializzatore di insieme Prefer compound assignments - Prefer compound assignments + Preferisci assegnazioni composte Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Preferisci l'espressione condizionale a 'if' con assegnazioni Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Preferisci l'espressione condizionale a 'if' con valori restituiti Prefer explicit tuple name - Prefer explicit tuple name + Preferisci nome di tupla esplicito Prefer framework type - Prefer framework type + Preferisci tipo di framework Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Preferisci nomi di membro di tipo anonimo dedotti Prefer inferred tuple element names - Prefer inferred tuple element names + Preferisci nomi di elemento di tupla dedotti Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Preferisci 'is null' per i controlli di uguaglianza dei riferimenti Prefer null propagation - Prefer null propagation + Preferisci propagazione di valori Null Prefer object initializer - Prefer object initializer + Preferisci inizializzatore di oggetto Prefer predefined type - Prefer predefined type + Preferisci tipo predefinito Prefer readonly fields - Prefer readonly fields + Preferisci campi readonly Prefer simplified boolean expressions - Prefer simplified boolean expressions + Preferisci espressioni booleane semplificate Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Preferisci 'this.' o 'Me.' @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Qualifica l'accesso agli eventi con 'this' o 'Me' Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Qualifica l'accesso ai campi con 'this' o 'Me' Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Qualifica l'accesso ai metodi con 'this' o 'Me' Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Qualifica l'accesso alle proprietà con 'this' o 'Me' + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Dimensione tabulazione @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Usa tabulazioni @@ -457,6 +462,11 @@ Tipi utente - Interfacce + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Tipi utente - Record @@ -609,7 +619,7 @@ Yes - Yes + @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Preferisci proprietà automatiche diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf index 5508feabfa76b..a487800ae2a5c 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + すべてのメソッド Always for clarity - Always for clarity + わかりやすくするために常に @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + 使用されないパラメーターを指定しないでください @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + 'this.' または 'Me' を優先しない @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + ローカル、パラメーター、メンバーの場合 For member access expressions - For member access expressions + メンバー アクセス式の場合 @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + '{0}' のヘルプの表示 Get help for '{0}' from Bing - Get help for '{0}' from Bing + Bing から '{0}' のヘルプを表示します Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + 提案を収集しています - '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + 提案を収集しています - ソリューションが完全に読み込まれるのを待機しています @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + 算術演算子内で In other binary operators - In other binary operators + その他のバイナリ演算子内で In other operators - In other operators + その他の演算子内で In relational operators - In relational operators + 関係演算子内で Indentation Size - Indentation Size + インデントのサイズ @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + 最後の改行を挿入する @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + 不必要なら保持しない New Line - New Line + 改行 No - No + いいえ Non-public methods - Non-public methods + パブリックでないメソッド @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + 'GetHashCode' の 'System.HashCode' を優先する Prefer coalesce expression - Prefer coalesce expression + 合体式を優先する Prefer collection initializer - Prefer collection initializer + コレクション初期化子を優先する Prefer compound assignments - Prefer compound assignments + 複合代入を優先 Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + 代入のある 'if' より条件式を優先する Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + 戻り値のある 'if' より条件式を優先する Prefer explicit tuple name - Prefer explicit tuple name + 明示的なタプル名を優先します Prefer framework type - Prefer framework type + フレームワークの型を優先する Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + 推定された匿名型のメンバー名を優先します Prefer inferred tuple element names - Prefer inferred tuple element names + 推定されたタプル要素の名前を優先します Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + 参照の等値性のチェックには 'is null' を優先する Prefer null propagation - Prefer null propagation + null 値の反映を優先する Prefer object initializer - Prefer object initializer + オブジェクト初期化子を優先する Prefer predefined type - Prefer predefined type + 定義済みの型を優先する Prefer readonly fields - Prefer readonly fields + readonly フィールドを優先する Prefer simplified boolean expressions - Prefer simplified boolean expressions + 単純なブール式を優先する Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + 'this.' または 'Me' を優先する @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + イベント アクセスを 'this' または 'Me' で修飾する Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + フィールド アクセスを 'this' または 'Me' で修飾する Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + メソッド アクセスを 'this' または 'Me' で修飾する Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + プロパティ アクセスを 'this' または 'Me' で修飾する + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + タブのサイズ @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + タブの使用 @@ -457,6 +462,11 @@ ユーザー タイプ - インターフェイス + + User Types - Record Structs + User Types - Record Structs + + User Types - Records ユーザー タイプ - レコード @@ -609,7 +619,7 @@ Yes - Yes + はい @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + 自動プロパティを優先する diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf index 295e6f802a05b..cc8ab6bc55e9d 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + 모든 메서드 Always for clarity - Always for clarity + 명확하게 하기 위해 항상 @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + 사용되지 않는 매개 변수를 사용하지 마세요. @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + 'this.' 또는 'Me.'를 기본으로 사용하지 마세요. @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + 로컬, 매개 변수 및 멤버의 경우 For member access expressions - For member access expressions + 멤버 액세스 식의 경우 @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + '{0}'에 대한 도움 받기 Get help for '{0}' from Bing - Get help for '{0}' from Bing + Bing에서 '{0}'에 대한 도움 받기 Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + 제안을 수집하는 중 - '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + 제안을 수집하는 중 - 솔루션이 완전히 로드될 때까지 기다리는 중 @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + 산술 연산자 In other binary operators - In other binary operators + 기타 이항 연산자 In other operators - In other operators + 기타 연산자 In relational operators - In relational operators + 관계 연산자 Indentation Size - Indentation Size + 들여쓰기 크기 @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + 최종 줄 바꿈 삽입 @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + 필요한 경우 사용 안 함 New Line - New Line + 줄 바꿈 No - No + 아니요 Non-public methods - Non-public methods + public이 아닌 메서드 @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + 'GetHashCode'에서 'System.HashCode' 선호 Prefer coalesce expression - Prefer coalesce expression + coalesce 식 사용 Prefer collection initializer - Prefer collection initializer + 컬렉션 이니셜라이저 사용 Prefer compound assignments - Prefer compound assignments + 복합 대입 선호 Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + 할당이 포함된 'if'보다 조건식 선호 Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + 반환이 포함된 'if'보다 조건식 선호 Prefer explicit tuple name - Prefer explicit tuple name + 명시적 튜플 이름 기본 사용 Prefer framework type - Prefer framework type + 프레임워크 형식 사용 Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + 유추된 무명 형식 멤버 이름 선호 Prefer inferred tuple element names - Prefer inferred tuple element names + 유추된 튜플 요소 이름 선호 Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + 참조 같음 검사에 대해 'is null' 선호 Prefer null propagation - Prefer null propagation + null 전파 사용 Prefer object initializer - Prefer object initializer + 개체 이니셜라이저 사용 Prefer predefined type - Prefer predefined type + 미리 정의된 형식 사용 Prefer readonly fields - Prefer readonly fields + 읽기 전용 필드 선호 Prefer simplified boolean expressions - Prefer simplified boolean expressions + 간단한 부울 식을 기본으로 사용 Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + 'this.' 또는 'Me.'를 기본으로 사용하세요. @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + 'this' 또는 'Me'를 사용하여 이벤트 액세스를 한정합니다. Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + 'this' 또는 'Me'를 사용하여 필드 액세스를 한정합니다. Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + 'this' 또는 'Me'를 사용하여 메서드 액세스를 한정합니다. Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + 'this' 또는 'Me'를 사용하여 속성 액세스를 한정합니다. + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + 탭 크기 @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + 탭 사용 @@ -457,6 +462,11 @@ 사용자 형식 - 인터페이스 + + User Types - Record Structs + User Types - Record Structs + + User Types - Records 사용자 유형 - 레코드 @@ -609,7 +619,7 @@ Yes - Yes + @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + 자동 속성 선호 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf index 823fd239c3d54..fdd6b92fd651f 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Wszystkie metody Always for clarity - Always for clarity + Zawsze w celu zachowania jednoznaczności @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Unikaj nieużywanych parametrów @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + Nie preferuj zapisu „this.” lub „me.” @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Dla zmiennych lokalnych, parametrów i składowych For member access expressions - For member access expressions + Dla wyrażenia dostępu do składowych @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Uzyskaj pomoc dla „{0}” Get help for '{0}' from Bing - Get help for '{0}' from Bing + Uzyskaj pomoc dla „{0}” z wyszukiwarki Bing Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Zbieranie sugestii — „{0}” Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Zbieranie sugestii — oczekiwanie na pełne załadowanie rozwiązania @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + W operatorach arytmetycznych In other binary operators - In other binary operators + W innych operatorach binarnych In other operators - In other operators + W innych operatorach In relational operators - In relational operators + W operatorach relacyjnych Indentation Size - Indentation Size + Rozmiar wcięcia @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Wstaw końcowy znak nowego wiersza @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Nigdy, jeśli niepotrzebne New Line - New Line + Nowy wiersz No - No + Nie Non-public methods - Non-public methods + Metody niepubliczne @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Preferuj element „System.HashCode” w metodzie „GetHashCode” Prefer coalesce expression - Prefer coalesce expression + Preferuj wyrażenie łączące Prefer collection initializer - Prefer collection initializer + Preferuj inicjator kolekcji Prefer compound assignments - Prefer compound assignments + Preferuj przypisania złożone Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Preferuj wyrażenia warunkowe przed instrukcjami „if” z przypisaniami Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Preferuj wyrażenia warunkowe przed instrukcjami „if” ze zwracaniem Prefer explicit tuple name - Prefer explicit tuple name + Preferuj jawną nazwę krotki Prefer framework type - Prefer framework type + Preferuj typ struktury Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Preferuj wywnioskowane nazwy anonimowych składowych typu Prefer inferred tuple element names - Prefer inferred tuple element names + Preferuj wywnioskowane nazwy elementów krotki Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Preferuj wyrażenie „is null” w przypadku sprawdzeń odwołań pod kątem równości Prefer null propagation - Prefer null propagation + Preferuj propagację wartości null Prefer object initializer - Prefer object initializer + Preferuj inicjator obiektu Prefer predefined type - Prefer predefined type + Preferuj wstępnie zdefiniowany typ Prefer readonly fields - Prefer readonly fields + Preferuj pola tylko do odczytu Prefer simplified boolean expressions - Prefer simplified boolean expressions + Preferuj uproszczone wyrażenia logiczne Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Preferuj zapis „this.” lub „me.” @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Kwalifikuj dostęp do zdarzenia przy użyciu zapisu „this.” lub „me.” Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Kwalifikuj dostęp do pola przy użyciu zapisu „this.” lub „me.” Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Kwalifikuj dostęp do metody przy użyciu zapisu „this.” lub „me.” Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Kwalifikuj dostęp do właściwości przy użyciu zapisu „this.” lub „me.” + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Rozmiar tabulacji @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Użyj tabulatorów @@ -457,6 +462,11 @@ Typy użytkownika — interfejsy + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Typy użytkownika — rekordy @@ -609,7 +619,7 @@ Yes - Yes + Tak @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Preferuj właściwości automatyczne diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf index 00eb77d4bcf95..f212f8ee8d7ed 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Todos os métodos Always for clarity - Always for clarity + Sempre para esclarecimento @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Evitar parâmetros não utilizados @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + Não preferir 'this.' nem 'Me'. @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Para locais, parâmetros e membros For member access expressions - For member access expressions + Para expressões de acesso de membro @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Obter ajuda para '{0}' Get help for '{0}' from Bing - Get help for '{0}' from Bing + Obter ajuda para o '{0}' do Bing Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Obtendo Sugestões – '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Obtendo Sugestões – aguardando a solução carregar totalmente @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + Em operadores aritméticos In other binary operators - In other binary operators + Em outros operadores binários In other operators - In other operators + Em outros operadores In relational operators - In relational operators + Em operadores relacionais Indentation Size - Indentation Size + Tamanho do Recuo @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Inserir Nova Linha Final @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Nunca se desnecessário New Line - New Line + Nova Linha No - No + Não Non-public methods - Non-public methods + Métodos não públicos @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Prefira 'System.HashCode' em 'GetHashCode' Prefer coalesce expression - Prefer coalesce expression + Preferir a expressão de união Prefer collection initializer - Prefer collection initializer + Preferir o inicializador de coleção Prefer compound assignments - Prefer compound assignments + Preferir atribuições de compostos Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Preferir expressão condicional em vez de 'if' com atribuições Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Preferir expressão condicional em vez de 'if' com retornos Prefer explicit tuple name - Prefer explicit tuple name + Preferir nome de tupla explícito Prefer framework type - Prefer framework type + Preferir tipo de estrutura Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Prefira usar nomes de membro inferidos do tipo anônimo Prefer inferred tuple element names - Prefer inferred tuple element names + Preferir usar nomes de elementos inferidos de tupla Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Preferir 'is null' para as verificações de igualdade de referência Prefer null propagation - Prefer null propagation + Preferir tratamento simplificado de nulo Prefer object initializer - Prefer object initializer + Preferir inicializador de objeto Prefer predefined type - Prefer predefined type + Preferir tipo predefinido Prefer readonly fields - Prefer readonly fields + Preferir campos readonly Prefer simplified boolean expressions - Prefer simplified boolean expressions + Preferir expressões boolianas simplificadas Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Preferir 'this.' ou 'Me.' @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Qualificar o acesso de evento com 'this' ou 'Me' Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Qualificar o acesso de campo com 'this' ou 'Me' Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Qualificar o acesso de método com 'this' ou 'Me' Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Qualificar o acesso de propriedade com 'this' ou 'Me' + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Tamanho da Tabulação @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Usar Tabulações @@ -457,6 +462,11 @@ Tipos de Usuário - Interfaces + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Tipos de Usuário – Registros @@ -609,7 +619,7 @@ Yes - Yes + Sim @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Preferir propriedades automáticas diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf index e797ae4690a91..1ce9d078d978a 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Все методы Always for clarity - Always for clarity + Всегда использовать для ясности @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Избегайте неиспользуемых параметров. @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + Не предпочитать "this." или "Me". @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Для локальных переменных, параметров и элементов For member access expressions - For member access expressions + Для выражений доступа к элементам @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + Получить справку для "{0}" Get help for '{0}' from Bing - Get help for '{0}' from Bing + Получить справку для "{0}" из системы Bing Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Сбор предложений — "{0}" Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Сбор предложений — ожидается полная загрузка решения @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + В арифметических операторах In other binary operators - In other binary operators + В других бинарных операторах In other operators - In other operators + В других операторах In relational operators - In relational operators + В реляционных операторах Indentation Size - Indentation Size + Размер отступа @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Вставить заключительную новую строку @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Никогда, если не требуется New Line - New Line + Новая строка No - No + Нет Non-public methods - Non-public methods + Методы, не являющиеся открытыми @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + Предпочитать "System.HashCode" в "GetHashCode" Prefer coalesce expression - Prefer coalesce expression + Предпочитать объединенное выражение Prefer collection initializer - Prefer collection initializer + Предпочитать инициализатор коллекции Prefer compound assignments - Prefer compound assignments + Предпочитать составные назначения Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Предпочитать условное выражение оператору if в назначениях Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Предпочитать условное выражение оператору if в операторах return Prefer explicit tuple name - Prefer explicit tuple name + Предпочитать явное имя кортежа Prefer framework type - Prefer framework type + Предпочитать тип платформы Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Предпочитать выводимые имена членов анонимного типа Prefer inferred tuple element names - Prefer inferred tuple element names + Предпочитать выводимые имена элементов кортежа Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Использовать "is null" вместо проверки ссылок на равенство. Prefer null propagation - Prefer null propagation + Предпочитать распространение значений NULL Prefer object initializer - Prefer object initializer + Предпочитать инициализатор объекта Prefer predefined type - Prefer predefined type + Предпочитать предопределенный тип Prefer readonly fields - Prefer readonly fields + Предпочитать поля только для чтения Prefer simplified boolean expressions - Prefer simplified boolean expressions + Предпочитать упрощенные логические выражения Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + Предпочитать "this." или "Me". @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Квалифицировать доступ к событию с помощью "this" или "Me" Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Квалифицировать доступ к полю с помощью "this" или "Me" Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Квалифицировать доступ к методу с помощью "this" или "Me" Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Квалифицировать доступ к свойству с помощью "this" или "Me" + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Размер интервала табуляции @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Использовать вкладки @@ -457,6 +462,11 @@ Пользовательские типы — интерфейсы + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Пользовательские типы — записи @@ -609,7 +619,7 @@ Yes - Yes + Да @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Предпочитать автосвойства diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf index 42dd9347671f5..9ae4535f0adbb 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + Tüm yöntemler Always for clarity - Always for clarity + Açıklık sağlamak için her zaman @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + Kullanılmayan parametreleri engelle @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + 'this.' veya 'Me.' tercih etme @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + Yerel öğeler, parametreler ve üyeler için For member access expressions - For member access expressions + Üye erişimi ifadeleri için @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + '{0}' için yardım alın Get help for '{0}' from Bing - Get help for '{0}' from Bing + '{0}' için Bing'den yardım alın Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + Öneriler Toplanıyor - '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + Öneriler Toplanıyor - Çözümün tam olarak yüklenmesi bekleniyor @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + Aritmetik operatörlerde In other binary operators - In other binary operators + Diğer ikili operatörlerde In other operators - In other operators + Diğer işleçlerde In relational operators - In relational operators + İlişkisel operatörlerde Indentation Size - Indentation Size + Girintileme Boyutu @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + Son Yeni Satır Ekle @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + Gereksizse hiçbir zaman New Line - New Line + Yeni Satır No - No + Hayır Non-public methods - Non-public methods + Ortak olmayan yöntemler @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + 'GetHashCode' içinde 'System.HashCode' tercih et Prefer coalesce expression - Prefer coalesce expression + Birleştirme ifadesini tercih et Prefer collection initializer - Prefer collection initializer + Koleksiyon başlatıcısını tercih et Prefer compound assignments - Prefer compound assignments + Bileşik atamaları tercih et Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + Atamalarda 'if' yerine koşullu deyim tercih et Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + Dönüşlerde 'if' yerine koşullu deyim tercih et Prefer explicit tuple name - Prefer explicit tuple name + Açık demet adını tercih et Prefer framework type - Prefer framework type + Çerçeve türünü tercih et Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + Gösterilen anonim tip üye adlarını tercih et Prefer inferred tuple element names - Prefer inferred tuple element names + Gösterilen demet öğesi adlarını tercih et Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + Başvuru eşitliği denetimleri için 'is null'ı tercih et Prefer null propagation - Prefer null propagation + Null yaymayı tercih et Prefer object initializer - Prefer object initializer + Nesne başlatıcısını tercih et Prefer predefined type - Prefer predefined type + Önceden tanımlanmış türü tercih et Prefer readonly fields - Prefer readonly fields + Saltokunur alanları tercih et Prefer simplified boolean expressions - Prefer simplified boolean expressions + Basitleştirilmiş boolean ifadelerini tercih edin Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + 'this.' veya 'Me.' tercih et @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + Olay erişimini 'this' veya 'Me' ile nitele Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + Alan erişimini 'this' veya 'Me' ile nitele Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + Metot erişimini 'this' veya 'Me' ile nitele Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + Özellik erişimini 'this' veya 'Me' ile nitele + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + Sekme Boyutu @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + Sekmeleri Kullan @@ -457,6 +462,11 @@ Kullanıcı Türleri - Arabirimler + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Kullanıcı Türleri - Kayıtlar @@ -609,7 +619,7 @@ Yes - Yes + Evet @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + Otomatik özellikleri tercih et diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf index 01cd292694cf0..8196c9e2bdd2f 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + 所有方法 Always for clarity - Always for clarity + 为始终保持清楚起见 @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + 避免未使用的参数 @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + 不首选 "this." 或 "Me." @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + 针对局部变量、参数和成员 For member access expressions - For member access expressions + 针对成员访问表达式 @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + 获取有关“{0}”的帮助 Get help for '{0}' from Bing - Get help for '{0}' from Bing + 从必应获取有关“{0}”的帮助 Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + 正在收集建议 -“{0}” Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + 正在收集建议 - 正在等待解决方案完全加载 @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + 在算术运算符中 In other binary operators - In other binary operators + 在其他二进制运算符中 In other operators - In other operators + 在其他运算符中 In relational operators - In relational operators + 在关系运算符中 Indentation Size - Indentation Size + 缩进大小 @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + 在最后插入一个新行 @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + 从不(若无必要) New Line - New Line + 新行 No - No + Non-public methods - Non-public methods + 非公共成员 @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + 在 "GetHashCode" 中首选 "System.HashCode" Prefer coalesce expression - Prefer coalesce expression + 首选联合表达式 Prefer collection initializer - Prefer collection initializer + 首选集合初始值设定项 Prefer compound assignments - Prefer compound assignments + 首选复合赋值 Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + 首选条件表达式而非赋值的“if” Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + 首选条件表达式而非带有返回结果的“if” Prefer explicit tuple name - Prefer explicit tuple name + 首选显式元组名称 Prefer framework type - Prefer framework type + 首选框架类型 Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + 首选推断匿名类型成员名称 Prefer inferred tuple element names - Prefer inferred tuple element names + 首选推断元组元素名称 Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + 引用相等检查偏好 “is null” Prefer null propagation - Prefer null propagation + 首选 null 传播 Prefer object initializer - Prefer object initializer + 首选对象初始值设定项 Prefer predefined type - Prefer predefined type + 首选预定义类型 Prefer readonly fields - Prefer readonly fields + 首选只读字段 Prefer simplified boolean expressions - Prefer simplified boolean expressions + 首选简化的布尔表达式 Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + 首选 "this." 或 "Me." @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + 使用 "this" 限定事件访问 Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + 使用 "this" 或 "Me" 限定字段访问 Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + 使用 "this" 或 "Me" 限定方法访问 Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + 使用 "this" 或 "Me" 限定属性访问 + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + 制表符大小 @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + 使用制表符 @@ -457,6 +462,11 @@ 用户类型 - 接口 + + User Types - Record Structs + User Types - Record Structs + + User Types - Records 用户类型 - 记录 @@ -609,7 +619,7 @@ Yes - Yes + @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + 首选自动属性 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf index 285e7d9493df4..04c15657130df 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf @@ -1,15 +1,15 @@ - + All methods - All methods + 全部方法 Always for clarity - Always for clarity + 一律使用以明確表示 @@ -24,7 +24,7 @@ Avoid unused parameters - Avoid unused parameters + 避免未使用的參數 @@ -44,7 +44,7 @@ Do not prefer 'this.' or 'Me.' - Do not prefer 'this.' or 'Me.' + 不建議使用 'this.' 或 'Me.' @@ -79,12 +79,12 @@ For locals, parameters and members - For locals, parameters and members + 針對區域變數、參數及成員 For member access expressions - For member access expressions + 針對成員存取運算式 @@ -94,22 +94,22 @@ Get help for '{0}' - Get help for '{0}' + 取得 '{0}' 的說明 Get help for '{0}' from Bing - Get help for '{0}' from Bing + 從 Bing 取得 '{0}' 的說明 Gathering Suggestions - '{0}' - Gathering Suggestions - '{0}' + 正在蒐集建議 - '{0}' Gathering Suggestions - Waiting for the solution to fully load - Gathering Suggestions - Waiting for the solution to fully load + 正在蒐集建議 - 正在等候解決方案完整載入 @@ -119,27 +119,27 @@ In arithmetic operators - In arithmetic operators + 在算術運算子中 In other binary operators - In other binary operators + 在其他二元運算子中 In other operators - In other operators + 其他運算子中 In relational operators - In relational operators + 在關係運算子中 Indentation Size - Indentation Size + 縮排大小 @@ -149,7 +149,7 @@ Insert Final Newline - Insert Final Newline + 插入最後一個新行 @@ -174,22 +174,22 @@ Never if unnecessary - Never if unnecessary + 不需要時一律不要 New Line - New Line + 新行 No - No + Non-public methods - Non-public methods + 非公用方法 @@ -204,87 +204,87 @@ Prefer 'System.HashCode' in 'GetHashCode' - Prefer 'System.HashCode' in 'GetHashCode' + 建議在 'GetHashCode' 中使用 'System.HashCode' Prefer coalesce expression - Prefer coalesce expression + 偏好聯合運算式 Prefer collection initializer - Prefer collection initializer + 偏好集合初始設定式 Prefer compound assignments - Prefer compound assignments + 優先使用複合指派 Prefer conditional expression over 'if' with assignments - Prefer conditional expression over 'if' with assignments + 建議優先使用條件運算式 (優先於具指派的 'if') Prefer conditional expression over 'if' with returns - Prefer conditional expression over 'if' with returns + 建議優先使用條件運算式 (優先於具傳回的 'if') Prefer explicit tuple name - Prefer explicit tuple name + 建議使用明確的元組名稱 Prefer framework type - Prefer framework type + 偏好架構類型 Prefer inferred anonymous type member names - Prefer inferred anonymous type member names + 優先使用推斷的匿名型別成員名稱 Prefer inferred tuple element names - Prefer inferred tuple element names + 優先使用推斷的元組元素名稱 Prefer 'is null' for reference equality checks - Prefer 'is null' for reference equality checks + 參考相等檢查最好使用 'is null' Prefer null propagation - Prefer null propagation + 偏好 null 傳播 Prefer object initializer - Prefer object initializer + 偏好物件初始設定式 Prefer predefined type - Prefer predefined type + 偏好預先定義的類型 Prefer readonly fields - Prefer readonly fields + 優先使用唯讀欄位 Prefer simplified boolean expressions - Prefer simplified boolean expressions + 建議使用簡易布林運算式 Prefer 'this.' or 'Me.' - Prefer 'this.' or 'Me.' + 建議使用 'this.' 或 'Me.' @@ -299,22 +299,27 @@ Qualify event access with 'this' or 'Me' - Qualify event access with 'this' or 'Me' + 以 'this' 或 'Me' 限定事件存取 Qualify field access with 'this' or 'Me' - Qualify field access with 'this' or 'Me' + 以 'this' 或 'Me' 限定欄位存取 Qualify method access with 'this' or 'Me' - Qualify method access with 'this' or 'Me' + 以 'this' 或 'Me' 限定方法存取 Qualify property access with 'this' or 'Me' - Qualify property access with 'this' or 'Me' + 以 'this' 或 'Me' 限定屬性存取 + + + + Reassigned variable + Reassigned variable @@ -349,7 +354,7 @@ Tab Size - Tab Size + 索引標籤大小 @@ -379,7 +384,7 @@ Use Tabs - Use Tabs + 使用索引標籤 @@ -457,6 +462,11 @@ 使用者類型 - 介面 + + User Types - Record Structs + User Types - Record Structs + + User Types - Records 使用者類型 - 記錄 @@ -609,7 +619,7 @@ Yes - Yes + @@ -834,7 +844,7 @@ Prefer auto properties - Prefer auto properties + 建議使用自動屬性 diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionTest.cs index a546bc75d3ca0..e82a257c1e6ae 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionTest.cs @@ -176,15 +176,18 @@ public TestPickMembersService( #pragma warning restore RS0034 // Exported parts should be marked with 'ImportingConstructorAttribute' public PickMembersResult PickMembers( - string title, ImmutableArray members, - ImmutableArray options) + string title, + ImmutableArray members, + ImmutableArray options, + bool selectAll) { OptionsCallback?.Invoke(options); return new PickMembersResult( MemberNames.IsDefault ? members : MemberNames.SelectAsArray(n => members.Single(m => m.Name == n)), - options); + options, + selectAll); } } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs index 8e3a92b5a5ab3..ac1137fc4802f 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs @@ -2,15 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Collections.Immutable; using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; -using Microsoft.CodeAnalysis.CodeRefactorings; #if !CODE_STYLE using System; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Utilities; #endif @@ -84,9 +91,69 @@ public Test() public string? EditorConfig { get; set; } + /// + /// The set of code action s offered the user in this exact order. + /// Set this to ensure that a very specific set of actions is offered. + /// + public string[]? ExactActionSetOffered { get; set; } + + protected override ImmutableArray FilterCodeActions(ImmutableArray actions) + { + var result = base.FilterCodeActions(actions); + + if (ExactActionSetOffered != null) + { + Verify.SequenceEqual(ExactActionSetOffered, result.SelectAsArray(a => a.Title)); + } + + return result; + } + #if !CODE_STYLE + private readonly List _workspaces = new(); + protected override AnalyzerOptions GetAnalyzerOptions(Project project) => new WorkspaceAnalyzerOptions(base.GetAnalyzerOptions(project), project.Solution); + + /// + /// The we want this test to run in. Defaults to if unspecified. + /// + public TestHost TestHost { get; set; } = TestHost.InProcess; + + private static readonly TestComposition s_editorFeaturesOOPComposition = EditorTestCompositions.EditorFeatures.WithTestHostParts(TestHost.OutOfProcess); + + public override AdhocWorkspace CreateWorkspace() + { + if (TestHost == TestHost.InProcess) + return base.CreateWorkspace(); + + var hostServices = s_editorFeaturesOOPComposition.GetHostServices(); + var workspace = new AdhocWorkspace(hostServices); + lock (_workspaces) + _workspaces.Add(workspace); + + return workspace; + } + + public override async Task RunAsync(CancellationToken cancellationToken = default) + { + try + { + await base.RunAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + var workspaces = new List(); + lock (_workspaces) + { + workspaces.AddRange(_workspaces); + _workspaces.Clear(); + } + + foreach (var workspace in workspaces) + workspace.Dispose(); + } + } #endif } } diff --git a/src/EditorFeatures/Test/AssemblyReferenceTests.cs b/src/EditorFeatures/Test/AssemblyReferenceTests.cs index 8770b4b110110..66c5c8ccf4879 100644 --- a/src/EditorFeatures/Test/AssemblyReferenceTests.cs +++ b/src/EditorFeatures/Test/AssemblyReferenceTests.cs @@ -23,5 +23,13 @@ public void TestNoReferenceToImageCatalog() var dependencies = editorsFeatureAssembly.GetReferencedAssemblies(); Assert.Empty(dependencies.Where(a => a.FullName.Contains("Microsoft.VisualStudio.ImageCatalog"))); } + + [Fact] + public void TestNoReferenceToImagingInterop() + { + var editorsFeatureAssembly = typeof(Microsoft.CodeAnalysis.Editor.Shared.Extensions.GlyphExtensions).Assembly; + var dependencies = editorsFeatureAssembly.GetReferencedAssemblies(); + Assert.Empty(dependencies.Where(a => a.FullName.Contains("Microsoft.VisualStudio.Imaging.Interop"))); + } } } diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 4d0bf3a947aff..f28edd0623202 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -645,9 +645,15 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); - var expectedCount = !testMultiple - ? 1 - : analysisScope == BackgroundAnalysisScope.FullSolution ? 4 : 2; + var expectedCount = (analysisScope, testMultiple) switch + { + (BackgroundAnalysisScope.ActiveFile, _) => 0, + (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution, false) => 1, + (BackgroundAnalysisScope.OpenFilesAndProjects, true) => 2, + (BackgroundAnalysisScope.FullSolution, true) => 4, + _ => throw ExceptionUtilities.Unreachable, + }; + Assert.Equal(expectedCount, diagnostics.Count); for (var i = 0; i < analyzers.Length; i++) @@ -658,7 +664,11 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool var applicableDiagnostics = diagnostics.Where( d => d.Id == analyzer.Descriptor.Id && d.DataLocation.OriginalFilePath == additionalDoc.FilePath); - if (analysisScope != BackgroundAnalysisScope.FullSolution && + if (analysisScope == BackgroundAnalysisScope.ActiveFile) + { + Assert.Empty(applicableDiagnostics); + } + else if (analysisScope == BackgroundAnalysisScope.OpenFilesAndProjects && firstAdditionalDocument != additionalDoc) { Assert.Empty(applicableDiagnostics); @@ -962,8 +972,8 @@ internal async Task TestOnlyRequiredAnalyzerExecutedDuringDiagnosticComputation( var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer1, analyzer2)); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); var project = workspace.CurrentSolution.Projects.Single(); - var documentId = documentAnalysis ? project.Documents.Single().Id : null; - var diagnosticComputer = new DiagnosticComputer(documentId, project, span: null, AnalysisKind.Semantic, new DiagnosticAnalyzerInfoCache()); + var document = documentAnalysis ? project.Documents.Single() : null; + var diagnosticComputer = new DiagnosticComputer(document, project, span: null, AnalysisKind.Semantic, new DiagnosticAnalyzerInfoCache()); var diagnosticsMapResults = await diagnosticComputer.GetDiagnosticsAsync(analyzerIdsToRequestDiagnostics, reportSuppressedDiagnostics: false, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); Assert.False(analyzer2.ReceivedSymbolCallback); @@ -1009,7 +1019,7 @@ void M() var diagnosticAnalyzerInfoCache = new DiagnosticAnalyzerInfoCache(); var kind = actionKind == AnalyzerRegisterActionKind.SyntaxTree ? AnalysisKind.Syntax : AnalysisKind.Semantic; - var diagnosticComputer = new DiagnosticComputer(document.Id, project, span: null, kind, diagnosticAnalyzerInfoCache); + var diagnosticComputer = new DiagnosticComputer(document, project, span: null, kind, diagnosticAnalyzerInfoCache); var analyzerIds = new[] { analyzer.GetAnalyzerId() }; // First invoke analysis with cancellation token, and verify canceled compilation and no reported diagnostics. diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs deleted file mode 100644 index 6002a22d63a53..0000000000000 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs +++ /dev/null @@ -1,364 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; -using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Test.Utilities; -using Roslyn.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - [UseExportProvider] - public class DiagnosticDataSerializerTests : TestBase - { - [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] - public async Task SerializationTest_Document() - { - using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts( - typeof(TestPersistentStorageServiceFactory))); - - var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); - - var diagnostics = new[] - { - new DiagnosticData( - id: "test1", - category: "Test", - message: "test1 message", - enuMessageForBingSearch: "test1 message format", - severity: DiagnosticSeverity.Info, - defaultSeverity:DiagnosticSeverity.Info, - isEnabledByDefault: false, - warningLevel: 1, - customTags: ImmutableArray.Empty, - properties: ImmutableDictionary.Empty, - document.Project.Id, - new DiagnosticDataLocation(document.Id, new TextSpan(10, 20), "originalFile1", 30, 30, 40, 40, "mappedFile1", 10, 10, 20, 20), - language: LanguageNames.CSharp), - - new DiagnosticData( - id: "test2", - category: "Test", - message: "test2 message", - enuMessageForBingSearch: "test2 message format", - severity: DiagnosticSeverity.Warning, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 0, - customTags: ImmutableArray.Create("Test2"), - properties: ImmutableDictionary.Empty.Add("propertyKey", "propertyValue"), - document.Project.Id, - new DiagnosticDataLocation(document.Id, new TextSpan(30, 40), "originalFile2", 70, 70, 80, 80, "mappedFile2", 50, 50, 60, 60), - language: "VB", - title: "test2 title", - description: "test2 description", - helpLink: "http://test2link"), - - new DiagnosticData( - id: "test3", - category: "Test", - message: "test3 message", - enuMessageForBingSearch: "test3 message format", - severity:DiagnosticSeverity.Error, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 2, - customTags: ImmutableArray.Create("Test3", "Test3_2"), - properties: ImmutableDictionary.Empty.Add("p1Key", "p1Value").Add("p2Key", "p2Value"), - document.Project.Id, - new DiagnosticDataLocation(document.Id, new TextSpan(50, 60), "originalFile3", 110, 110, 120, 120, "mappedFile3", 90, 90, 100, 100), - title: "test3 title", - description: "test3 description", - helpLink: "http://test3link"), - - }.ToImmutableArray(); - - var utcTime = DateTime.UtcNow; - var analyzerVersion = VersionStamp.Create(utcTime); - var version = VersionStamp.Create(utcTime.AddDays(1)); - - var key = "document"; - - var persistentService = workspace.Services.GetRequiredService(); - var serializer = new CodeAnalysis.Workspaces.Diagnostics.DiagnosticDataSerializer(analyzerVersion, version); - - Assert.True(await serializer.SerializeAsync(persistentService, document.Project, document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); - - var recovered = await serializer.DeserializeAsync(persistentService, document.Project, document, key, CancellationToken.None); - - AssertDiagnostics(diagnostics, recovered); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] - public async Task SerializationTest_Project() - { - using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts( - typeof(TestPersistentStorageServiceFactory))); - - var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); - - var diagnostics = new[] - { - new DiagnosticData( - id: "test1", - category: "Test", - message: "test1 message", - enuMessageForBingSearch: "test1 message format", - severity: DiagnosticSeverity.Info, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: false, - warningLevel: 1, - customTags: ImmutableArray.Empty, - properties: ImmutableDictionary.Empty, - projectId: document.Project.Id, - language: LanguageNames.VisualBasic, - description: "test1 description", - helpLink: "http://test1link"), - - new DiagnosticData( - id: "test2", - category: "Test", - message: "test2 message", - enuMessageForBingSearch: "test2 message format", - severity: DiagnosticSeverity.Warning, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 0, - customTags: ImmutableArray.Create("Test2"), - properties: ImmutableDictionary.Empty.Add("p1Key", "p2Value"), - projectId: document.Project.Id), - - new DiagnosticData( - id: "test3", - category: "Test", - message: "test3 message", - enuMessageForBingSearch: "test3 message format", - severity: DiagnosticSeverity.Error, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 2, - customTags: ImmutableArray.Create("Test3", "Test3_2"), - properties: ImmutableDictionary.Empty.Add("p2Key", "p2Value").Add("p1Key", "p1Value"), - projectId: document.Project.Id, - description: "test3 description", - helpLink: "http://test3link"), - - }.ToImmutableArray(); - - var utcTime = DateTime.UtcNow; - var analyzerVersion = VersionStamp.Create(utcTime); - var version = VersionStamp.Create(utcTime.AddDays(1)); - - var key = "project"; - var persistentService = workspace.Services.GetRequiredService(); - var serializer = new CodeAnalysis.Workspaces.Diagnostics.DiagnosticDataSerializer(analyzerVersion, version); - - Assert.True(await serializer.SerializeAsync(persistentService, document.Project, document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); - var recovered = await serializer.DeserializeAsync(persistentService, document.Project, document, key, CancellationToken.None); - - AssertDiagnostics(diagnostics, recovered); - } - - [WorkItem(6104, "https://github.com/dotnet/roslyn/issues/6104")] - [Fact] - public void DiagnosticEquivalence() - { -#if DEBUG - var source = -@"class C -{ - static int F(string s) { return 1; } - static int x = F(new { }); - static int y = F(new { A = 1 }); -}"; - var tree = SyntaxFactory.ParseSyntaxTree(source); - var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, concurrentBuild: false); - var compilation = CSharpCompilation.Create(GetUniqueName(), new[] { tree }, new[] { MscorlibRef }, options); - var model = compilation.GetSemanticModel(tree); - - // Each call to GetDiagnostics will bind field initializers - // (see https://github.com/dotnet/roslyn/issues/6264). - var diagnostics1 = model.GetDiagnostics().ToArray(); - var diagnostics2 = model.GetDiagnostics().ToArray(); - - diagnostics1.Verify( - // (4,22): error CS1503: Argument 1: cannot convert from '' to 'string' - // static int x = F(new { }); - Diagnostic(1503, "new { }").WithArguments("1", "", "string").WithLocation(4, 22), - // (5,22): error CS1503: Argument 1: cannot convert from '' to 'string' - // static int y = F(new { A = 1 }); - Diagnostic(1503, "new { A = 1 }").WithArguments("1", "", "string").WithLocation(5, 22)); - - Assert.NotSame(diagnostics1[0], diagnostics2[0]); - Assert.NotSame(diagnostics1[1], diagnostics2[1]); - Assert.Equal(diagnostics1, diagnostics2); - Assert.True(AnalyzerHelper.AreEquivalent(diagnostics1, diagnostics2)); - - // Verify that not all collections are treated as equivalent. - diagnostics1 = new[] { diagnostics1[0] }; - diagnostics2 = new[] { diagnostics2[1] }; - - Assert.NotEqual(diagnostics1, diagnostics2); - Assert.False(AnalyzerHelper.AreEquivalent(diagnostics1, diagnostics2)); -#endif - } - - private static void AssertDiagnostics(ImmutableArray items1, ImmutableArray items2) - { - Assert.Equal(items1.Length, items2.Length); - - for (var i = 0; i < items1.Length; i++) - { - AssertDiagnostics(items1[i], items2[i]); - } - } - - private static void AssertDiagnostics(DiagnosticData item1, DiagnosticData item2) - { - Assert.Equal(item1.Id, item2.Id); - Assert.Equal(item1.Category, item2.Category); - Assert.Equal(item1.Message, item2.Message); - Assert.Equal(item1.ENUMessageForBingSearch, item2.ENUMessageForBingSearch); - Assert.Equal(item1.Severity, item2.Severity); - Assert.Equal(item1.IsEnabledByDefault, item2.IsEnabledByDefault); - Assert.Equal(item1.WarningLevel, item2.WarningLevel); - Assert.Equal(item1.DefaultSeverity, item2.DefaultSeverity); - - Assert.Equal(item1.CustomTags.Length, item2.CustomTags.Length); - for (var j = 0; j < item1.CustomTags.Length; j++) - Assert.Equal(item1.CustomTags[j], item2.CustomTags[j]); - - Assert.Equal(item1.Properties.Count, item2.Properties.Count); - Assert.True(item1.Properties.SetEquals(item2.Properties)); - - Assert.Equal(item1.ProjectId, item2.ProjectId); - Assert.Equal(item1.DocumentId, item2.DocumentId); - - Assert.Equal(item1.HasTextSpan, item2.HasTextSpan); - if (item1.HasTextSpan) - { - Assert.Equal(item1.GetTextSpan(), item2.GetTextSpan()); - } - - Assert.Equal(item1.DataLocation?.MappedFilePath, item2.DataLocation?.MappedFilePath); - Assert.Equal(item1.DataLocation?.MappedStartLine, item2.DataLocation?.MappedStartLine); - Assert.Equal(item1.DataLocation?.MappedStartColumn, item2.DataLocation?.MappedStartColumn); - Assert.Equal(item1.DataLocation?.MappedEndLine, item2.DataLocation?.MappedEndLine); - Assert.Equal(item1.DataLocation?.MappedEndColumn, item2.DataLocation?.MappedEndColumn); - - Assert.Equal(item1.DataLocation?.OriginalFilePath, item2.DataLocation?.OriginalFilePath); - Assert.Equal(item1.DataLocation?.OriginalStartLine, item2.DataLocation?.OriginalStartLine); - Assert.Equal(item1.DataLocation?.OriginalStartColumn, item2.DataLocation?.OriginalStartColumn); - Assert.Equal(item1.DataLocation?.OriginalEndLine, item2.DataLocation?.OriginalEndLine); - Assert.Equal(item1.DataLocation?.OriginalEndColumn, item2.DataLocation?.OriginalEndColumn); - - Assert.Equal(item1.Description, item2.Description); - Assert.Equal(item1.HelpLink, item2.HelpLink); - } - - [ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), ServiceLayer.Test), Shared, PartNotDiscoverable] - public class TestPersistentStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestPersistentStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new Service(); - - public class Service : IPersistentStorageService - { - private readonly Storage _instance = new(); - - IPersistentStorage IPersistentStorageService.GetStorage(Solution solution) - => _instance; - - ValueTask IPersistentStorageService.GetStorageAsync(Solution solution, CancellationToken cancellationToken) - => new(_instance); - - internal class Storage : IPersistentStorage - { - private readonly Dictionary _map = new(); - - public Task ReadStreamAsync(string name, CancellationToken cancellationToken = default) - { - var stream = _map[name]; - stream.Position = 0; - - return Task.FromResult(stream); - } - - public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default) - { - var stream = _map[Tuple.Create(project, name)]; - stream.Position = 0; - - return Task.FromResult(stream); - } - - public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default) - { - var stream = _map[Tuple.Create(document, name)]; - stream.Position = 0; - - return Task.FromResult(stream); - } - - public Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default) - { - _map[name] = new MemoryStream(); - stream.CopyTo(_map[name]); - - return SpecializedTasks.True; - } - - public Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken = default) - { - _map[Tuple.Create(project, name)] = new MemoryStream(); - stream.CopyTo(_map[Tuple.Create(project, name)]); - - return SpecializedTasks.True; - } - - public Task WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken = default) - { - _map[Tuple.Create(document, name)] = new MemoryStream(); - stream.CopyTo(_map[Tuple.Create(document, name)]); - - return SpecializedTasks.True; - } - - public void Dispose() - { - } - - public ValueTask DisposeAsync() - { - return ValueTaskFactory.CompletedTask; - } - } - } - } - } -} diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 4e8cd17a5bb66..559dbdedb94e3 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -71,7 +71,7 @@ public class IDEDiagnosticIDConfigurationTests private static void ValidateHelpLinkForDiagnostic(string diagnosticId, string helpLinkUri) { if (diagnosticId is "IDE0043" // Intentionally undocumented because it's being removed in favor of CA2241 - or "IDE1007" or "IDE1008" or "RemoveUnnecessaryImportsFixable" + or "IDE1007" or "RemoveUnnecessaryImportsFixable" or "RE0001") // Tracked by https://github.com/dotnet/roslyn/issues/48530 { Assert.True(helpLinkUri == string.Empty, $"Expected empty help link for {diagnosticId}"); @@ -411,9 +411,6 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE1007 dotnet_diagnostic.IDE1007.severity = %value% -# IDE1008 -dotnet_diagnostic.IDE1008.severity = %value% - # IDE0120 dotnet_diagnostic.IDE0120.severity = %value% @@ -584,12 +581,12 @@ public void VisualBasic_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE1007 dotnet_diagnostic.IDE1007.severity = %value% -# IDE1008 -dotnet_diagnostic.IDE1008.severity = %value% - # IDE0120 dotnet_diagnostic.IDE0120.severity = %value% +# IDE0140 +dotnet_diagnostic.IDE0140.severity = %value% + # IDE2000 dotnet_diagnostic.IDE2000.severity = %value% @@ -1001,9 +998,6 @@ No editorconfig based code style option # IDE1007 No editorconfig based code style option -# IDE1008 -No editorconfig based code style option - # IDE2000, AllowMultipleBlankLines dotnet_style_allow_multiple_blank_lines_experimental = true @@ -1210,12 +1204,12 @@ No editorconfig based code style option # IDE1007 No editorconfig based code style option -# IDE1008 -No editorconfig based code style option - # IDE0120 No editorconfig based code style option +# IDE0140, PreferSimplifiedObjectCreation +visual_basic_style_prefer_simplified_object_creation = true + # IDE2000, AllowMultipleBlankLines dotnet_style_allow_multiple_blank_lines_experimental = true diff --git a/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs b/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs new file mode 100644 index 0000000000000..ccbf3d2968f1a --- /dev/null +++ b/src/EditorFeatures/Test/EditAndContinue/ActiveStatementsMapTests.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests +{ + [UseExportProvider] + public class ActiveStatementsMapTests + { + [Theory] + [InlineData(/*span*/ 3, 0, 5, 2, /*expected*/ 0, 4)] + [InlineData(/*span*/ 2, 0, 3, 1, /*expected*/ 0, 1)] + [InlineData(/*span*/ 19, 1, 19, 100, /*expected*/ 0, 0)] + [InlineData(/*span*/ 20, 1, 20, 2, /*expected*/ 0, 0)] + [InlineData(/*span*/ 0, 0, 100, 0, /*expected*/ 0, 6)] + public void GetSpansStartingInSpan1(int sl, int sc, int el, int ec, int s, int e) + { + var span = new LinePositionSpan(new(sl, sc), new(el, ec)); + var array = ImmutableArray.Create( + new LinePositionSpan(new(3, 0), new(3, 1)), + new LinePositionSpan(new(3, 5), new(3, 6)), + new LinePositionSpan(new(4, 4), new(4, 18)), + new LinePositionSpan(new(5, 1), new(5, 2)), + new LinePositionSpan(new(5, 2), new(5, 8)), + new LinePositionSpan(new(19, 0), new(19, 42))); + + Assert.Equal(new Range(s, e), ActiveStatementsMap.GetSpansStartingInSpan(span.Start, span.End, array, startPositionComparer: (x, y) => x.Start.CompareTo(y))); + } + + [Fact] + public void GetSpansStartingInSpan2() + { + var span = TextSpan.FromBounds(8, 11); + + var array = ImmutableArray.Create( + TextSpan.FromBounds(1, 6), // does not overlap + TextSpan.FromBounds(3, 9), // overlaps + TextSpan.FromBounds(4, 5), // does not overlap + TextSpan.FromBounds(6, 7), // does not overlap + TextSpan.FromBounds(7, 9), // overlaps + TextSpan.FromBounds(10, 12), // overlaps + TextSpan.FromBounds(13, 15)); // does not overlap + + // only one span has start position within the span: + Assert.Equal(new Range(5, 6), ActiveStatementsMap.GetSpansStartingInSpan(span.Start, span.End, array, startPositionComparer: (x, y) => x.Start.CompareTo(y))); + } + + [Fact] + public async Task Ordering() + { + using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); + + var source = @" +class C +{ + void F() + { +#line 2 ""x"" +S1(); +S2(); +S3(); +#line 1 ""x"" +S0(); +S1(); +S2(); +#line 5 ""x"" +S4(); +S5(); +S5(); +#line default + } +}"; + + var solution = workspace.CurrentSolution + .AddProject("proj", "proj", LanguageNames.CSharp) + .AddDocument("doc", SourceText.From(source, Encoding.UTF8), filePath: "a.cs").Project.Solution; + + var project = solution.Projects.Single(); + var document = project.Documents.Single(); + var analyzer = project.LanguageServices.GetRequiredService(); + + var documentPathMap = new Dictionary>(); + + var moduleId = Guid.NewGuid(); + var token = 0x06000001; + ManagedActiveStatementDebugInfo CreateInfo(int startLine, int startColumn, int endLine, int endColumn, string fileName) + => new(new(new(moduleId, token++, version: 1), ilOffset: 0), fileName, new SourceSpan(startLine, startColumn, endLine, endColumn), ActiveStatementFlags.None); + + var debugInfos = ImmutableArray.Create( + CreateInfo(3, 0, 3, 4, "x"), + CreateInfo(6, 0, 6, 4, "x"), + CreateInfo(4, 0, 4, 4, "x"), + CreateInfo(2, 0, 2, 4, "x"), + CreateInfo(5, 0, 5, 4, "x"), + CreateInfo(0, 0, 0, 4, "x"), + CreateInfo(1, 0, 1, 4, "x") + ); + + var map = ActiveStatementsMap.Create(debugInfos, remapping: ImmutableDictionary>.Empty); + + var oldSpans = await map.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None); + + AssertEx.Equal(new[] + { + "[48..52) -> (1,0)-(1,4) #6", + "[55..59) -> (2,0)-(2,4) #3", + "[62..66) -> (3,0)-(3,4) #0", + "[86..90) -> (0,0)-(0,4) #5", + "[120..124) -> (4,0)-(4,4) #2", + "[127..131) -> (5,0)-(5,4) #4", + "[134..138) -> (6,0)-(6,4) #1" + }, oldSpans.Select(s => $"{s.UnmappedSpan} -> {s.Statement.Span} #{s.Statement.Ordinal}")); + } + } +} diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 6daf7df83e97d..ecd7e89fb48cd 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -40,11 +40,8 @@ public sealed partial class EditAndContinueWorkspaceServiceTests : TestBase { private static readonly TestComposition s_composition = FeaturesTestCompositions.Features; - private static readonly SolutionActiveStatementSpanProvider s_noSolutionActiveSpans = - (_, _) => new(ImmutableArray.Empty); - - private static readonly DocumentActiveStatementSpanProvider s_noDocumentActiveSpans = - _ => new(ImmutableArray.Empty); + private static readonly ActiveStatementSpanProvider s_noActiveSpans = + (_, _, _) => new(ImmutableArray.Empty); private const TargetFramework DefaultTargetFramework = TargetFramework.NetStandard20; @@ -65,31 +62,35 @@ public EditAndContinueWorkspaceServiceTests() }; } - private static TestWorkspace CreateWorkspace(Type[] additionalParts = null) - => new(composition: s_composition.AddParts(additionalParts)); + private TestWorkspace CreateWorkspace(out Solution solution, out EditAndContinueWorkspaceService service, Type[] additionalParts = null) + { + var workspace = new TestWorkspace(composition: s_composition.AddParts(additionalParts)); + solution = workspace.CurrentSolution; + service = GetEditAndContinueService(workspace); + return workspace; + } private static SourceText GetAnalyzerConfigText((string key, string value)[] analyzerConfig) => SourceText.From("[*.*]" + Environment.NewLine + string.Join(Environment.NewLine, analyzerConfig.Select(c => $"{c.key} = {c.value}"))); - private static Project AddDefaultTestProject( - TestWorkspace workspace, + private static (Solution, Document) AddDefaultTestProject( + Solution solution, string source, ISourceGenerator generator = null, string additionalFileText = null, (string key, string value)[] analyzerConfig = null) { - return AddDefaultTestProject(workspace, new[] { source }, generator, additionalFileText, analyzerConfig); + solution = AddDefaultTestProject(solution, new[] { source }, generator, additionalFileText, analyzerConfig); + return (solution, solution.Projects.Single().Documents.Single()); } - private static Project AddDefaultTestProject( - TestWorkspace workspace, + private static Solution AddDefaultTestProject( + Solution solution, string[] sources, ISourceGenerator generator = null, string additionalFileText = null, (string key, string value)[] analyzerConfig = null) { - var solution = workspace.CurrentSolution; - var project = solution. AddProject("proj", "proj", LanguageNames.CSharp). WithMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)); @@ -127,15 +128,14 @@ private static Project AddDefaultTestProject( solution = document.Project.Solution; } - workspace.ChangeSolution(document.Project.Solution); - return workspace.CurrentSolution.GetProject(document.Project.Id); + return document.Project.Solution; } private EditAndContinueWorkspaceService GetEditAndContinueService(Workspace workspace) { var service = (EditAndContinueWorkspaceService)workspace.Services.GetRequiredService(); var accessor = service.GetTestAccessor(); - accessor.SetOutputProvider(_mockCompilationOutputsProvider); + accessor.SetOutputProvider(project => _mockCompilationOutputsProvider(project)); accessor.SetReportTelemetry(data => EditAndContinueWorkspaceService.LogDebuggingSessionTelemetry(data, (id, message) => _telemetryLog.Add($"{id}: {message.GetMessage()}"), () => ++_telemetryId)); return service; } @@ -192,9 +192,9 @@ private static void EndDebuggingSession(EditAndContinueWorkspaceService service, private static async Task<(ManagedModuleUpdates updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync( IEditAndContinueWorkspaceService service, Solution solution, - SolutionActiveStatementSpanProvider activeStatementSpanProvider = null) + ActiveStatementSpanProvider activeStatementSpanProvider = null) { - var result = await service.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider ?? s_noSolutionActiveSpans, CancellationToken.None); + var result = await service.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); return (result.ModuleUpdates, result.GetDiagnosticData(solution)); } @@ -398,9 +398,7 @@ public async Task StartDebuggingSession_CapturingDocuments() sourceFileB.WriteAllText(sourceB2, encodingB); // prepare workspace as if it was loaded from project files: - using var workspace = CreateWorkspace(new[] { typeof(DummyLanguageService) }); - - var solution = workspace.CurrentSolution; + using var _ = CreateWorkspace(out var solution, out var service, new[] { typeof(DummyLanguageService) }); var projectP = solution.AddProject("P", "P", LanguageNames.CSharp); solution = projectP.Solution; @@ -454,8 +452,6 @@ public async Task StartDebuggingSession_CapturingDocuments() loader: new FailingTextLoader(), filePath: sourceFileD.Path)); - var service = GetEditAndContinueService(workspace); - await service.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: true, CancellationToken.None); var debuggingSession = service.GetTestAccessor().GetDebuggingSession(); @@ -484,26 +480,23 @@ public async Task StartDebuggingSession_CapturingDocuments() [Fact] public async Task RunMode_ProjectThatDoesNotSupportEnC() { - using var workspace = CreateWorkspace(new[] { typeof(DummyLanguageService) }); - var solution = workspace.CurrentSolution; + using var _ = CreateWorkspace(out var solution, out var service, new[] { typeof(DummyLanguageService) }); var project = solution.AddProject("dummy_proj", "dummy_proj", DummyLanguageService.LanguageName); var document = project.AddDocument("test", SourceText.From("dummy1")); - workspace.ChangeSolution(document.Project.Solution); - - var service = GetEditAndContinueService(workspace); + solution = document.Project.Solution; - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); // no changes: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noDocumentActiveSpans, CancellationToken.None); + var document1 = solution.Projects.Single().Documents.Single(); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // change the source: - workspace.ChangeDocument(document1.Id, SourceText.From("dummy2")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("dummy2")); + var document2 = solution.GetDocument(document1.Id); - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); } @@ -512,29 +505,26 @@ public async Task RunMode_DesignTimeOnlyDocument() { var moduleFile = Temp.CreateFile().WriteAllBytes(TestResources.Basic.Members); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }"); - var documentInfo = CreateDesignTimeOnlyDocument(project.Id); + var documentInfo = CreateDesignTimeOnlyDocument(document1.Project.Id); + solution = solution.WithProjectOutputFilePath(document1.Project.Id, moduleFile.Path).AddDocument(documentInfo); - workspace.ChangeSolution(project.Solution.WithProjectOutputFilePath(project.Id, moduleFile.Path).AddDocument(documentInfo)); _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); // update a design-time-only source file: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(d => d.Id == documentInfo.Id); - workspace.ChangeDocument(document1.Id, SourceText.From("class UpdatedC2 {}")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(d => d.Id == documentInfo.Id); + solution = solution.WithDocumentText(documentInfo.Id, SourceText.From("class UpdatedC2 {}")); + var document2 = solution.GetDocument(documentInfo.Id); // no updates: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // validate solution update status and emit - changes made in design-time-only documents are ignored: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); EndDebuggingSession(service); @@ -547,28 +537,23 @@ public async Task RunMode_DesignTimeOnlyDocument() [Fact] public async Task RunMode_ProjectNotBuilt() { - using (var workspace = CreateWorkspace()) - { - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); - - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.Empty); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }"); - var service = GetEditAndContinueService(workspace); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.Empty); - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); - // no changes: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noDocumentActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + // no changes: + var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None); + Assert.Empty(diagnostics); - // change the source: - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + // change the source: + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }")); + var document2 = solution.GetDocument(document1.Id); - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); - } + diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + Assert.Empty(diagnostics); } [Fact] @@ -577,29 +562,27 @@ public async Task RunMode_DifferentDocumentWithSameContent() var source = "class C1 { void M1() { System.Console.WriteLine(1); } }"; var moduleFile = Temp.CreateFile().WriteAllBytes(TestResources.Basic.Members); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, source); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, source); - workspace.ChangeSolution(project.Solution.WithProjectOutputFilePath(project.Id, moduleFile.Path)); + solution = solution.WithProjectOutputFilePath(document.Project.Id, moduleFile.Path); _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); // update the document - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(source)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var document1 = solution.GetDocument(document.Id); + solution = solution.WithDocumentText(document.Id, SourceText.From(source)); + var document2 = solution.GetDocument(document.Id); Assert.Equal(document1.Id, document2.Id); Assert.NotSame(document1, document2); - var diagnostics2 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics2 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics2); // validate solution update status and emit - changes made during run mode are ignored: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); EndDebuggingSession(service); @@ -612,42 +595,37 @@ public async Task RunMode_DifferentDocumentWithSameContent() [Fact] public async Task BreakMode_ProjectThatDoesNotSupportEnC() { - using (var workspace = CreateWorkspace(new[] { typeof(DummyLanguageService) })) - { - var solution = workspace.CurrentSolution; - var project = solution.AddProject("dummy_proj", "dummy_proj", DummyLanguageService.LanguageName); - var document = project.AddDocument("test", SourceText.From("dummy1")); - workspace.ChangeSolution(document.Project.Solution); - - var service = GetEditAndContinueService(workspace); + using var _ = CreateWorkspace(out var solution, out var service, new[] { typeof(DummyLanguageService) }); + var project = solution.AddProject("dummy_proj", "dummy_proj", DummyLanguageService.LanguageName); + var document = project.AddDocument("test", SourceText.From("dummy1")); + solution = document.Project.Solution; - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); - EnterBreakState(service); + await StartDebuggingSessionAsync(service, solution); + EnterBreakState(service); - // change the source: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("dummy2")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + // change the source: + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("dummy2")); + var document2 = solution.GetDocument(document1.Id); - // validate solution update status and emit: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + // validate solution update status and emit: + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); - Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); - } + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); + Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); + Assert.Empty(updates.Updates); + Assert.Empty(emitDiagnostics); } [Fact] public async Task BreakMode_DesignTimeOnlyDocument_Dynamic() { - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); - var project = AddDefaultTestProject(workspace, "class C {}"); + (solution, var document) = AddDefaultTestProject(solution, "class C {}"); var documentInfo = DocumentInfo.Create( - DocumentId.CreateNewId(project.Id), + DocumentId.CreateNewId(document.Project.Id), name: "design-time-only.cs", folders: Array.Empty(), sourceCodeKind: SourceCodeKind.Regular, @@ -657,22 +635,19 @@ public async Task BreakMode_DesignTimeOnlyDocument_Dynamic() designTimeOnly: true, documentServiceProvider: null); - var solution = workspace.CurrentSolution.AddDocument(documentInfo); - workspace.ChangeSolution(solution); + solution = solution.AddDocument(documentInfo); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); // change the source: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(d => d.Id == documentInfo.Id); - workspace.ChangeDocument(document1.Id, SourceText.From("class E {}")); + var document1 = solution.GetDocument(documentInfo.Id); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class E {}")); // validate solution update status and emit: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -690,10 +665,10 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad) var dir = Temp.CreateDirectory(); var sourceFileA = dir.CreateFile("a.cs").WriteAllText(sourceA); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): - var documentA = workspace.CurrentSolution. + var documentA = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("a.cs", SourceText.From(sourceA, Encoding.UTF8), filePath: sourceFileA.Path); @@ -704,7 +679,7 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad) var documentC = documentB.Project. AddDocument("c.g.i.vb", SourceText.From(sourceC, Encoding.UTF8), filePath: "c.g.i.vb"); - workspace.ChangeSolution(documentC.Project.Solution); + solution = documentC.Project.Solution; // only compile A; B and C are design-time-only: var moduleId = EmitLibrary(sourceA, sourceFilePath: sourceFileA.Path); @@ -714,26 +689,24 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad) LoadLibraryToDebuggee(moduleId); } - var service = GetEditAndContinueService(workspace); - - await service.StartDebuggingSessionAsync(workspace.CurrentSolution, _debuggerService, captureMatchingDocuments: false, CancellationToken.None); + await service.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, CancellationToken.None); EnterBreakState(service); // change the source (rude edit): - workspace.ChangeDocument(documentB.Id, SourceText.From("class B { public void RenamedMethod() { } }")); - workspace.ChangeDocument(documentC.Id, SourceText.From("class C { public void RenamedMethod() { } }")); - var documentB2 = workspace.CurrentSolution.Projects.Single().Documents.Single(d => d.Id == documentB.Id); - var documentC2 = workspace.CurrentSolution.Projects.Single().Documents.Single(d => d.Id == documentC.Id); + solution = solution.WithDocumentText(documentB.Id, SourceText.From("class B { public void RenamedMethod() { } }")); + solution = solution.WithDocumentText(documentC.Id, SourceText.From("class C { public void RenamedMethod() { } }")); + var documentB2 = solution.GetDocument(documentB.Id); + var documentC2 = solution.GetDocument(documentC.Id); // no Rude Edits reported: - Assert.Empty(await service.GetDocumentDiagnosticsAsync(documentB2, s_noDocumentActiveSpans, CancellationToken.None)); - Assert.Empty(await service.GetDocumentDiagnosticsAsync(documentC2, s_noDocumentActiveSpans, CancellationToken.None)); + Assert.Empty(await service.GetDocumentDiagnosticsAsync(documentB2, s_noActiveSpans, CancellationToken.None)); + Assert.Empty(await service.GetDocumentDiagnosticsAsync(documentC2, s_noActiveSpans, CancellationToken.None)); // validate solution update status and emit: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(emitDiagnostics); @@ -742,9 +715,9 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad) LoadLibraryToDebuggee(moduleId); // validate solution update status and emit: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(emitDiagnostics); } @@ -771,14 +744,12 @@ public async Task ErrorReadingModuleFile(bool breakMode) expectedErrorMessage = e.Message; } - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); + using var _w = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }"); _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); if (breakMode) { @@ -786,20 +757,19 @@ public async Task ErrorReadingModuleFile(bool breakMode) } // change the source: - var document1 = project.Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }")); + var document2 = solution.GetDocument(document1.Id); // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); - AssertEx.Equal(new[] { $"{project.Id} Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}" }, InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal(new[] { $"{document2.Project.Id} Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}" }, InspectDiagnostics(emitDiagnostics)); if (breakMode) { @@ -834,15 +804,15 @@ public async Task BreakMode_ErrorReadingPdbFile() var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path); var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); @@ -854,22 +824,21 @@ public async Task BreakMode_ErrorReadingPdbFile() } }; - var service = GetEditAndContinueService(workspace); - await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // change the source: - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); - var document2 = workspace.CurrentSolution.GetDocument(document1.Id); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); + var document2 = solution.GetDocument(document1.Id); // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // an error occurred so we need to call update to determine whether we have changes to apply or not: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(updates.Updates); AssertEx.Equal(new[] { $"{project.Id} Warning ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}" }, InspectDiagnostics(emitDiagnostics)); @@ -890,37 +859,36 @@ public async Task BreakMode_ErrorReadingSourceFile() var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)). AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path); var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); - var service = GetEditAndContinueService(workspace); - await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // change the source: - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); - var document2 = workspace.CurrentSolution.GetDocument(document1.Id); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); + var document2 = solution.GetDocument(document1.Id); using var fileLock = File.Open(sourceFile.Path, FileMode.Open, FileAccess.Read, FileShare.None); // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // an error occurred so we need to call update to determine whether we have changes to apply or not: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); // try apply changes: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(updates.Updates); AssertEx.Equal(new[] { $"{project.Id} Warning ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}" }, InspectDiagnostics(emitDiagnostics)); @@ -928,7 +896,7 @@ public async Task BreakMode_ErrorReadingSourceFile() fileLock.Dispose(); // try apply changes again: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); Assert.NotEmpty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -952,22 +920,21 @@ public async Task FileAdded(bool breakMode) var sourceFileA = Temp.CreateFile().WriteAllText(sourceA); var sourceFileB = Temp.CreateFile().WriteAllText(sourceB); - using var workspace = new TestWorkspace(composition: s_composition); + using var _ = CreateWorkspace(out var solution, out var service); - var documentA = workspace.CurrentSolution. + var documentA = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", SourceText.From(sourceA, Encoding.UTF8), filePath: sourceFileA.Path); - workspace.ChangeSolution(documentA.Project.Solution); + solution = documentA.Project.Solution; // Source B will be added while debugging. EmitAndLoadLibraryToDebuggee(sourceA, sourceFilePath: sourceFileA.Path); var project = documentA.Project; - var service = GetEditAndContinueService(workspace); - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); if (breakMode) { @@ -976,15 +943,15 @@ public async Task FileAdded(bool breakMode) // add a source file: 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); + solution = documentB.Project.Solution; + documentB = solution.GetDocument(documentB.Id); - var diagnostics2 = await service.GetDocumentDiagnosticsAsync(documentB, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics2 = await service.GetDocumentDiagnosticsAsync(documentB, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics2); - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); if (breakMode) @@ -1037,48 +1004,45 @@ void M() System.Console.WriteLine(30); } }"; - using (var workspace = CreateWorkspace()) - { - var project = AddDefaultTestProject(workspace, source1); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, source1); - LoadLibraryToDebuggee(moduleId, new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.NotAllowedForRuntime, "*message*")); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); - var service = GetEditAndContinueService(workspace); + LoadLibraryToDebuggee(moduleId, new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.NotAllowedForRuntime, "*message*")); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); - EnterBreakState(service); + EnterBreakState(service); - // change the source: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(source2)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + // change the source: + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From(source2)); + var document2 = solution.GetDocument(document1.Id); - // We do not report module diagnostics until emit. - // This is to make the analysis deterministic (not dependent on the current state of the debuggee). - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); - AssertEx.Empty(diagnostics1); + // We do not report module diagnostics until emit. + // This is to make the analysis deterministic (not dependent on the current state of the debuggee). + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + AssertEx.Empty(diagnostics1); - // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + // validate solution update status and emit: + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); - Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - AssertEx.Equal(new[] { $"{project.Id} Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, project.Name, "*message*")}" }, InspectDiagnostics(emitDiagnostics)); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); + Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); + Assert.Empty(updates.Updates); + AssertEx.Equal(new[] { $"{document2.Project.Id} Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, document2.Project.Name, "*message*")}" }, InspectDiagnostics(emitDiagnostics)); - EndDebuggingSession(service); + EndDebuggingSession(service); - AssertEx.SetEqual(new[] { moduleId }, debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); + AssertEx.SetEqual(new[] { moduleId }, debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); - AssertEx.Equal(new[] - { - "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1", - "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC2016" - }, _telemetryLog); - } + AssertEx.Equal(new[] + { + "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", + "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1", + "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC2016" + }, _telemetryLog); } [Fact] @@ -1091,9 +1055,9 @@ public async Task BreakMode_Encodings() var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1, encoding); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", SourceText.From(source1, encoding), filePath: sourceFile.Path); @@ -1101,23 +1065,22 @@ public async Task BreakMode_Encodings() var documentId = document1.Id; var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path, encoding: encoding); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // Emulate opening the file, which will trigger "out-of-sync" check. // Since we find content matching the PDB checksum we update the committed solution with this source text. // If we used wrong encoding this would lead to a false change detected below. - var currentDocument = workspace.CurrentSolution.GetDocument(documentId); + var currentDocument = solution.GetDocument(documentId); await debuggingSession.LastCommittedSolution.OnSourceFileUpdatedAsync(currentDocument, debuggingSession.CancellationToken); // EnC service queries for a document, which triggers read of the source file from disk. - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); EndDebuggingSession(service); } @@ -1131,13 +1094,12 @@ public async Task RudeEdits(bool breakMode) var moduleId = Guid.NewGuid(); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, source1); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, source1); - var service = GetEditAndContinueService(workspace); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); if (breakMode) { @@ -1145,18 +1107,18 @@ public async Task RudeEdits(bool breakMode) } // change the source (rude edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(source2, Encoding.UTF8)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From(source2, Encoding.UTF8)); + var document2 = solution.GetDocument(document1.Id); - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal(new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1204,25 +1166,23 @@ class C { int Y => 2; } var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1, generator: generator); - - var service = GetEditAndContinueService(workspace); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, sourceV1, generator: generator); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); // change the source: - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); - var generatedDocument = (await workspace.CurrentSolution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single(); + var generatedDocument = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single(); - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None); AssertEx.Equal(new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.property_) }, diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1241,13 +1201,13 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("a.cs"); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); - var project = workspace.CurrentSolution. + var project = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)); - workspace.ChangeSolution(project.Solution); + solution = project.Solution; // compile with source0: var moduleId = EmitAndLoadLibraryToDebuggee(source0, sourceFilePath: sourceFile.Path); @@ -1257,10 +1217,9 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) // source1 is reflected in workspace before session starts: var document1 = project.AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path); - workspace.ChangeSolution(document1.Project.Solution); + solution = document1.Project.Solution; - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); if (breakMode) { @@ -1268,17 +1227,17 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) } // change the source (rude edit): - workspace.ChangeDocument(document1.Id, SourceText.From(source2)); - var document2 = workspace.CurrentSolution.GetDocument(document1.Id); + solution = solution.WithDocumentText(document1.Id, SourceText.From(source2)); + var document2 = solution.GetDocument(document1.Id); // no Rude Edits, since the document is out-of-sync - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // since the document is out-of-sync we need to call update to determine whether we have changes to apply or not: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(updates.Updates); AssertEx.Equal(new[] { $"{project.Id} Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}" }, InspectDiagnostics(emitDiagnostics)); @@ -1287,18 +1246,18 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) sourceFile.WriteAllText(source0); // we do not reload the content of out-of-sync file for analyzer query: - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // debugger query will trigger reload of out-of-sync file content: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); // now we see the rude edit: - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal(new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1337,40 +1296,38 @@ public async Task BreakMode_RudeEdits_DocumentWithoutSequencePoints() var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path); var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); - var service = GetEditAndContinueService(workspace); - // do not initialize the document state - we will detect the state based on the PDB content. - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // change the source (rude edit since the base document content matches the PDB checksum, so the document is not out-of-sync): - workspace.ChangeDocument(document1.Id, SourceText.From("abstract class C { public abstract void M(); public abstract void N(); }")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("abstract class C { public abstract void M(); public abstract void N(); }")); + var document2 = solution.Projects.Single().Documents.Single(); // Rude Edits reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( new[] { "ENC0023: " + string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1385,39 +1342,37 @@ public async Task BreakMode_RudeEdits_DelayLoadedModule() var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path); var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitLibrary(source1, sourceFilePath: sourceFile.Path); - var service = GetEditAndContinueService(workspace); - // do not initialize the document state - we will detect the state based on the PDB content. - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // change the source (rude edit) before the library is loaded: - workspace.ChangeDocument(document1.Id, SourceText.From("class C { public void Renamed() { } }")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C { public void Renamed() { } }")); + var document2 = solution.Projects.Single().Documents.Single(); // Rude Edits reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1426,14 +1381,14 @@ public async Task BreakMode_RudeEdits_DelayLoadedModule() LoadLibraryToDebuggee(moduleId); // Rude Edits still reported: - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1446,29 +1401,28 @@ public async Task BreakMode_SyntaxError() { var moduleId = Guid.NewGuid(); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }"); - var service = GetEditAndContinueService(workspace); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); // change the source (compilation error): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { ")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { ")); + var document2 = solution.Projects.Single().Documents.Single(); // compilation errors are not reported via EnC diagnostic analyzer: - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics1); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); @@ -1489,38 +1443,36 @@ public async Task BreakMode_SemanticError() { var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }"; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, sourceV1); var moduleId = EmitAndLoadLibraryToDebuggee(sourceV1); - var service = GetEditAndContinueService(workspace); - - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); // change the source (compilation error): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { int i = 0L; System.Console.WriteLine(i); } }", Encoding.UTF8)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { int i = 0L; System.Console.WriteLine(i); } }", Encoding.UTF8)); + var document2 = solution.Projects.Single().Documents.Single(); // compilation errors are not reported via EnC diagnostic analyzer: - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics1); // The EnC analyzer does not check for and block on all semantic errors as they are already reported by diagnostic analyzer. // Blocking update on semantic errors would be possible, but the status check is only an optimization to avoid emitting. - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); // TODO: https://github.com/dotnet/roslyn/issues/36061 // Semantic errors should not be reported in emit diagnostics. - AssertEx.Equal(new[] { $"{project.Id} Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}" }, InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal(new[] { $"{document2.Project.Id} Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}" }, InspectDiagnostics(emitDiagnostics)); EndDebuggingSession(service); @@ -1537,8 +1489,9 @@ public async Task BreakMode_SemanticError() [Fact] public async Task BreakMode_FileStatus_CompilationError() { - using var workspace = CreateWorkspace(); - workspace.ChangeSolution(workspace.CurrentSolution. + using var _ = CreateWorkspace(out var solution, out var service); + + solution = solution. AddProject("A", "A", "C#"). AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: "A.cs").Project.Solution. AddProject("B", "B", "C#"). @@ -1546,26 +1499,24 @@ public async Task BreakMode_FileStatus_CompilationError() AddDocument("B.cs", "class B {}", filePath: "B.cs").Project.Solution. AddProject("C", "C", "C#"). AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. - AddDocument("C.cs", "class C {}", filePath: "C.cs").Project.Solution); + AddDocument("C.cs", "class C {}", filePath: "C.cs").Project.Solution; - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); // change C.cs to have a compilation error: - var projectC = workspace.CurrentSolution.GetProjectsByName("C").Single(); + var projectC = solution.GetProjectsByName("C").Single(); var documentC = projectC.Documents.Single(d => d.Name == "C.cs"); - workspace.ChangeDocument(documentC.Id, SourceText.From("class C { void M() { ")); + solution = solution.WithDocumentText(documentC.Id, SourceText.From("class C { void M() { ")); // Common.cs is included in projects B and C. Both of these projects must have no errors, otherwise update is blocked. - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: "Common.cs", CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: "Common.cs", CancellationToken.None)); // No changes in project containing file B.cs. - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: "B.cs", CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: "B.cs", CancellationToken.None)); // All projects must have no errors. - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); EndDebuggingSession(service); } @@ -1575,31 +1526,29 @@ public async Task BreakMode_ValidSignificantChange_EmitError() { var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }"; - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); - var project = AddDefaultTestProject(workspace, sourceV1); + (solution, var document) = AddDefaultTestProject(solution, sourceV1); EmitAndLoadLibraryToDebuggee(sourceV1); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); // change the source (valid edit but passing no encoding to emulate emit error): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", encoding: null)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", encoding: null)); + var document2 = solution.Projects.Single().Documents.Single(); - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics1); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); - AssertEx.Equal(new[] { $"{project.Id} Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}" }, InspectDiagnostics(emitDiagnostics)); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); + AssertEx.Equal(new[] { $"{document2.Project.Id} Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}" }, InspectDiagnostics(emitDiagnostics)); // no emitted delta: Assert.Empty(updates.Updates); @@ -1614,7 +1563,7 @@ public async Task BreakMode_ValidSignificantChange_EmitError() Assert.Empty(editSession.DebuggingSession.NonRemappableRegions); // solution update status after discarding an update (still has update ready): - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); EndDebuggingSession(service); @@ -1649,30 +1598,27 @@ public async Task BreakMode_ValidSignificantChange_ApplyBeforeFileWatcherEvent(b var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)). AddDocument("test.cs", SourceText.From("class C1 { void M() { System.Console.WriteLine(0); } }", Encoding.UTF8), filePath: sourceFile.Path); var documentId = document1.Id; - - var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = document1.Project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // The user opens the source file and changes the source before Roslyn receives file watcher event. var source2 = "class C1 { void M() { System.Console.WriteLine(2); } }"; - workspace.ChangeDocument(documentId, SourceText.From(source2, Encoding.UTF8)); - var document2 = workspace.CurrentSolution.GetDocument(documentId); + solution = solution.WithDocumentText(documentId, SourceText.From(source2, Encoding.UTF8)); + var document2 = solution.GetDocument(documentId); // Save the document: if (saveDocument) @@ -1682,8 +1628,8 @@ public async Task BreakMode_ValidSignificantChange_ApplyBeforeFileWatcherEvent(b } // EnC service queries for a document, which triggers read of the source file from disk. - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -1694,11 +1640,11 @@ public async Task BreakMode_ValidSignificantChange_ApplyBeforeFileWatcherEvent(b EnterBreakState(service); // file watcher updates the workspace: - workspace.ChangeDocument(documentId, CreateSourceTextFromFile(sourceFile.Path)); - var document3 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(documentId, CreateSourceTextFromFile(sourceFile.Path)); + var document3 = solution.Projects.Single().Documents.Single(); - var hasChanges = await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var hasChanges = await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); if (saveDocument) @@ -1732,10 +1678,10 @@ public async Task BreakMode_ValidSignificantChange_FileUpdateNotObservedBeforeDe var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("test.cs").WriteAllText(source2); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): - var document2 = workspace.CurrentSolution. + var document2 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", SourceText.From(source2, Encoding.UTF8), filePath: sourceFile.Path); @@ -1743,36 +1689,35 @@ public async Task BreakMode_ValidSignificantChange_FileUpdateNotObservedBeforeDe var documentId = document2.Id; var project = document2.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // user edits the file: - workspace.ChangeDocument(documentId, SourceText.From(source3, Encoding.UTF8)); - var document3 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(documentId, SourceText.From(source3, Encoding.UTF8)); + var document3 = solution.Projects.Single().Documents.Single(); // EnC service queries for a document, but the source file on disk doesn't match the PDB // We don't report rude edits for out-of-sync documents: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); // since the document is out-of-sync we need to call update to determine whether we have changes to apply or not: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); AssertEx.Equal(new[] { $"{project.Id} Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}" }, InspectDiagnostics(emitDiagnostics)); // undo: - workspace.ChangeDocument(documentId, SourceText.From(source1, Encoding.UTF8)); + solution = solution.WithDocumentText(documentId, SourceText.From(source1, Encoding.UTF8)); - var currentDocument = workspace.CurrentSolution.GetDocument(documentId); + var currentDocument = solution.GetDocument(documentId); // save (note that this call will fail to match the content with the PDB since it uses the content prior to the actual file write) await debuggingSession.LastCommittedSolution.OnSourceFileUpdatedAsync(currentDocument, debuggingSession.CancellationToken); @@ -1781,8 +1726,8 @@ public async Task BreakMode_ValidSignificantChange_FileUpdateNotObservedBeforeDe Assert.Equal(CommittedSolution.DocumentState.OutOfSync, state); sourceFile.WriteAllText(source1); - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); // the content actually hasn't changed: @@ -1804,22 +1749,20 @@ public async Task BreakMode_ValidSignificantChange_AddedFileNotObservedBeforeDeb var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1); - using var workspace = new TestWorkspace(composition: s_composition); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with no file - var project = workspace.CurrentSolution. + var project = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)); - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); - var service = GetEditAndContinueService(workspace); - _debuggerService.IsEditAndContinueAvailable = _ => new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.Attach, localizedMessage: "*attached*"); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); // An active statement may be present in the added file since the file exists in the PDB: var activeInstruction1 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1); @@ -1839,21 +1782,21 @@ public async Task BreakMode_ValidSignificantChange_AddedFileNotObservedBeforeDeb // File watcher observes the document and adds it to the workspace: var document1 = project.AddDocument("test.cs", sourceText1, filePath: sourceFile.Path); - workspace.ChangeSolution(document1.Project.Solution); + solution = document1.Project.Solution; // We don't report rude edits for the added document: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); // TODO: https://github.com/dotnet/roslyn/issues/49938 // We currently create the AS map against the committed solution, which may not contain all documents. - // var spans = await service.GetBaseActiveStatementSpansAsync(workspace.CurrentSolution, ImmutableArray.Create(document1.Id), CancellationToken.None); + // var spans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None); // AssertEx.Equal(new[] { $"({activeLineSpan1}, IsLeafFrame)" }, spans.Single().Select(s => s.ToString())); // No changes. - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); AssertEx.Empty(emitDiagnostics); @@ -1870,16 +1813,16 @@ public async Task BreakMode_ValidSignificantChange_DocumentOutOfSync(bool delayL var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("test.cs").WriteAllText(sourceOnDisk); - using var workspace = CreateWorkspace(); + using var _ = CreateWorkspace(out var solution, out var service); // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): - var document1 = workspace.CurrentSolution. + var document1 = solution. AddProject("test", "test", LanguageNames.CSharp). AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", SourceText.From("class C1 { void M() { System.Console.WriteLine(0); } }", Encoding.UTF8), filePath: sourceFile.Path); var project = document1.Project; - workspace.ChangeSolution(project.Solution); + solution = project.Solution; var moduleId = EmitLibrary(sourceOnDisk, sourceFilePath: sourceFile.Path); @@ -1888,31 +1831,29 @@ public async Task BreakMode_ValidSignificantChange_DocumentOutOfSync(bool delayL LoadLibraryToDebuggee(moduleId); } - var service = GetEditAndContinueService(workspace); - - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution, initialState: CommittedSolution.DocumentState.None); + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None); EnterBreakState(service); // no changes have been made to the project - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); // a file watcher observed a change and updated the document, so it now reflects the content on disk (the code that we compiled): - workspace.ChangeDocument(document1.Id, SourceText.From(sourceOnDisk, Encoding.UTF8)); - var document3 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From(sourceOnDisk, Encoding.UTF8)); + var document3 = solution.Projects.Single().Documents.Single(); - var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); // the content of the file is now exactly the same as the compiled document, so there is no change to be applied: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.None, updates.Status); Assert.Empty(emitDiagnostics); @@ -1928,13 +1869,12 @@ public async Task ValidSignificantChange_EmitSuccessful(bool breakMode, bool com var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }"; var sourceV2 = "class C1 { void M() { System.Console.WriteLine(2); } }"; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1); var moduleId = EmitAndLoadLibraryToDebuggee(sourceV1); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); if (breakMode) { @@ -1944,17 +1884,16 @@ public async Task ValidSignificantChange_EmitSuccessful(bool breakMode, bool com var editSession = service.GetTestAccessor().GetEditSession(); // change the source (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); + var document2 = solution.GetDocument(document1.Id); - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics1); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); ValidateDelta(updates.Updates.Single()); @@ -1976,7 +1915,7 @@ void ValidateDelta(ManagedModuleUpdate delta) var pendingUpdate = editSession.GetTestAccessor().GetPendingSolutionUpdate(); var (baselineProjectId, newBaseline) = pendingUpdate.EmitBaselines.Single(); AssertEx.Equal(updates.Updates, pendingUpdate.Deltas); - Assert.Equal(project.Id, baselineProjectId); + Assert.Equal(document2.Project.Id, baselineProjectId); Assert.Equal(moduleId, newBaseline.OriginalMetadata.GetModuleVersionId()); var readers = debuggingSession.GetTestAccessor().GetBaselineModuleReaders(); @@ -2000,10 +1939,10 @@ void ValidateDelta(ManagedModuleUpdate delta) Assert.Same(readers[1], baselineReaders[1]); // verify that baseline is added: - Assert.Same(newBaseline, editSession.DebuggingSession.GetTestAccessor().GetProjectEmitBaseline(project.Id)); + Assert.Same(newBaseline, editSession.DebuggingSession.GetTestAccessor().GetProjectEmitBaseline(document2.Project.Id)); // solution update status after committing an update: - var commitedUpdateSolutionStatus = await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None); + var commitedUpdateSolutionStatus = await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None); Assert.False(commitedUpdateSolutionStatus); } else @@ -2014,10 +1953,10 @@ void ValidateDelta(ManagedModuleUpdate delta) Assert.Null(editSession.GetTestAccessor().GetPendingSolutionUpdate()); // solution update status after committing an update: - var discardedUpdateSolutionStatus = await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None); + var discardedUpdateSolutionStatus = await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None); Assert.True(discardedUpdateSolutionStatus); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2069,10 +2008,8 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred var pdbFile = dir.CreateFile("lib.pdb").WriteAllBytes(pdbImage); var moduleId = moduleMetadata.GetModuleVersionId(); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1); - - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1); _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path, pdbFile.Path); @@ -2083,9 +2020,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred sourceSpan: new SourceSpan(0, 15, 0, 16), ActiveStatementFlags.IsLeafFrame)); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); var debuggingSession = service.GetTestAccessor().GetDebuggingSession(); // module is not loaded: @@ -2093,13 +2028,13 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred var editSession = service.GetTestAccessor().GetEditSession(); // change the source (valid edit): - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M1() { int a = 1; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }", Encoding.UTF8)); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M1() { int a = 1; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }", Encoding.UTF8)); + var document2 = solution.GetDocument(document1.Id); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); Assert.Empty(emitDiagnostics); @@ -2123,7 +2058,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred Assert.NotNull(readers[0]); Assert.NotNull(readers[1]); - Assert.Equal(project.Id, baselineProjectId); + Assert.Equal(document2.Project.Id, baselineProjectId); Assert.Equal(moduleId, newBaseline.OriginalMetadata.GetModuleVersionId()); if (commitUpdate) @@ -2135,10 +2070,10 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred Assert.Empty(editSession.DebuggingSession.NonRemappableRegions); // verify that baseline is added: - Assert.Same(newBaseline, editSession.DebuggingSession.GetTestAccessor().GetProjectEmitBaseline(project.Id)); + Assert.Same(newBaseline, editSession.DebuggingSession.GetTestAccessor().GetProjectEmitBaseline(document2.Project.Id)); // solution update status after committing an update: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); ExitBreakState(); @@ -2148,10 +2083,10 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred // Update M1 - this method has an active statement, so we will attempt to preserve the local signature. // Since the method hasn't been edited before we'll read the baseline PDB to get the signature token. // This validates that the Portable PDB reader can be used (and is not disposed) for a second generation edit. - var document3 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document3.Id, SourceText.From("class C1 { void M1() { int a = 3; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }", Encoding.UTF8)); + var document3 = solution.GetDocument(document1.Id); + solution = solution.WithDocumentText(document3.Id, SourceText.From("class C1 { void M1() { int a = 3; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }", Encoding.UTF8)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); Assert.Empty(emitDiagnostics); } @@ -2197,13 +2132,13 @@ partial class C { int Y = 2; } partial class E { int B = 2; public E(int a, int b) { A = a; B = new System.Func(() => b)(); } } "; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, new[] { sourceA1, sourceB1 }); + using var _ = CreateWorkspace(out var solution, out var service); + solution = AddDefaultTestProject(solution, new[] { sourceA1, sourceB1 }); + var project = solution.Projects.Single(); LoadLibraryToDebuggee(EmitLibrary(new[] { (sourceA1, "test1.cs"), (sourceB1, "test2.cs") })); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); @@ -2211,11 +2146,11 @@ partial class C { int Y = 2; } // change the source (valid edit): var documentA = project.Documents.First(); var documentB = project.Documents.Skip(1).First(); - workspace.ChangeDocument(documentA.Id, SourceText.From(sourceA2, Encoding.UTF8)); - workspace.ChangeDocument(documentB.Id, SourceText.From(sourceB2, Encoding.UTF8)); + solution = solution.WithDocumentText(documentA.Id, SourceText.From(sourceA2, Encoding.UTF8)); + solution = solution.WithDocumentText(documentB.Id, SourceText.From(sourceB2, Encoding.UTF8)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2256,9 +2191,6 @@ static string EncLogRowToString(EditAndContinueLogEntry row) private static void GenerateSource(GeneratorExecutionContext context) { - const string OpeningMarker = "/* GENERATE:"; - const string ClosingMarker = "*/"; - foreach (var syntaxTree in context.Compilation.SyntaxTrees) { var fileName = PathUtilities.GetFileName(syntaxTree.FilePath); @@ -2278,16 +2210,30 @@ private static void GenerateSource(GeneratorExecutionContext context) void Generate(string source, string fileName) { - var index = source.IndexOf(OpeningMarker); - if (index > 0) + var generatedSource = GetGeneratedCodeFromMarkedSource(source); + if (generatedSource != null) { - index += OpeningMarker.Length; - var closing = source.IndexOf(ClosingMarker, index); - context.AddSource($"Generated_{fileName}", source[index..closing].Trim()); + context.AddSource($"Generated_{fileName}", generatedSource); } } } + private static string GetGeneratedCodeFromMarkedSource(string markedSource) + { + const string OpeningMarker = "/* GENERATE:"; + const string ClosingMarker = "*/"; + + var index = markedSource.IndexOf(OpeningMarker); + if (index > 0) + { + index += OpeningMarker.Length; + var closing = markedSource.IndexOf(ClosingMarker, index); + return markedSource[index..closing].Trim(); + } + + return null; + } + [Fact] public async Task BreakMode_ValidSignificantChange_SourceGenerators_DocumentUpdate_GeneratedDocumentUpdate() { @@ -2304,24 +2250,22 @@ class C { int Y => 2; } var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1, generator); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator); var moduleId = EmitLibrary(sourceV1, generator: generator); LoadLibraryToDebuggee(moduleId); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); - // change the source (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); + // change the source (valid edit) + solution = solution.WithDocumentText(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2365,24 +2309,22 @@ int M() var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1, generator); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator); var moduleId = EmitLibrary(sourceV1, generator: generator); LoadLibraryToDebuggee(moduleId); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); // change the source (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); + solution = solution.WithDocumentText(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2414,24 +2356,22 @@ partial class C { int X = 1; } var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1, generator); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator); var moduleId = EmitLibrary(sourceV1, generator: generator); LoadLibraryToDebuggee(moduleId); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); // change the source (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); + solution = solution.WithDocumentText(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2462,24 +2402,23 @@ class C { int Y => 1; } var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, source, generator, additionalFileText: additionalSourceV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, source, generator, additionalFileText: additionalSourceV1); var moduleId = EmitLibrary(source, generator: generator, additionalFileText: additionalSourceV1); LoadLibraryToDebuggee(moduleId); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); // change the additional source (valid edit): - var additionalDocument1 = workspace.CurrentSolution.Projects.Single().AdditionalDocuments.Single(); - workspace.ChangeAdditionalDocument(additionalDocument1.Id, SourceText.From(additionalSourceV2, Encoding.UTF8)); + var additionalDocument1 = solution.Projects.Single().AdditionalDocuments.Single(); + solution = solution.WithAdditionalDocumentText(additionalDocument1.Id, SourceText.From(additionalSourceV2, Encoding.UTF8)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2506,24 +2445,23 @@ class C { int Y => 1; } var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, source, generator, analyzerConfig: configV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, source, generator, analyzerConfig: configV1); var moduleId = EmitLibrary(source, generator: generator, analyzerOptions: configV1); LoadLibraryToDebuggee(moduleId); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); // change the additional source (valid edit): - var configDocument1 = workspace.CurrentSolution.Projects.Single().AnalyzerConfigDocuments.Single(); - workspace.ChangeAnalyzerConfigDocument(configDocument1.Id, GetAnalyzerConfigText(configV2)); + var configDocument1 = solution.Projects.Single().AnalyzerConfigDocuments.Single(); + solution = solution.WithAnalyzerConfigDocumentText(configDocument1.Id, GetAnalyzerConfigText(configV2)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2548,24 +2486,22 @@ public async Task BreakMode_ValidSignificantChange_SourceGenerators_DocumentRemo ExecuteImpl = context => context.AddSource("generated", $"class G {{ int X => {context.Compilation.SyntaxTrees.Count()}; }}") }; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, source1, generator); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, source1, generator); var moduleId = EmitLibrary(source1, generator: generator); LoadLibraryToDebuggee(moduleId); - var service = GetEditAndContinueService(workspace); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); EnterBreakState(service); var editSession = service.GetTestAccessor().GetEditSession(); // remove the source document (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeSolution(document1.Project.Solution.RemoveDocument(document1.Id)); + solution = document1.Project.Solution.RemoveDocument(document1.Id); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Empty(emitDiagnostics); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -2607,11 +2543,12 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() var pdbFileB = dir.CreateFile("B.pdb").WriteAllBytes(pdbImageB); var moduleIdB = moduleMetadataB.GetModuleVersionId(); - using var workspace = CreateWorkspace(); - var projectA = AddDefaultTestProject(workspace, source1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var documentA) = AddDefaultTestProject(solution, source1); + var projectA = documentA.Project; - var projectB = workspace.CurrentSolution.AddProject("B", "A", "C#").AddMetadataReferences(projectA.MetadataReferences).AddDocument("DocB", source1, filePath: "DocB.cs").Project; - workspace.ChangeSolution(projectB.Solution); + var projectB = solution.AddProject("B", "A", "C#").AddMetadataReferences(projectA.MetadataReferences).AddDocument("DocB", source1, filePath: "DocB.cs").Project; + solution = projectB.Solution; _mockCompilationOutputsProvider = project => (project.Id == projectA.Id) ? new CompilationOutputFiles(moduleFileA.Path, pdbFileA.Path) : @@ -2621,9 +2558,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() // only module A is loaded LoadLibraryToDebuggee(moduleIdA); - var service = GetEditAndContinueService(workspace); - - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); var debuggingSession = service.GetTestAccessor().GetDebuggingSession(); EnterBreakState(service); @@ -2633,13 +2568,13 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() // First update. // - workspace.ChangeDocument(projectA.Documents.Single().Id, SourceText.From(source2, Encoding.UTF8)); - workspace.ChangeDocument(projectB.Documents.Single().Id, SourceText.From(source2, Encoding.UTF8)); + solution = solution.WithDocumentText(projectA.Documents.Single().Id, SourceText.From(source2, Encoding.UTF8)); + solution = solution.WithDocumentText(projectB.Documents.Single().Id, SourceText.From(source2, Encoding.UTF8)); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); Assert.Empty(emitDiagnostics); @@ -2673,7 +2608,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() Assert.Same(newBaselineB1, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(projectB.Id)); // solution update status after committing an update: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); ExitBreakState(); EnterBreakState(service); @@ -2683,13 +2618,13 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() // Second update. // - workspace.ChangeDocument(projectA.Documents.Single().Id, SourceText.From(source3, Encoding.UTF8)); - workspace.ChangeDocument(projectB.Documents.Single().Id, SourceText.From(source3, Encoding.UTF8)); + solution = solution.WithDocumentText(projectA.Documents.Single().Id, SourceText.From(source3, Encoding.UTF8)); + solution = solution.WithDocumentText(projectB.Documents.Single().Id, SourceText.From(source3, Encoding.UTF8)); // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.True(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); + (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); Assert.Empty(emitDiagnostics); @@ -2728,7 +2663,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() Assert.Same(newBaselineB2, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(projectB.Id)); // solution update status after committing an update: - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None)); + Assert.False(await service.HasChangesAsync(solution, s_noActiveSpans, sourceFilePath: null, CancellationToken.None)); ExitBreakState(); EndDebuggingSession(service); @@ -2740,31 +2675,26 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() [Fact] public async Task BreakMode_ValidSignificantChange_BaselineCreationFailed_NoStream() { - using (var workspace = CreateWorkspace()) - { - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }"); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.NewGuid()) - { - OpenPdbStreamImpl = () => null, - OpenAssemblyStreamImpl = () => null, - }; - - var service = GetEditAndContinueService(workspace); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.NewGuid()) + { + OpenPdbStreamImpl = () => null, + OpenAssemblyStreamImpl = () => null, + }; - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); - // module not loaded - EnterBreakState(service); + // module not loaded + EnterBreakState(service); - // change the source (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); + // change the source (valid edit): + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); - AssertEx.Equal(new[] { $"{project.Id} Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}" }, InspectDiagnostics(emitDiagnostics)); - Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); - } + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); + AssertEx.Equal(new[] { $"{document1.Project.Id} Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}" }, InspectDiagnostics(emitDiagnostics)); + Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); } [Fact] @@ -2777,40 +2707,36 @@ public async Task BreakMode_ValidSignificantChange_BaselineCreationFailed_Assemb var peImage = compilationV1.EmitToArray(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb), pdbStream: pdbStream); pdbStream.Position = 0; - using (var workspace = CreateWorkspace()) - { - var project = AddDefaultTestProject(workspace, sourceV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, sourceV1); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.NewGuid()) - { - OpenPdbStreamImpl = () => pdbStream, - OpenAssemblyStreamImpl = () => throw new IOException("*message*"), - }; - - var service = GetEditAndContinueService(workspace); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.NewGuid()) + { + OpenPdbStreamImpl = () => pdbStream, + OpenAssemblyStreamImpl = () => throw new IOException("*message*"), + }; - await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + await StartDebuggingSessionAsync(service, solution); - // module not loaded - EnterBreakState(service); + // module not loaded + EnterBreakState(service); - // change the source (valid edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); + // change the source (valid edit): + var document1 = solution.Projects.Single().Documents.Single(); + solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, workspace.CurrentSolution); - AssertEx.Equal(new[] { $"{project.Id} Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}" }, InspectDiagnostics(emitDiagnostics)); - Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); + AssertEx.Equal(new[] { $"{document.Project.Id} Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}" }, InspectDiagnostics(emitDiagnostics)); + Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); - EndDebuggingSession(service); + EndDebuggingSession(service); - AssertEx.Equal(new[] - { + AssertEx.Equal(new[] + { "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1", "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC1001" }, _telemetryLog); - } } [Fact] @@ -2819,8 +2745,8 @@ public async Task ActiveStatements() var sourceV1 = "class C { void F() { G(1); } void G(int a) => System.Console.WriteLine(1); }"; var sourceV2 = "class C { int x; void F() { G(2); G(1); } void G(int a) => System.Console.WriteLine(2); }"; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1); var activeSpan11 = GetSpan(sourceV1, "G(1);"); var activeSpan12 = GetSpan(sourceV1, "System.Console.WriteLine(1)"); @@ -2829,7 +2755,6 @@ public async Task ActiveStatements() var adjustedActiveSpan1 = GetSpan(sourceV2, "G(2);"); var adjustedActiveSpan2 = GetSpan(sourceV2, "System.Console.WriteLine(2)"); - var document1 = project.Documents.Single(); var documentId = document1.Id; var documentPath = document1.FilePath; @@ -2843,15 +2768,13 @@ public async Task ActiveStatements() var adjustedActiveLineSpan1 = sourceTextV2.Lines.GetLinePositionSpan(adjustedActiveSpan1); var adjustedActiveLineSpan2 = sourceTextV2.Lines.GetLinePositionSpan(adjustedActiveSpan2); - var service = GetEditAndContinueService(workspace); - // default if not called in a break state - Assert.True((await service.GetBaseActiveStatementSpansAsync(workspace.CurrentSolution, ImmutableArray.Create(document1.Id), CancellationToken.None)).IsDefault); + Assert.True((await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None)).IsDefault); - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); // default if not called in a break state - Assert.True((await service.GetBaseActiveStatementSpansAsync(workspace.CurrentSolution, ImmutableArray.Create(document1.Id), CancellationToken.None)).IsDefault); + Assert.True((await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None)).IsDefault); var moduleId = Guid.NewGuid(); var activeInstruction1 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1); @@ -2872,47 +2795,44 @@ public async Task ActiveStatements() EnterBreakState(service, activeStatements); var editSession = service.GetTestAccessor().GetEditSession(); - var baseSpans = await service.GetBaseActiveStatementSpansAsync(workspace.CurrentSolution, ImmutableArray.Create(document1.Id), CancellationToken.None); + var activeStatementSpan11 = new ActiveStatementSpan(0, activeLineSpan11, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null); + var activeStatementSpan12 = new ActiveStatementSpan(1, activeLineSpan12, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null); + + var baseSpans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None); AssertEx.Equal(new[] { - (activeLineSpan11, ActiveStatementFlags.IsNonLeafFrame), - (activeLineSpan12, ActiveStatementFlags.IsLeafFrame) + activeStatementSpan11, + activeStatementSpan12 }, baseSpans.Single()); - var trackedActiveSpans1 = ImmutableArray.Create(activeSpan11, activeSpan12); + var trackedActiveSpans1 = ImmutableArray.Create(activeStatementSpan11, activeStatementSpan12); - var currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document1, (_) => new(trackedActiveSpans1), CancellationToken.None); - AssertEx.Equal(new[] - { - (activeLineSpan11, ActiveStatementFlags.IsNonLeafFrame), - (activeLineSpan12, ActiveStatementFlags.IsLeafFrame) - }, currentSpans); + var currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document1, (_, _, _) => new(trackedActiveSpans1), CancellationToken.None); + AssertEx.Equal(trackedActiveSpans1, currentSpans); Assert.Equal(activeLineSpan11, - await service.GetCurrentActiveStatementPositionAsync(document1.Project.Solution, (_, _) => new(trackedActiveSpans1), activeInstruction1, CancellationToken.None)); + await service.GetCurrentActiveStatementPositionAsync(document1.Project.Solution, (_, _, _) => new(trackedActiveSpans1), activeInstruction1, CancellationToken.None)); Assert.Equal(activeLineSpan12, - await service.GetCurrentActiveStatementPositionAsync(document1.Project.Solution, (_, _) => new(trackedActiveSpans1), activeInstruction2, CancellationToken.None)); + await service.GetCurrentActiveStatementPositionAsync(document1.Project.Solution, (_, _, _) => new(trackedActiveSpans1), activeInstruction2, CancellationToken.None)); // change the source (valid edit): - workspace.ChangeDocument(documentId, sourceTextV2); - var document2 = workspace.CurrentSolution.GetDocument(documentId); + solution = solution.WithDocumentText(documentId, sourceTextV2); + var document2 = solution.GetDocument(documentId); // tracking span update triggered by the edit: - var trackedActiveSpans2 = ImmutableArray.Create(activeSpan21, activeSpan22); + var activeStatementSpan21 = new ActiveStatementSpan(0, activeLineSpan21, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null); + var activeStatementSpan22 = new ActiveStatementSpan(1, activeLineSpan22, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null); + var trackedActiveSpans2 = ImmutableArray.Create(activeStatementSpan21, activeStatementSpan22); - currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document2, _ => new(trackedActiveSpans2), CancellationToken.None); - AssertEx.Equal(new[] - { - (adjustedActiveLineSpan1, ActiveStatementFlags.IsNonLeafFrame), - (adjustedActiveLineSpan2, ActiveStatementFlags.IsLeafFrame) - }, currentSpans); + currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document2, (_, _, _) => new(trackedActiveSpans2), CancellationToken.None); + AssertEx.Equal(new[] { adjustedActiveLineSpan1, adjustedActiveLineSpan2 }, currentSpans.Select(s => s.LineSpan)); Assert.Equal(adjustedActiveLineSpan1, - await service.GetCurrentActiveStatementPositionAsync(workspace.CurrentSolution, (_, _) => new(trackedActiveSpans2), activeInstruction1, CancellationToken.None)); + await service.GetCurrentActiveStatementPositionAsync(solution, (_, _, _) => new(trackedActiveSpans2), activeInstruction1, CancellationToken.None)); Assert.Equal(adjustedActiveLineSpan2, - await service.GetCurrentActiveStatementPositionAsync(workspace.CurrentSolution, (_, _) => new(trackedActiveSpans2), activeInstruction2, CancellationToken.None)); + await service.GetCurrentActiveStatementPositionAsync(solution, (_, _, _) => new(trackedActiveSpans2), activeInstruction2, CancellationToken.None)); } [Theory] @@ -2926,13 +2846,12 @@ public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSy "class C { int x; void F() => G(1); void G(int a) => System.Console.WriteLine(2); }" : "class C { int x void F() => G(1); void G(int a) => System.Console.WriteLine(2); }"; - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, sourceV1); + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, sourceV1); var activeSpan11 = GetSpan(sourceV1, "G(1)"); var activeSpan12 = GetSpan(sourceV1, "System.Console.WriteLine(1)"); - var document1 = project.Documents.Single(); var documentId = document1.Id; var documentFilePath = document1.FilePath; @@ -2941,11 +2860,10 @@ public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSy var activeLineSpan11 = sourceTextV1.Lines.GetLinePositionSpan(activeSpan11); var activeLineSpan12 = sourceTextV1.Lines.GetLinePositionSpan(activeSpan12); - var service = GetEditAndContinueService(workspace); var debuggingSession = await StartDebuggingSessionAsync( service, - workspace.CurrentSolution, + solution, isOutOfSync ? CommittedSolution.DocumentState.OutOfSync : CommittedSolution.DocumentState.MatchesBuildOutput); var moduleId = Guid.NewGuid(); @@ -2967,31 +2885,33 @@ public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSy EnterBreakState(service, activeStatements); var editSession = service.GetTestAccessor().GetEditSession(); - var baseSpans = await service.GetBaseActiveStatementSpansAsync(workspace.CurrentSolution, ImmutableArray.Create(documentId), CancellationToken.None); + var baseSpans = (await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); + AssertEx.Equal(new[] + { + new ActiveStatementSpan(0, activeLineSpan11, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null), + new ActiveStatementSpan(1, activeLineSpan12, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null) + }, baseSpans); + + // change the source (valid edit): + solution = solution.WithDocumentText(documentId, sourceTextV2); + var document2 = solution.GetDocument(documentId); + + // no adjustments made due to syntax error or out-of-sync document: + var currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document2, (_, _, _) => ValueTaskFactory.FromResult(baseSpans), CancellationToken.None); + AssertEx.Equal(new[] { activeLineSpan11, activeLineSpan12 }, currentSpans.Select(s => s.LineSpan)); + var currentSpan1 = await service.GetCurrentActiveStatementPositionAsync(solution, (_, _, _) => ValueTaskFactory.FromResult(baseSpans), activeInstruction1, CancellationToken.None); + var currentSpan2 = await service.GetCurrentActiveStatementPositionAsync(solution, (_, _, _) => ValueTaskFactory.FromResult(baseSpans), activeInstruction2, CancellationToken.None); if (isOutOfSync) { - Assert.Empty(baseSpans.Single()); + Assert.Equal(baseSpans[0].LineSpan, currentSpan1.Value); + Assert.Equal(baseSpans[1].LineSpan, currentSpan2.Value); } else { - AssertEx.Equal(new[] - { - (activeLineSpan11, ActiveStatementFlags.IsNonLeafFrame), - (activeLineSpan12, ActiveStatementFlags.IsLeafFrame) - }, baseSpans.Single()); + Assert.Null(currentSpan1); + Assert.Null(currentSpan2); } - - // change the source (valid edit): - workspace.ChangeDocument(documentId, sourceTextV2); - var document2 = workspace.CurrentSolution.GetDocument(documentId); - - // no active statements due to syntax error or out-of-sync document: - var currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document2, s_noDocumentActiveSpans, CancellationToken.None); - Assert.True(currentSpans.IsDefault); - - Assert.Null(await service.GetCurrentActiveStatementPositionAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, activeInstruction1, CancellationToken.None)); - Assert.Null(await service.GetCurrentActiveStatementPositionAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, activeInstruction2, CancellationToken.None)); } [Fact] @@ -2999,15 +2919,13 @@ public async Task ActiveStatements_ForeignDocument() { var composition = FeaturesTestCompositions.Features.AddParts(typeof(DummyLanguageService)); - using var workspace = new TestWorkspace(composition: composition); - var solution = workspace.CurrentSolution; + using var _ = CreateWorkspace(out var solution, out var service, new[] { typeof(DummyLanguageService) }); + var project = solution.AddProject("dummy_proj", "dummy_proj", DummyLanguageService.LanguageName); var document = project.AddDocument("test", SourceText.From("dummy1")); - workspace.ChangeSolution(document.Project.Solution); - - var service = GetEditAndContinueService(workspace); + solution = document.Project.Solution; - var debuggingSession = await StartDebuggingSessionAsync(service, workspace.CurrentSolution); + var debuggingSession = await StartDebuggingSessionAsync(service, solution); var activeStatements = ImmutableArray.Create( new ManagedActiveStatementDebugInfo( @@ -3018,14 +2936,290 @@ public async Task ActiveStatements_ForeignDocument() EnterBreakState(service, activeStatements); - // active statements are tracked not in non-Roslyn projects: - var currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document, s_noDocumentActiveSpans, CancellationToken.None); - Assert.True(currentSpans.IsDefault); + // active statements are not tracked in non-Roslyn projects: + var currentSpans = await service.GetAdjustedActiveStatementSpansAsync(document, s_noActiveSpans, CancellationToken.None); + Assert.Empty(currentSpans); - var baseSpans = await service.GetBaseActiveStatementSpansAsync(workspace.CurrentSolution, ImmutableArray.Create(document.Id), CancellationToken.None); + var baseSpans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None); Assert.Empty(baseSpans.Single()); } + [Fact, WorkItem(24320, "https://github.com/dotnet/roslyn/issues/24320")] + public async Task ActiveStatements_LinkedDocuments() + { + var markedSources = new[] + { +@"class Test1 +{ + static void Main() => Project2::Test1.F(); + static void F() => Project4::Test2.M(); +}", +@"class Test2 { static void M() => Console.WriteLine(); }" + }; + + var module1 = Guid.NewGuid(); + var module2 = Guid.NewGuid(); + var module4 = Guid.NewGuid(); + + var debugInfos = GetActiveStatementDebugInfosCSharp( + markedSources, + methodRowIds: new[] { 1, 2, 1 }, + modules: new[] { module4, module2, module1 }); + + // Project1: Test1.cs, Test2.cs + // Project2: Test1.cs (link from P1) + // Project3: Test1.cs (link from P1) + // Project4: Test2.cs (link from P1) + + using var _ = CreateWorkspace(out var solution, out var service); + solution = AddDefaultTestProject(solution, ActiveStatementsDescription.ClearTags(markedSources)); + + var documents = solution.Projects.Single().Documents; + var doc1 = documents.First(); + var doc2 = documents.Skip(1).First(); + var text1 = await doc1.GetTextAsync(); + var text2 = await doc2.GetTextAsync(); + + DocumentId AddProjectAndLinkDocument(string projectName, Document doc, SourceText text) + { + var p = solution.AddProject(projectName, projectName, "C#"); + var linkedDocId = DocumentId.CreateNewId(p.Id, projectName + "->" + doc.Name); + solution = p.Solution.AddDocument(linkedDocId, doc.Name, text, filePath: doc.FilePath); + return linkedDocId; + } + + var docId3 = AddProjectAndLinkDocument("Project2", doc1, text1); + var docId4 = AddProjectAndLinkDocument("Project3", doc1, text1); + var docId5 = AddProjectAndLinkDocument("Project4", doc2, text2); + + var debuggingSession = await StartDebuggingSessionAsync(service, solution); + EnterBreakState(service, debugInfos); + + // Base Active Statements + + var baseActiveStatementsMap = await debuggingSession.EditSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); + var documentMap = baseActiveStatementsMap.DocumentPathMap; + + Assert.Equal(2, documentMap.Count); + + AssertEx.Equal(new[] + { + $"2: {doc1.FilePath}: (2,32)-(2,52) flags=[MethodUpToDate, IsNonLeafFrame]", + $"1: {doc1.FilePath}: (3,29)-(3,49) flags=[MethodUpToDate, IsNonLeafFrame]" + }, documentMap[doc1.FilePath].Select(InspectActiveStatement)); + + AssertEx.Equal(new[] + { + $"0: {doc2.FilePath}: (0,39)-(0,59) flags=[IsLeafFrame, MethodUpToDate]", + }, documentMap[doc2.FilePath].Select(InspectActiveStatement)); + + Assert.Equal(3, baseActiveStatementsMap.InstructionMap.Count); + + var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); + var s = statements[0]; + Assert.Equal(0x06000001, s.InstructionId.Method.Token); + Assert.Equal(module4, s.InstructionId.Method.Module); + + s = statements[1]; + Assert.Equal(0x06000002, s.InstructionId.Method.Token); + Assert.Equal(module2, s.InstructionId.Method.Module); + + s = statements[2]; + Assert.Equal(0x06000001, s.InstructionId.Method.Token); + Assert.Equal(module1, s.InstructionId.Method.Module); + + var spans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(doc1.Id, doc2.Id, docId3, docId4, docId5), CancellationToken.None); + + AssertEx.Equal(new[] + { + "(2,32)-(2,52), (3,29)-(3,49)", // test1.cs + "(0,39)-(0,59)", // test2.cs + "(2,32)-(2,52), (3,29)-(3,49)", // link test1.cs + "(2,32)-(2,52), (3,29)-(3,49)", // link test1.cs + "(0,39)-(0,59)" // link test2.cs + }, spans.Select(docSpans => string.Join(", ", docSpans.Select(span => span.LineSpan)))); + } + + [Fact] + public async Task ActiveStatements_OutOfSyncDocuments() + { + var markedSource1 = +@"class C +{ + static void M() + { + try + { + } + catch (Exception e) + { + M(); + } + } +}"; + var source2 = + @"class C +{ + static void M() + { + try + { + } + catch (Exception e) + { + + M(); + } + } +}"; + + var markedSources = new[] { markedSource1 }; + + var thread1 = Guid.NewGuid(); + + // Thread1 stack trace: F (AS:0 leaf) + + var debugInfos = GetActiveStatementDebugInfosCSharp( + markedSources, + methodRowIds: new[] { 1 }, + ilOffsets: new[] { 1 }, + flags: new[] + { + ActiveStatementFlags.IsLeafFrame | ActiveStatementFlags.MethodUpToDate + }); + + using var _ = CreateWorkspace(out var solution, out var service); + solution = AddDefaultTestProject(solution, ActiveStatementsDescription.ClearTags(markedSources)); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); + + var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.OutOfSync); + EnterBreakState(service, debugInfos); + + // update document to test a changed solution + solution = solution.WithDocumentText(document.Id, SourceText.From(source2, Encoding.UTF8)); + document = solution.GetDocument(document.Id); + + var baseActiveStatementMap = await debuggingSession.EditSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); + + // Active Statements - available in out-of-sync documents, as they reflect the state of the debuggee and not the base document content + + Assert.Single(baseActiveStatementMap.DocumentPathMap); + + AssertEx.Equal(new[] + { + $"0: {document.FilePath}: (9,18)-(9,22) flags=[IsLeafFrame, MethodUpToDate]", + }, baseActiveStatementMap.DocumentPathMap[document.FilePath].Select(InspectActiveStatement)); + + Assert.Equal(1, baseActiveStatementMap.InstructionMap.Count); + + var activeStatement1 = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.InstructionId.Method.Token).Single(); + Assert.Equal(0x06000001, activeStatement1.InstructionId.Method.Token); + Assert.Equal(document.FilePath, activeStatement1.FilePath); + Assert.True(activeStatement1.IsLeaf); + + // Active statement reported as unchanged as the containing document is out-of-sync: + var baseSpans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None); + AssertEx.Equal(new[] { $"(9,18)-(9,22)" }, baseSpans.Single().Select(s => s.LineSpan.ToString())); + + // Whether or not an active statement is in an exception region is unknown if the document is out-of-sync: + Assert.Null(await service.IsActiveStatementInExceptionRegionAsync(solution, activeStatement1.InstructionId, CancellationToken.None)); + + // Document got synchronized: + debuggingSession.LastCommittedSolution.Test_SetDocumentState(document.Id, CommittedSolution.DocumentState.MatchesBuildOutput); + + // New location of the active statement reported: + baseSpans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None); + AssertEx.Equal(new[] { $"(10,12)-(10,16)" }, baseSpans.Single().Select(s => s.LineSpan.ToString())); + + Assert.True(await service.IsActiveStatementInExceptionRegionAsync(solution, activeStatement1.InstructionId, CancellationToken.None)); + } + + [Fact] + public async Task ActiveStatements_SourceGeneratedDocuments_LineDirectives() + { + var markedSource1 = @" +/* GENERATE: +class C +{ + void F() + { +#line 1 ""a.razor"" + F(); +#line default + } +} +*/ +"; + var markedSource2 = @" +/* GENERATE: +class C +{ + void F() + { +#line 2 ""a.razor"" + F(); +#line default + } +} +*/ +"; + var source1 = ActiveStatementsDescription.ClearTags(markedSource1); + var source2 = ActiveStatementsDescription.ClearTags(markedSource2); + + var additionalFileSourceV1 = @" + xxxxxxxxxxxxxxxxx +"; + + var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; + + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document1) = AddDefaultTestProject(solution, source1, generator, additionalFileText: additionalFileSourceV1); + + var generatedDocument1 = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync().ConfigureAwait(false)).Single(); + + var moduleId = EmitLibrary(source1, generator: generator, additionalFileText: additionalFileSourceV1); + LoadLibraryToDebuggee(moduleId); + + var debuggingSession = await StartDebuggingSessionAsync(service, solution); + + EnterBreakState(service, GetActiveStatementDebugInfosCSharp( + new[] { GetGeneratedCodeFromMarkedSource(markedSource1) }, + filePaths: new[] { generatedDocument1.FilePath }, + modules: new[] { moduleId }, + methodRowIds: new[] { 1 }, + methodVersions: new[] { 1 }, + flags: new[] + { + ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame + })); + + var editSession = service.GetTestAccessor().GetEditSession(); + + // change the source (valid edit) + solution = solution.WithDocumentText(document1.Id, SourceText.From(source2, Encoding.UTF8)); + + // validate solution update status and emit: + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); + Assert.Empty(emitDiagnostics); + Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); + + // check emitted delta: + var delta = updates.Updates.Single(); + Assert.Empty(delta.ActiveStatements); + Assert.NotEmpty(delta.ILDelta); + Assert.NotEmpty(delta.MetadataDelta); + Assert.NotEmpty(delta.PdbDelta); + Assert.Empty(delta.UpdatedMethods); + + AssertEx.Equal(new[] + { + "a.razor: [0 -> 1]" + }, delta.SequencePoints.Inspect()); + + EndDebuggingSession(service); + } + /// /// Scenario: /// F5 a program that has function F that calls G. G has a long-running loop, which starts executing. @@ -3062,17 +3256,15 @@ static void F() var moduleId = EmitAndLoadLibraryToDebuggee(ActiveStatementsDescription.ClearTags(markedSourceV1)); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, ActiveStatementsDescription.ClearTags(markedSourceV1)); - var documentId = project.DocumentIds.Single(); - var solution = project.Solution; + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, ActiveStatementsDescription.ClearTags(markedSourceV1)); + var documentId = document.Id; - var service = GetEditAndContinueService(workspace); var debuggingSession = await StartDebuggingSessionAsync(service, solution); // EnC update F v1 -> v2 - EnterBreakState(service, GetActiveStatementDebugInfos( + EnterBreakState(service, GetActiveStatementDebugInfosCSharp( new[] { markedSourceV1 }, modules: new[] { moduleId, moduleId }, methodRowIds: new[] { 2, 3 }, @@ -3094,7 +3286,7 @@ static void F() AssertEx.Equal(new[] { - "0x06000003 v1 | AS (9,14)-(9,18) δ=1", + $"0x06000003 v1 | AS {document.FilePath}: (9,14)-(9,18) δ=1", }, InspectNonRemappableRegions(debuggingSession.NonRemappableRegions)); ExitBreakState(); @@ -3112,12 +3304,12 @@ static void F() AssertEx.Equal(new[] { - "0x06000003 v1 | AS (9,14)-(9,18) δ=1", + $"0x06000003 v1 | AS {document.FilePath}: (9,14)-(9,18) δ=1", }, InspectNonRemappableRegions(debuggingSession.NonRemappableRegions)); // EnC update F v3 -> v4 - EnterBreakState(service, GetActiveStatementDebugInfos( + EnterBreakState(service, GetActiveStatementDebugInfosCSharp( new[] { markedSourceV1 }, // matches F v1 modules: new[] { moduleId, moduleId }, methodRowIds: new[] { 2, 3 }, @@ -3141,7 +3333,7 @@ static void F() // this is incorrect. correct value is: 0x06000003 v1 | AS (9,14)-(9,18) δ=16 AssertEx.Equal(new[] { - "0x06000003 v1 | AS (9,14)-(9,18) δ=5" + $"0x06000003 v1 | AS {document.FilePath}: (9,14)-(9,18) δ=5" }, InspectNonRemappableRegions(debuggingSession.NonRemappableRegions)); ExitBreakState(); @@ -3197,12 +3389,10 @@ static void F() var moduleId = EmitAndLoadLibraryToDebuggee(ActiveStatementsDescription.ClearTags(markedSource1)); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, ActiveStatementsDescription.ClearTags(markedSource2)); - var documentId = project.DocumentIds.Single(); - var solution = project.Solution; + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, ActiveStatementsDescription.ClearTags(markedSource1)); + var documentId = document.Id; - var service = GetEditAndContinueService(workspace); var debuggingSession = await StartDebuggingSessionAsync(service, solution); // Update to snapshot 2, but don't apply @@ -3211,7 +3401,7 @@ static void F() // EnC update F v2 -> v3 - EnterBreakState(service, GetActiveStatementDebugInfos( + EnterBreakState(service, GetActiveStatementDebugInfosCSharp( new[] { markedSource1 }, modules: new[] { moduleId, moduleId }, methodRowIds: new[] { 2, 3 }, @@ -3227,14 +3417,14 @@ static void F() var expectedSpanF1 = new LinePositionSpan(new LinePosition(8, 14), new LinePosition(8, 18)); var activeInstructionF1 = new ManagedInstructionId(new ManagedMethodId(moduleId, 0x06000003, version: 1), ilOffset: 0); - var span = await service.GetCurrentActiveStatementPositionAsync(solution, s_noSolutionActiveSpans, activeInstructionF1, CancellationToken.None); - Assert.Equal(expectedSpanF1, span); + var span = await service.GetCurrentActiveStatementPositionAsync(solution, s_noActiveSpans, activeInstructionF1, CancellationToken.None); + Assert.Equal(expectedSpanF1, span.Value); var spans = (await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - (expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame), - (expectedSpanF1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsNonLeafFrame) + new ActiveStatementSpan(0, expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame, documentId), + new ActiveStatementSpan(1, expectedSpanF1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsNonLeafFrame, documentId) }, spans); solution = solution.WithDocumentText(documentId, SourceText.From(ActiveStatementsDescription.ClearTags(markedSource3), Encoding.UTF8)); @@ -3243,19 +3433,19 @@ static void F() var expectedSpanG2 = new LinePositionSpan(new LinePosition(3, 41), new LinePosition(3, 42)); var expectedSpanF2 = new LinePositionSpan(new LinePosition(9, 14), new LinePosition(9, 18)); - span = await service.GetCurrentActiveStatementPositionAsync(solution, s_noSolutionActiveSpans, activeInstructionF1, CancellationToken.None); + span = await service.GetCurrentActiveStatementPositionAsync(solution, s_noActiveSpans, activeInstructionF1, CancellationToken.None); Assert.Equal(expectedSpanF2, span); spans = (await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - (expectedSpanG2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame), - (expectedSpanF2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsNonLeafFrame) + new ActiveStatementSpan(0, expectedSpanG2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame, documentId), + new ActiveStatementSpan(1, expectedSpanF2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsNonLeafFrame, documentId) }, spans); // no rude edits: var document1 = solution.GetDocument(documentId); - var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noDocumentActiveSpans, CancellationToken.None); + var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(service, solution); @@ -3267,7 +3457,7 @@ static void F() AssertEx.Equal(new[] { - "0x06000003 v1 | AS (7,14)-(7,18) δ=2", + $"0x06000003 v1 | AS {document.FilePath}: (7,14)-(7,18) δ=2", }, InspectNonRemappableRegions(debuggingSession.NonRemappableRegions)); ExitBreakState(); @@ -3306,12 +3496,10 @@ static void F() }"; var moduleId = EmitAndLoadLibraryToDebuggee(ActiveStatementsDescription.ClearTags(markedSource1)); - using var workspace = CreateWorkspace(); - var project = AddDefaultTestProject(workspace, ActiveStatementsDescription.ClearTags(markedSource1)); - var documentId = project.DocumentIds.Single(); - var solution = project.Solution; + using var _ = CreateWorkspace(out var solution, out var service); + (solution, var document) = AddDefaultTestProject(solution, ActiveStatementsDescription.ClearTags(markedSource1)); + var documentId = document.Id; - var service = GetEditAndContinueService(workspace); var debuggingSession = await StartDebuggingSessionAsync(service, solution); // Apply update: F v1 -> v2. @@ -3327,7 +3515,7 @@ static void F() // Break - EnterBreakState(service, GetActiveStatementDebugInfos( + EnterBreakState(service, GetActiveStatementDebugInfosCSharp( new[] { markedSource1 }, modules: new[] { moduleId, moduleId }, methodRowIds: new[] { 2, 3 }, @@ -3339,21 +3527,22 @@ static void F() })); // check that the active statement is mapped correctly to snapshot v2: + var expectedSpanF1 = new LinePositionSpan(new LinePosition(7, 14), new LinePosition(7, 18)); var expectedSpanG1 = new LinePositionSpan(new LinePosition(3, 41), new LinePosition(3, 42)); var activeInstructionF1 = new ManagedInstructionId(new ManagedMethodId(moduleId, 0x06000003, version: 1), ilOffset: 0); - var span = await service.GetCurrentActiveStatementPositionAsync(solution, s_noSolutionActiveSpans, activeInstructionF1, CancellationToken.None); - Assert.Null(span); + var span = await service.GetCurrentActiveStatementPositionAsync(solution, s_noActiveSpans, activeInstructionF1, CancellationToken.None); + Assert.Equal(expectedSpanF1, span); var spans = (await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - (expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame), + new ActiveStatementSpan(0, expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null), // TODO: https://github.com/dotnet/roslyn/issues/52100 // This is incorrect: the active statement shouldn't be reported since it has been deleted. // We need the debugger to mark the method version as replaced by run-mode update. - (new LinePositionSpan(new LinePosition(7, 14), new LinePosition(7, 18)), ActiveStatementFlags.IsNonLeafFrame) + new ActiveStatementSpan(1, expectedSpanF1, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null) }, spans); ExitBreakState(); @@ -3370,9 +3559,7 @@ public async Task WatchHotReloadServiceTest() var sourceFileA = dir.CreateFile("A.cs").WriteAllText(source1); var moduleId = EmitLibrary(source1, sourceFileA.Path, Encoding.UTF8, "Proj"); - using var workspace = CreateWorkspace(); - - var solution = workspace.CurrentSolution; + using var workspace = CreateWorkspace(out var solution, out var encService); var projectP = solution. AddProject("P", "P", LanguageNames.CSharp). @@ -3387,7 +3574,6 @@ public async Task WatchHotReloadServiceTest() loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8), filePath: sourceFileA.Path)); - var encService = GetEditAndContinueService(workspace); var hotReload = new WatchHotReloadService(workspace.Services); await hotReload.StartSessionAsync(solution, CancellationToken.None); @@ -3416,5 +3602,48 @@ public async Task WatchHotReloadServiceTest() hotReload.EndSession(); } + + [Fact] + public void ParseCapabilities() + { + var capabilities = ImmutableArray.Create("Baseline"); + + var service = EditAndContinueWorkspaceService.ParseCapabilities(capabilities); + + Assert.True(service.HasFlag(EditAndContinueCapabilities.Baseline)); + Assert.False(service.HasFlag(EditAndContinueCapabilities.NewTypeDefinition)); + } + + [Fact] + public void ParseCapabilities_CaseSensitive() + { + var capabilities = ImmutableArray.Create("BaseLine"); + + var service = EditAndContinueWorkspaceService.ParseCapabilities(capabilities); + + Assert.False(service.HasFlag(EditAndContinueCapabilities.Baseline)); + } + + [Fact] + public void ParseCapabilities_IgnoreInvalid() + { + var capabilities = ImmutableArray.Create("Baseline", "Invalid", "NewTypeDefinition"); + + var service = EditAndContinueWorkspaceService.ParseCapabilities(capabilities); + + Assert.True(service.HasFlag(EditAndContinueCapabilities.Baseline)); + Assert.True(service.HasFlag(EditAndContinueCapabilities.NewTypeDefinition)); + } + + [Fact] + public void ParseCapabilities_IgnoreInvalidNumeric() + { + var capabilities = ImmutableArray.Create("Baseline", "90", "NewTypeDefinition"); + + var service = EditAndContinueWorkspaceService.ParseCapabilities(capabilities); + + Assert.True(service.HasFlag(EditAndContinueCapabilities.Baseline)); + Assert.True(service.HasFlag(EditAndContinueCapabilities.NewTypeDefinition)); + } } } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditSessionActiveStatementsTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditSessionActiveStatementsTests.cs index 63377aad61c5d..7a75c9168dd21 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditSessionActiveStatementsTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditSessionActiveStatementsTests.cs @@ -49,6 +49,7 @@ private static EditSession CreateEditSession( var debuggingSession = new DebuggingSession( solution, mockDebuggerService, + EditAndContinueTestHelpers.Net5RuntimeCapabilities, mockCompilationOutputsProvider, SpecializedCollections.EmptyEnumerable>(), new DebuggingSessionTelemetry(), @@ -86,11 +87,6 @@ private static Solution AddDefaultTestSolution(TestWorkspace workspace, string[] return solution; } - private static ImmutableArray GetDocumentIds(Solution solution) - => (from p in solution.Projects - from d in p.DocumentIds - select d).ToImmutableArray(); - [Fact] public async Task BaseActiveStatementsAndExceptionRegions1() { @@ -142,8 +138,10 @@ static void Main() var module1 = new Guid("11111111-1111-1111-1111-111111111111"); var module2 = new Guid("22222222-2222-2222-2222-222222222222"); + var module3 = new Guid("33333333-3333-3333-3333-333333333333"); + var module4 = new Guid("44444444-4444-4444-4444-444444444444"); - var activeStatements = GetActiveStatementDebugInfos( + var activeStatements = GetActiveStatementDebugInfosCSharp( markedSources, methodRowIds: new[] { 1, 2, 3, 4, 5 }, ilOffsets: new[] { 1, 1, 1, 2, 3 }, @@ -160,7 +158,7 @@ static void Main() // add an extra active statement from project not belonging to the solution, it should be ignored: activeStatements = activeStatements.Add( new ManagedActiveStatementDebugInfo( - new ManagedInstructionId(new ManagedMethodId(module: Guid.NewGuid(), token: 0x06000005, version: 1), ilOffset: 10), + new ManagedInstructionId(new ManagedMethodId(module: module3, token: 0x06000005, version: 1), ilOffset: 10), "NonRoslynDocument.mcpp", new SourceSpan(1, 1, 1, 10), ActiveStatementFlags.IsNonLeafFrame)); @@ -169,94 +167,107 @@ static void Main() // See https://github.com/dotnet/roslyn/issues/24408 for test scenario. activeStatements = activeStatements.Add( new ManagedActiveStatementDebugInfo( - new ManagedInstructionId(new ManagedMethodId(module: Guid.NewGuid(), token: 0x06000005, version: 1), ilOffset: 10), + new ManagedInstructionId(new ManagedMethodId(module: module4, token: 0x06000005, version: 1), ilOffset: 10), "a.dummy", - new SourceSpan(1, 1, 1, 10), + new SourceSpan(2, 1, 2, 10), ActiveStatementFlags.IsNonLeafFrame)); using var workspace = new TestWorkspace(composition: s_composition); var solution = AddDefaultTestSolution(workspace, markedSources); - var project = solution.AddProject("dummy_proj", "dummy_proj", DummyLanguageService.LanguageName); - solution = project.Solution.AddDocument(DocumentId.CreateNewId(project.Id, DummyLanguageService.LanguageName), "a.dummy", ""); + var projectId = solution.ProjectIds.Single(); + var dummyProject = solution.AddProject("dummy_proj", "dummy_proj", DummyLanguageService.LanguageName); + solution = dummyProject.Solution.AddDocument(DocumentId.CreateNewId(dummyProject.Id, DummyLanguageService.LanguageName), "a.dummy", ""); + var project = solution.GetProject(projectId); + var document1 = project.Documents.Single(d => d.Name == "test1.cs"); + var document2 = project.Documents.Single(d => d.Name == "test2.cs"); var editSession = CreateEditSession(solution, activeStatements); var baseActiveStatementsMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); - var docs = GetDocumentIds(solution); // Active Statements var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); AssertEx.Equal(new[] { - "0: (9,14)-(9,35) flags=[IsLeafFrame, MethodUpToDate] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0001", - "1: (4,32)-(4,37) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v1 IL_0001", - "2: (21,14)-(21,24) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test2.cs docs=[test2.cs] mvid=22222222-2222-2222-2222-222222222222 0x06000003 v1 IL_0001", - "3: (8,20)-(8,25) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test2.cs docs=[test2.cs] mvid=22222222-2222-2222-2222-222222222222 0x06000004 v1 IL_0002", - "4: (26,20)-(26,25) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test2.cs docs=[test2.cs] mvid=22222222-2222-2222-2222-222222222222 0x06000005 v1 IL_0003" + $"0: {document1.FilePath}: (9,14)-(9,35) flags=[IsLeafFrame, MethodUpToDate] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0001", + $"1: {document1.FilePath}: (4,32)-(4,37) flags=[MethodUpToDate, IsNonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v1 IL_0001", + $"2: {document2.FilePath}: (21,14)-(21,24) flags=[MethodUpToDate, IsNonLeafFrame] mvid=22222222-2222-2222-2222-222222222222 0x06000003 v1 IL_0001", // [|Test1.M1()|] in F2 + $"3: {document2.FilePath}: (8,20)-(8,25) flags=[MethodUpToDate, IsNonLeafFrame] mvid=22222222-2222-2222-2222-222222222222 0x06000004 v1 IL_0002", // [|F2();|] in M2 + $"4: {document2.FilePath}: (26,20)-(26,25) flags=[MethodUpToDate, IsNonLeafFrame] mvid=22222222-2222-2222-2222-222222222222 0x06000005 v1 IL_0003", // [|M2();|] in Main + $"5: NonRoslynDocument.mcpp: (1,1)-(1,10) flags=[IsNonLeafFrame] mvid={module3} 0x06000005 v1 IL_000A", + $"6: a.dummy: (2,1)-(2,10) flags=[IsNonLeafFrame] mvid={module4} 0x06000005 v1 IL_000A" }, statements.Select(InspectActiveStatementAndInstruction)); // Active Statements per document - Assert.Equal(2, baseActiveStatementsMap.DocumentMap.Count); + Assert.Equal(4, baseActiveStatementsMap.DocumentPathMap.Count); AssertEx.Equal(new[] { - "0: (9,14)-(9,35) flags=[IsLeafFrame, MethodUpToDate] pdid=test1.cs docs=[test1.cs]", - "1: (4,32)-(4,37) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs]" - }, baseActiveStatementsMap.DocumentMap[docs[0]].Select(InspectActiveStatement)); + $"1: {document1.FilePath}: (4,32)-(4,37) flags=[MethodUpToDate, IsNonLeafFrame]", + $"0: {document1.FilePath}: (9,14)-(9,35) flags=[IsLeafFrame, MethodUpToDate]" + }, baseActiveStatementsMap.DocumentPathMap[document1.FilePath].Select(InspectActiveStatement)); AssertEx.Equal(new[] { - "2: (21,14)-(21,24) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test2.cs docs=[test2.cs]", - "3: (8,20)-(8,25) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test2.cs docs=[test2.cs]", - "4: (26,20)-(26,25) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test2.cs docs=[test2.cs]" - }, baseActiveStatementsMap.DocumentMap[docs[1]].Select(InspectActiveStatement)); + $"3: {document2.FilePath}: (8,20)-(8,25) flags=[MethodUpToDate, IsNonLeafFrame]", // [|F2();|] in M2 + $"2: {document2.FilePath}: (21,14)-(21,24) flags=[MethodUpToDate, IsNonLeafFrame]", // [|Test1.M1()|] in F2 + $"4: {document2.FilePath}: (26,20)-(26,25) flags=[MethodUpToDate, IsNonLeafFrame]" // [|M2();|] in Main + }, baseActiveStatementsMap.DocumentPathMap[document2.FilePath].Select(InspectActiveStatement)); + + AssertEx.Equal(new[] + { + $"5: NonRoslynDocument.mcpp: (1,1)-(1,10) flags=[IsNonLeafFrame]", + }, baseActiveStatementsMap.DocumentPathMap["NonRoslynDocument.mcpp"].Select(InspectActiveStatement)); + + AssertEx.Equal(new[] + { + $"6: a.dummy: (2,1)-(2,10) flags=[IsNonLeafFrame]", + }, baseActiveStatementsMap.DocumentPathMap["a.dummy"].Select(InspectActiveStatement)); // Exception Regions - var baseExceptionRegions = await editSession.GetBaseActiveExceptionRegionsAsync(solution, CancellationToken.None).ConfigureAwait(false); + var analyzer = solution.GetProject(projectId).LanguageServices.GetRequiredService(); + var oldActiveStatements1 = await baseActiveStatementsMap.GetOldActiveStatementsAsync(analyzer, document1, CancellationToken.None).ConfigureAwait(false); AssertEx.Equal(new[] { + $"[{document1.FilePath}: (4,8)-(4,46)]", "[]", - "[(4,8)-(4,46)]", + }, oldActiveStatements1.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans) + "]")); + + var oldActiveStatements2 = await baseActiveStatementsMap.GetOldActiveStatementsAsync(analyzer, document2, CancellationToken.None).ConfigureAwait(false); + AssertEx.Equal(new[] + { + $"[{document2.FilePath}: (14,8)-(16,9), {document2.FilePath}: (10,10)-(12,11)]", "[]", - "[(14,8)-(16,9),(10,10)-(12,11)]", - "[(26,35)-(26,46)]" - }, baseExceptionRegions.Select(r => "[" + string.Join(",", r.Spans) + "]")); + $"[{document2.FilePath}: (26,35)-(26,46)]", + }, oldActiveStatements2.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans) + "]")); // GetActiveStatementAndExceptionRegionSpans - // Assume 2 updates in Project2: + // Assume 2 updates in Document2: // Test2.M2: adding a line in front of try-catch. // Test2.F2: moving the entire method 2 lines down. - static LinePositionSpan AddDelta(LinePositionSpan span, int lineDelta) - => new LinePositionSpan(new LinePosition(span.Start.Line + lineDelta, span.Start.Character), new LinePosition(span.End.Line + lineDelta, span.End.Character)); - var newActiveStatementsInChangedDocuments = ImmutableArray.Create( - ( - docs[1], - - ImmutableArray.Create( - statements[2].WithSpan(AddDelta(statements[2].Span, +2)), - statements[3].WithSpan(AddDelta(statements[3].Span, +1)), + new DocumentActiveStatementChanges( + oldSpans: oldActiveStatements2, + newStatements: ImmutableArray.Create( + statements[3].WithFileSpan(statements[3].FileSpan.AddLineDelta(+1)), + statements[2].WithFileSpan(statements[2].FileSpan.AddLineDelta(+2)), statements[4]), - - ImmutableArray.Create( - baseExceptionRegions[2].Spans, - baseExceptionRegions[3].Spans.SelectAsArray(es => AddDelta(es, +1)), - baseExceptionRegions[4].Spans) - ) - ); + newExceptionRegions: ImmutableArray.Create( + oldActiveStatements2[0].ExceptionRegions.Spans.SelectAsArray(es => es.AddLineDelta(+1)), + oldActiveStatements2[1].ExceptionRegions.Spans, + oldActiveStatements2[2].ExceptionRegions.Spans))); EditSession.GetActiveStatementAndExceptionRegionSpans( module2, baseActiveStatementsMap, - baseExceptionRegions, updatedMethodTokens: ImmutableArray.Create(0x06000004), // contains only recompiled methods in the project we are interested in (module2) - ImmutableDictionary>.Empty, + previousNonRemappableRegions: ImmutableDictionary>.Empty, newActiveStatementsInChangedDocuments, out var activeStatementsInUpdatedMethods, out var nonRemappableRegions, @@ -264,20 +275,20 @@ static LinePositionSpan AddDelta(LinePositionSpan span, int lineDelta) AssertEx.Equal(new[] { - "0x06000004 v1 | AS (8,20)-(8,25) δ=1", - "0x06000004 v1 | ER (14,8)-(16,9) δ=1", - "0x06000004 v1 | ER (10,10)-(12,11) δ=1" + $"0x06000004 v1 | AS {document2.FilePath}: (8,20)-(8,25) δ=1", + $"0x06000004 v1 | ER {document2.FilePath}: (14,8)-(16,9) δ=1", + $"0x06000004 v1 | ER {document2.FilePath}: (10,10)-(12,11) δ=1" }, nonRemappableRegions.Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}")); AssertEx.Equal(new[] { - "0x06000004 v1 | (15,8)-(17,9) Delta=-1", - "0x06000004 v1 | (11,10)-(13,11) Delta=-1" + $"0x06000004 v1 | (15,8)-(17,9) Delta=-1", + $"0x06000004 v1 | (11,10)-(13,11) Delta=-1" }, exceptionRegionUpdates.Select(InspectExceptionRegionUpdate)); AssertEx.Equal(new[] { - "0x06000004 v1 IL_0002: (9,20)-(9,25)" + $"0x06000004 v1 IL_0002: (9,20)-(9,25)" }, activeStatementsInUpdatedMethods.Select(InspectActiveStatementUpdate)); } @@ -312,7 +323,7 @@ static void F2() var baseText = SourceText.From(baseSource); var updatedText = SourceText.From(updatedSource); - var baseActiveStatementInfos = GetActiveStatementDebugInfos( + var baseActiveStatementInfos = GetActiveStatementDebugInfosCSharp( new[] { baseSource }, modules: new[] { module1, module1 }, methodVersions: new[] { 1, 1 }, @@ -324,10 +335,11 @@ static void F2() using var workspace = new TestWorkspace(composition: s_composition); var solution = AddDefaultTestSolution(workspace, new[] { baseSource }); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); var editSession = CreateEditSession(solution, baseActiveStatementInfos); var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); - var docs = GetDocumentIds(solution); // Active Statements @@ -335,43 +347,40 @@ static void F2() AssertEx.Equal(new[] { - "0: (6,18)-(6,23) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0000 'F2();'", - "1: (18,14)-(18,36) flags=[IsLeafFrame, MethodUpToDate] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v1 IL_0000 'throw new Exception();'" + $"0: {document.FilePath}: (6,18)-(6,23) flags=[MethodUpToDate, IsNonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0000 'F2();'", + $"1: {document.FilePath}: (18,14)-(18,36) flags=[IsLeafFrame, MethodUpToDate] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v1 IL_0000 'throw new Exception();'" }, baseActiveStatements.Select(s => InspectActiveStatementAndInstruction(s, baseText))); // Exception Regions - var baseExceptionRegions = await editSession.GetBaseActiveExceptionRegionsAsync(solution, CancellationToken.None).ConfigureAwait(false); + var analyzer = solution.GetProject(project.Id).LanguageServices.GetRequiredService(); + var oldActiveStatements = await baseActiveStatementMap.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None).ConfigureAwait(false); // Note that the spans correspond to the base snapshot (V2). AssertEx.Equal(new[] { - "[(8,8)-(12,9) 'catch (Exception) {']", + $"[{document.FilePath}: (8,8)-(12,9) 'catch (Exception) {{']", "[]", - }, baseExceptionRegions.Select(r => "[" + string.Join(", ", r.Spans.Select(s => $"{s} '{GetFirstLineText(s, baseText)}'")) + "]")); + }, oldActiveStatements.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans.Select(span => $"{span} '{GetFirstLineText(span.Span, baseText)}'")) + "]")); // GetActiveStatementAndExceptionRegionSpans var newActiveStatementsInChangedDocuments = ImmutableArray.Create( - ( - docs[0], - - ImmutableArray.Create( + new DocumentActiveStatementChanges( + oldSpans: oldActiveStatements, + newStatements: ImmutableArray.Create( baseActiveStatements[0], - baseActiveStatements[1].WithSpan(baseActiveStatements[1].Span.AddLineDelta(+1))), - - ImmutableArray.Create( - baseExceptionRegions[0].Spans, - baseExceptionRegions[1].Spans) - ) + baseActiveStatements[1].WithFileSpan(baseActiveStatements[1].FileSpan.AddLineDelta(+1))), + newExceptionRegions: ImmutableArray.Create( + oldActiveStatements[0].ExceptionRegions.Spans, + oldActiveStatements[1].ExceptionRegions.Spans)) ); EditSession.GetActiveStatementAndExceptionRegionSpans( module1, baseActiveStatementMap, - baseExceptionRegions, updatedMethodTokens: ImmutableArray.Create(0x06000001), // F1 - ImmutableDictionary>.Empty, + previousNonRemappableRegions: ImmutableDictionary>.Empty, newActiveStatementsInChangedDocuments, out var activeStatementsInUpdatedMethods, out var nonRemappableRegions, @@ -380,9 +389,9 @@ static void F2() // although the span has not changed the method has, so we need to add corresponding non-remappable regions AssertEx.Equal(new[] { - "0x06000001 v1 | AS (6,18)-(6,23) δ=0", - "0x06000001 v1 | ER (8,8)-(12,9) δ=0", - }, nonRemappableRegions.OrderBy(r => r.Region.Span.Start.Line).Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}")); + $"0x06000001 v1 | AS {document.FilePath}: (6,18)-(6,23) δ=0", + $"0x06000001 v1 | ER {document.FilePath}: (8,8)-(12,9) δ=0", + }, nonRemappableRegions.OrderBy(r => r.Region.Span.Span.Start.Line).Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}")); AssertEx.Equal(new[] { @@ -395,83 +404,6 @@ static void F2() }, activeStatementsInUpdatedMethods.Select(update => $"{InspectActiveStatementUpdate(update)} '{GetFirstLineText(update.NewSpan.ToLinePositionSpan(), updatedText)}'")); } - [Fact] - public async Task BaseActiveStatementsAndExceptionRegions_OutOfSyncDocuments() - { - var markedSources = new[] - { -@"class C -{ - static void M() - { - try - { - M(); - } - catch (Exception e) - { - } - } -}" - }; - - var thread1 = Guid.NewGuid(); - - // Thread1 stack trace: F (AS:0 leaf) - - var activeStatements = GetActiveStatementDebugInfos( - markedSources, - methodRowIds: new[] { 1 }, - ilOffsets: new[] { 1 }, - flags: new[] - { - ActiveStatementFlags.IsLeafFrame | ActiveStatementFlags.MethodUpToDate - }); - - using var workspace = new TestWorkspace(composition: s_composition); - var solution = AddDefaultTestSolution(workspace, markedSources); - - var editSession = CreateEditSession(solution, activeStatements, initialState: CommittedSolution.DocumentState.OutOfSync); - var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); - var docs = GetDocumentIds(solution); - - // Active Statements - available in out-of-sync documents, as they reflect the state of the debuggee and not the base document content - - Assert.Single(baseActiveStatementMap.DocumentMap); - - AssertEx.Equal(new[] - { - "0: (6,18)-(6,22) flags=[IsLeafFrame, MethodUpToDate] pdid=test1.cs docs=[test1.cs]", - }, baseActiveStatementMap.DocumentMap[docs[0]].Select(InspectActiveStatement)); - - Assert.Equal(1, baseActiveStatementMap.InstructionMap.Count); - - var s = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.InstructionId.Method.Token).Single(); - Assert.Equal(0x06000001, s.InstructionId.Method.Token); - Assert.Equal(0, s.PrimaryDocumentOrdinal); - Assert.Equal(docs[0], s.DocumentIds.Single()); - Assert.True(s.IsLeaf); - - // Exception Regions - not available in out-of-sync documents as we need the content of the base document to calculate them - - var baseExceptionRegions = await editSession.GetBaseActiveExceptionRegionsAsync(solution, CancellationToken.None).ConfigureAwait(false); - - AssertEx.Equal(new[] - { - "out-of-sync" - }, baseExceptionRegions.Select(r => r.Spans.IsDefault ? "out-of-sync" : "[" + string.Join(",", r.Spans) + "]")); - - // document got synchronized: - editSession.DebuggingSession.LastCommittedSolution.Test_SetDocumentState(docs[0], CommittedSolution.DocumentState.MatchesBuildOutput); - - baseExceptionRegions = await editSession.GetBaseActiveExceptionRegionsAsync(solution, CancellationToken.None).ConfigureAwait(false); - - AssertEx.Equal(new[] - { - "[]" - }, baseExceptionRegions.Select(r => r.Spans.IsDefault ? "out-of-sync" : "[" + string.Join(",", r.Spans) + "]")); - } - [Fact] public async Task BaseActiveStatementsAndExceptionRegions_WithInitialNonRemappableRegions() { @@ -544,7 +476,7 @@ static void F4() var sourceTextV2 = SourceText.From(markedSourceV2); var sourceTextV3 = SourceText.From(markedSourceV3); - var activeStatementsPreRemap = GetActiveStatementDebugInfos(new[] { markedSourceV1 }, + var activeStatementsPreRemap = GetActiveStatementDebugInfosCSharp(new[] { markedSourceV1 }, modules: new[] { module1, module1, module1, module1 }, methodVersions: new[] { 2, 2, 1, 1 }, // method F3 and F4 were not remapped flags: new[] @@ -555,14 +487,15 @@ static void F4() ActiveStatementFlags.None | ActiveStatementFlags.IsNonLeafFrame, // F4 }); - var exceptionSpans = ActiveStatementsDescription.GetExceptionRegions(markedSourceV1, activeStatementsPreRemap.Length); + var exceptionSpans = ActiveStatementsDescription.GetExceptionRegions(markedSourceV1); - var spanPreRemap2 = activeStatementsPreRemap[2].SourceSpan.ToLinePositionSpan(); - var erPreRemap20 = sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[2][0]); - var erPreRemap21 = sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[2][1]); - var spanPreRemap3 = activeStatementsPreRemap[3].SourceSpan.ToLinePositionSpan(); - var erPreRemap30 = sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[3][0]); - var erPreRemap31 = sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[3][1]); + var filePath = activeStatementsPreRemap[0].DocumentName; + var spanPreRemap2 = new SourceFileSpan(filePath, activeStatementsPreRemap[2].SourceSpan.ToLinePositionSpan()); + var erPreRemap20 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[2][0])); + var erPreRemap21 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[2][1])); + var spanPreRemap3 = new SourceFileSpan(filePath, activeStatementsPreRemap[3].SourceSpan.ToLinePositionSpan()); + var erPreRemap30 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[3][0])); + var erPreRemap31 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[3][1])); // Assume that the following edits have been made to F3 and F4 and set up non-remappable regions mapping // from the pre-remap spans of AS:2 and AS:3 to their current location. @@ -584,10 +517,11 @@ static void F4() using var workspace = new TestWorkspace(composition: s_composition); var solution = AddDefaultTestSolution(workspace, new[] { markedSourceV2 }); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); var editSession = CreateEditSession(solution, activeStatementsPreRemap, initialNonRemappableRegions); var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); - var docs = GetDocumentIds(solution); // Active Statements @@ -596,24 +530,25 @@ static void F4() // Note that the spans of AS:2 and AS:3 correspond to the base snapshot (V2). AssertEx.Equal(new[] { - "0: (6,18)-(6,22) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v2 IL_0000 'M();'", - "1: (20,18)-(20,22) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v2 IL_0000 'M();'", - "2: (29,22)-(29,26) flags=[IsNonLeafFrame] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000003 v1 IL_0000 '{ M();'", - "3: (53,22)-(53,26) flags=[IsNonLeafFrame] pdid=test1.cs docs=[test1.cs] mvid=11111111-1111-1111-1111-111111111111 0x06000004 v1 IL_0000 'M();'" + $"0: {document.FilePath}: (6,18)-(6,22) flags=[MethodUpToDate, IsNonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v2 IL_0000 'M();'", + $"1: {document.FilePath}: (20,18)-(20,22) flags=[MethodUpToDate, IsNonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v2 IL_0000 'M();'", + $"2: {document.FilePath}: (29,22)-(29,26) flags=[IsNonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000003 v1 IL_0000 '{{ M();'", + $"3: {document.FilePath}: (53,22)-(53,26) flags=[IsNonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000004 v1 IL_0000 'M();'" }, baseActiveStatements.Select(s => InspectActiveStatementAndInstruction(s, sourceTextV2))); // Exception Regions - var baseExceptionRegions = await editSession.GetBaseActiveExceptionRegionsAsync(solution, CancellationToken.None).ConfigureAwait(false); + var analyzer = solution.GetProject(project.Id).LanguageServices.GetRequiredService(); + var oldActiveStatements = await baseActiveStatementMap.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None).ConfigureAwait(false); // Note that the spans correspond to the base snapshot (V2). AssertEx.Equal(new[] { - "[(8,16)-(10,9) 'catch']", - "[(18,16)-(21,9) 'catch']", - "[(38,16)-(40,9) 'catch', (34,20)-(36,13) 'finally']", - "[(56,16)-(58,9) 'catch', (51,20)-(54,13) 'catch']", - }, baseExceptionRegions.Select(r => "[" + string.Join(", ", r.Spans.Select(s => $"{s} '{GetFirstLineText(s, sourceTextV2)}'")) + "]")); + $"[{document.FilePath}: (8,16)-(10,9) 'catch']", + $"[{document.FilePath}: (18,16)-(21,9) 'catch']", + $"[{document.FilePath}: (38,16)-(40,9) 'catch', {document.FilePath}: (34,20)-(36,13) 'finally']", + $"[{document.FilePath}: (56,16)-(58,9) 'catch', {document.FilePath}: (51,20)-(54,13) 'catch']", + }, oldActiveStatements.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans.Select(span => $"{span} '{GetFirstLineText(span.Span, sourceTextV2)}'")) + "]")); // GetActiveStatementAndExceptionRegionSpans @@ -621,27 +556,22 @@ static void F4() // F2: Move 'try' one line up (a new non-remappable entries will be added) // F4: Insert 2 new lines before the first 'try' (an existing non-remappable entries will be updated) var newActiveStatementsInChangedDocuments = ImmutableArray.Create( - ( - docs[0], - - ImmutableArray.Create( + new DocumentActiveStatementChanges( + oldSpans: oldActiveStatements, + newStatements: ImmutableArray.Create( baseActiveStatements[0], - baseActiveStatements[1].WithSpan(baseActiveStatements[1].Span.AddLineDelta(-1)), + baseActiveStatements[1].WithFileSpan(baseActiveStatements[1].FileSpan.AddLineDelta(-1)), baseActiveStatements[2], - baseActiveStatements[3].WithSpan(baseActiveStatements[3].Span.AddLineDelta(+2))), - - ImmutableArray.Create( - baseExceptionRegions[0].Spans, - baseExceptionRegions[1].Spans.SelectAsArray(es => es.AddLineDelta(-1)), - baseExceptionRegions[2].Spans, - baseExceptionRegions[3].Spans.SelectAsArray(es => es.AddLineDelta(+2))) - ) - ); + baseActiveStatements[3].WithFileSpan(baseActiveStatements[3].FileSpan.AddLineDelta(+2))), + newExceptionRegions: ImmutableArray.Create( + oldActiveStatements[0].ExceptionRegions.Spans, + oldActiveStatements[1].ExceptionRegions.Spans.SelectAsArray(es => es.AddLineDelta(-1)), + oldActiveStatements[2].ExceptionRegions.Spans, + oldActiveStatements[3].ExceptionRegions.Spans.SelectAsArray(es => es.AddLineDelta(+2))))); EditSession.GetActiveStatementAndExceptionRegionSpans( module1, baseActiveStatementMap, - baseExceptionRegions, updatedMethodTokens: ImmutableArray.Create(0x06000002, 0x06000004), // F2, F4 initialNonRemappableRegions, newActiveStatementsInChangedDocuments, @@ -652,29 +582,29 @@ static void F4() // Note: Since no method have been remapped yet all the following spans are in their pre-remap locations: AssertEx.Equal(new[] { - "0x06000002 v2 | ER (18,16)-(21,9) δ=-1", - "0x06000002 v2 | AS (20,18)-(20,22) δ=-1", - "0x06000003 v1 | AS (30,22)-(30,26) δ=-1", // AS:2 moved -1 in first edit, 0 in second - "0x06000003 v1 | ER (32,20)-(34,13) δ=2", // ER:2.0 moved +2 in first edit, 0 in second - "0x06000003 v1 | ER (36,16)-(38,9) δ=2", // ER:2.0 moved +2 in first edit, 0 in second - "0x06000004 v1 | ER (50,20)-(53,13) δ=3", // ER:3.0 moved +1 in first edit, +2 in second - "0x06000004 v1 | AS (52,22)-(52,26) δ=3", // AS:3 moved +1 in first edit, +2 in second - "0x06000004 v1 | ER (55,16)-(57,9) δ=3", // ER:3.1 moved +1 in first edit, +2 in second - }, nonRemappableRegions.OrderBy(r => r.Region.Span.Start.Line).Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}")); + $"0x06000002 v2 | ER {document.FilePath}: (18,16)-(21,9) δ=-1", + $"0x06000002 v2 | AS {document.FilePath}: (20,18)-(20,22) δ=-1", + $"0x06000003 v1 | AS {document.FilePath}: (30,22)-(30,26) δ=-1", // AS:2 moved -1 in first edit, 0 in second + $"0x06000003 v1 | ER {document.FilePath}: (32,20)-(34,13) δ=2", // ER:2.0 moved +2 in first edit, 0 in second + $"0x06000003 v1 | ER {document.FilePath}: (36,16)-(38,9) δ=2", // ER:2.0 moved +2 in first edit, 0 in second + $"0x06000004 v1 | ER {document.FilePath}: (50,20)-(53,13) δ=3", // ER:3.0 moved +1 in first edit, +2 in second + $"0x06000004 v1 | AS {document.FilePath}: (52,22)-(52,26) δ=3", // AS:3 moved +1 in first edit, +2 in second + $"0x06000004 v1 | ER {document.FilePath}: (55,16)-(57,9) δ=3", // ER:3.1 moved +1 in first edit, +2 in second + }, nonRemappableRegions.OrderBy(r => r.Region.Span.Span.Start.Line).Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}")); AssertEx.Equal(new[] { - "0x06000002 v2 | (17,16)-(20,9) Delta=1", - "0x06000003 v1 | (34,20)-(36,13) Delta=-2", - "0x06000003 v1 | (38,16)-(40,9) Delta=-2", - "0x06000004 v1 | (53,20)-(56,13) Delta=-3", - "0x06000004 v1 | (58,16)-(60,9) Delta=-3", + $"0x06000002 v2 | (17,16)-(20,9) Delta=1", + $"0x06000003 v1 | (34,20)-(36,13) Delta=-2", + $"0x06000003 v1 | (38,16)-(40,9) Delta=-2", + $"0x06000004 v1 | (53,20)-(56,13) Delta=-3", + $"0x06000004 v1 | (58,16)-(60,9) Delta=-3", }, exceptionRegionUpdates.OrderBy(r => r.NewSpan.StartLine).Select(InspectExceptionRegionUpdate)); AssertEx.Equal(new[] { - "0x06000002 v2 IL_0000: (19,18)-(19,22) 'M();'", - "0x06000004 v1 IL_0000: (55,22)-(55,26) 'M();'" + $"0x06000002 v2 IL_0000: (19,18)-(19,22) 'M();'", + $"0x06000004 v1 IL_0000: (55,22)-(55,26) 'M();'" }, activeStatementsInUpdatedMethods.Select(update => $"{InspectActiveStatementUpdate(update)} '{GetFirstLineText(update.NewSpan.ToLinePositionSpan(), sourceTextV3)}'")); } @@ -709,7 +639,7 @@ static void F() // Thread1 stack trace: F (AS:0), M (AS:1 leaf) // Thread2 stack trace: F (AS:0), M (AS:1), M (AS:1 leaf) - var activeStatements = GetActiveStatementDebugInfos( + var activeStatements = GetActiveStatementDebugInfosCSharp( markedSources, methodRowIds: new[] { 1, 2 }, ilOffsets: new[] { 1, 1 }, @@ -721,155 +651,46 @@ static void F() using var workspace = new TestWorkspace(composition: s_composition); var solution = AddDefaultTestSolution(workspace, markedSources); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); var editSession = CreateEditSession(solution, activeStatements); var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); - var docs = GetDocumentIds(solution); // Active Statements - Assert.Equal(1, baseActiveStatementMap.DocumentMap.Count); + Assert.Equal(1, baseActiveStatementMap.DocumentPathMap.Count); AssertEx.Equal(new[] { - "0: (15,14)-(15,18) flags=[PartiallyExecuted, NonUserCode, MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs]", - "1: (6,18)-(6,22) flags=[IsLeafFrame, MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs]", - }, baseActiveStatementMap.DocumentMap[docs[0]].Select(InspectActiveStatement)); + $"1: {document.FilePath}: (6,18)-(6,22) flags=[IsLeafFrame, MethodUpToDate, IsNonLeafFrame]", + $"0: {document.FilePath}: (15,14)-(15,18) flags=[PartiallyExecuted, NonUserCode, MethodUpToDate, IsNonLeafFrame]", + }, baseActiveStatementMap.DocumentPathMap[document.FilePath].Select(InspectActiveStatement)); Assert.Equal(2, baseActiveStatementMap.InstructionMap.Count); var statements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.InstructionId.Method.Token).ToArray(); var s = statements[0]; Assert.Equal(0x06000001, s.InstructionId.Method.Token); - Assert.Equal(0, s.PrimaryDocumentOrdinal); - Assert.Equal(docs[0], s.DocumentIds.Single()); + Assert.Equal(document.FilePath, s.FilePath); Assert.True(s.IsNonLeaf); s = statements[1]; Assert.Equal(0x06000002, s.InstructionId.Method.Token); - Assert.Equal(1, s.PrimaryDocumentOrdinal); - Assert.Equal(docs[0], s.DocumentIds.Single()); + Assert.Equal(document.FilePath, s.FilePath); Assert.True(s.IsLeaf); Assert.True(s.IsNonLeaf); // Exception Regions - var baseExceptionRegions = await editSession.GetBaseActiveExceptionRegionsAsync(solution, CancellationToken.None).ConfigureAwait(false); - - AssertEx.Equal(new[] - { - "[]", - "[(8,8)-(10,9)]" - }, baseExceptionRegions.Select(r => "[" + string.Join(",", r.Spans) + "]")); - } - - [Fact, WorkItem(24320, "https://github.com/dotnet/roslyn/issues/24320")] - public async Task BaseActiveStatementsAndExceptionRegions_LinkedDocuments() - { - var markedSources = new[] - { -@"class Test1 -{ - static void Main() => Project2::Test1.F(); - static void F() => Project4::Test2.M(); -}", -@" -class Test2 -{ - static void M() => Console.WriteLine(); -}" - }; - - var module1 = Guid.NewGuid(); - var module2 = Guid.NewGuid(); - var module4 = Guid.NewGuid(); - - var activeStatements = GetActiveStatementDebugInfos( - markedSources, - methodRowIds: new[] { 1, 2, 1 }, - modules: new[] { module4, module2, module1 }); - - // Project1: Test1.cs [AS 2], Test2.cs - // Project2: Test1.cs (link from P1) [AS 1] - // Project3: Test1.cs (link from P1) - // Project4: Test2.cs (link from P1) [AS 0] - - using var workspace = new TestWorkspace(composition: s_composition); - var solution = AddDefaultTestSolution(workspace, markedSources); - - void AddProjectAndLinkDocument(string projectName, Document doc, SourceText text) - { - var p = solution.AddProject(projectName, projectName, "C#"); - solution = p.Solution.AddDocument(DocumentId.CreateNewId(p.Id, projectName + "->" + doc.Name), doc.Name, text, filePath: doc.FilePath); - } - - var documents = solution.Projects.Single().Documents; - var doc1 = documents.First(); - var doc2 = documents.Skip(1).First(); - var text1 = await doc1.GetTextAsync(); - var text2 = await doc2.GetTextAsync(); - - AddProjectAndLinkDocument("Project2", doc1, text1); - AddProjectAndLinkDocument("Project3", doc1, text1); - AddProjectAndLinkDocument("Project4", doc2, text2); - - var editSession = CreateEditSession(solution, activeStatements); - - var baseActiveStatementsMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false); - var docs = GetDocumentIds(solution); - - // Active Statements - - var documentMap = baseActiveStatementsMap.DocumentMap; - - Assert.Equal(5, docs.Length); - Assert.Equal(5, documentMap.Count); - - // TODO: currently we associate all linked documents to the AS regardless of whether they belong to a project that matches the AS module. - // https://github.com/dotnet/roslyn/issues/24320 - - AssertEx.Equal(new[] - { - "1: (3,29)-(3,49) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs,Project2->test1.cs,Project3->test1.cs]", - "2: (2,32)-(2,52) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs,Project2->test1.cs,Project3->test1.cs]" - }, documentMap[docs[0]].Select(InspectActiveStatement)); + var analyzer = solution.GetProject(project.Id).LanguageServices.GetRequiredService(); + var oldActiveStatements = await baseActiveStatementMap.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None).ConfigureAwait(false); AssertEx.Equal(new[] { - "0: (3,29)-(3,49) flags=[IsLeafFrame, MethodUpToDate] pdid=test2.cs docs=[test2.cs,Project4->test2.cs]", - }, documentMap[docs[1]].Select(InspectActiveStatement)); - - AssertEx.Equal(new[] - { - "1: (3,29)-(3,49) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs,Project2->test1.cs,Project3->test1.cs]", - "2: (2,32)-(2,52) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs,Project2->test1.cs,Project3->test1.cs]" - }, documentMap[docs[2]].Select(InspectActiveStatement)); - - AssertEx.Equal(new[] - { - "1: (3,29)-(3,49) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs,Project2->test1.cs,Project3->test1.cs]", - "2: (2,32)-(2,52) flags=[MethodUpToDate, IsNonLeafFrame] pdid=test1.cs docs=[test1.cs,Project2->test1.cs,Project3->test1.cs]" - }, documentMap[docs[3]].Select(InspectActiveStatement)); - - AssertEx.Equal(new[] - { - "0: (3,29)-(3,49) flags=[IsLeafFrame, MethodUpToDate] pdid=test2.cs docs=[test2.cs,Project4->test2.cs]", - }, documentMap[docs[4]].Select(InspectActiveStatement)); - - Assert.Equal(3, baseActiveStatementsMap.InstructionMap.Count); - - var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); - var s = statements[0]; - Assert.Equal(0x06000001, s.InstructionId.Method.Token); - Assert.Equal(module4, s.InstructionId.Method.Module); - - s = statements[1]; - Assert.Equal(0x06000002, s.InstructionId.Method.Token); - Assert.Equal(module2, s.InstructionId.Method.Module); - - s = statements[2]; - Assert.Equal(0x06000001, s.InstructionId.Method.Token); - Assert.Equal(module1, s.InstructionId.Method.Module); + $"[{document.FilePath}: (8,8)-(10,9)]", + "[]" + }, oldActiveStatements.Select(s => "[" + string.Join(",", s.ExceptionRegions.Spans) + "]")); } } } diff --git a/src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs b/src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs new file mode 100644 index 0000000000000..497182e37ea8e --- /dev/null +++ b/src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests +{ + [UseExportProvider] + public class EmitSolutionUpdateResultsTests + { + [Fact] + public async Task GetHotReloadDiagnostics() + { + using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); + + var sourcePath = Path.Combine(TempRoot.Root, "x", "a.cs"); + var razorPath = Path.Combine(TempRoot.Root, "a.razor"); + + var document = workspace.CurrentSolution. + AddProject("proj", "proj", LanguageNames.CSharp). + WithMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Standard)). + AddDocument(sourcePath, SourceText.From("class C {}", Encoding.UTF8), filePath: Path.Combine(TempRoot.Root, sourcePath)); + + var solution = document.Project.Solution; + + var diagnosticData = ImmutableArray.Create( + new DiagnosticData( + id: "CS0001", + category: "Test", + message: "warning", + enuMessageForBingSearch: "test2 message format", + severity: DiagnosticSeverity.Warning, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + warningLevel: 0, + customTags: ImmutableArray.Create("Test2"), + properties: ImmutableDictionary.Empty, + document.Project.Id, + new DiagnosticDataLocation(document.Id, new TextSpan(1, 2), "a.cs", 0, 0, 0, 5, "a.razor", 10, 10, 10, 15), + language: "C#", + title: "title", + description: "description", + helpLink: "http://link"), + new DiagnosticData( + id: "CS0012", + category: "Test", + message: "error", + enuMessageForBingSearch: "test2 message format", + severity: DiagnosticSeverity.Error, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + warningLevel: 0, + customTags: ImmutableArray.Create("Test2"), + properties: ImmutableDictionary.Empty, + document.Project.Id, + new DiagnosticDataLocation(document.Id, new TextSpan(1, 2), originalFilePath: sourcePath, 0, 0, 0, 5, mappedFilePath: @"..\a.razor", 10, 10, 10, 15), + language: "C#", + title: "title", + description: "description", + helpLink: "http://link")); + + var rudeEdits = ImmutableArray.Create( + (document.Id, ImmutableArray.Create(new RudeEditDiagnostic(RudeEditKind.Insert, TextSpan.FromBounds(1, 10), 123, new[] { "a" }))), + (document.Id, ImmutableArray.Create(new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(1, 10), 123, new[] { "b" })))); + + var actual = await EmitSolutionUpdateResults.GetHotReloadDiagnosticsAsync(solution, diagnosticData, rudeEdits, CancellationToken.None); + + AssertEx.Equal(new[] + { + $@"Error CS0012: {razorPath} (10,10)-(10,15): error", + $@"Error ENC0021: {sourcePath} (0,1)-(0,10): {string.Format(FeaturesResources.Adding_0_will_prevent_the_debug_session_from_continuing, "a")}", + $@"Error ENC0033: {sourcePath} (0,1)-(0,10): {string.Format(FeaturesResources.Deleting_0_will_prevent_the_debug_session_from_continuing, "b")}" + }, actual.Select(d => $"{d.Severity} {d.Id}: {d.FilePath} {d.Span.GetDebuggerDisplay()}: {d.Message}")); + } + } +} diff --git a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 2c4e7342489f7..6a9141fa07e69 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -24,9 +24,9 @@ using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; using Xunit; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; namespace Roslyn.VisualStudio.Next.UnitTests.EditAndContinue { @@ -118,16 +118,16 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) var document1 = localWorkspace.CurrentSolution.Projects.Single().Documents.Single(); - var activeSpans1 = ImmutableArray.Create(TextSpan.FromBounds(1, 2), TextSpan.FromBounds(3, 4)); + var activeSpans1 = ImmutableArray.Create( + new ActiveStatementSpan(0, new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)), ActiveStatementFlags.IsNonLeafFrame, document.Id)); - var solutionActiveStatementSpanProvider = new SolutionActiveStatementSpanProvider((documentId, cancellationToken) => + var activeStatementSpanProvider = new ActiveStatementSpanProvider((documentId, path, cancellationToken) => { Assert.Equal(document1.Id, documentId); + Assert.Equal("test.cs", path); return new(activeSpans1); }); - var documentActiveStatementSpanProvider = new DocumentActiveStatementSpanProvider(cancellationToken => new(activeSpans1)); - var proxy = new RemoteEditAndContinueServiceProxy(localWorkspace); // StartDebuggingSession @@ -191,11 +191,11 @@ await proxy.StartDebuggingSessionAsync( { Assert.Equal("proj", solution.Projects.Single().Name); Assert.Equal("test.cs", sourceFilePath); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).Result); return true; }; - Assert.True(await proxy.HasChangesAsync(localWorkspace.CurrentSolution, solutionActiveStatementSpanProvider, "test.cs", CancellationToken.None).ConfigureAwait(false)); + Assert.True(await proxy.HasChangesAsync(localWorkspace.CurrentSolution, activeStatementSpanProvider, "test.cs", CancellationToken.None).ConfigureAwait(false)); // EmitSolutionUpdate @@ -205,7 +205,7 @@ await proxy.StartDebuggingSessionAsync( { var project = solution.Projects.Single(); Assert.Equal("proj", project.Name); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).Result); var deltas = ImmutableArray.Create(new ManagedModuleUpdate( module: moduleId1, @@ -213,6 +213,7 @@ await proxy.StartDebuggingSessionAsync( metadataDelta: ImmutableArray.Create(3, 4), pdbDelta: ImmutableArray.Create(5, 6), updatedMethods: ImmutableArray.Create(0x06000001), + updatedTypes: ImmutableArray.Create(0x02000001), sequencePoints: ImmutableArray.Create(new SequencePointUpdates("file.cs", ImmutableArray.Create(new SourceLineUpdate(1, 2)))), activeStatements: ImmutableArray.Create(new ManagedActiveStatementUpdate(instructionId1.Method.Method, instructionId1.ILOffset, span1.ToSourceSpan())), exceptionRegions: ImmutableArray.Create(exceptionRegionUpdate1))); @@ -229,7 +230,8 @@ await proxy.StartDebuggingSessionAsync( return new(updates, diagnostics, documentsWithRudeEdits); }; - var updates = await proxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, solutionActiveStatementSpanProvider, mockDiagnosticService, diagnosticUpdateSource, CancellationToken.None).ConfigureAwait(false); + var (updates, _, _) = await proxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, activeStatementSpanProvider, mockDiagnosticService, diagnosticUpdateSource, CancellationToken.None).ConfigureAwait(false); + VerifyReanalyzeInvocation(ImmutableArray.Create(document1.Id)); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); @@ -256,6 +258,7 @@ await proxy.StartDebuggingSessionAsync( AssertEx.Equal(new byte[] { 3, 4 }, delta.MetadataDelta); AssertEx.Equal(new byte[] { 5, 6 }, delta.PdbDelta); AssertEx.Equal(new[] { 0x06000001 }, delta.UpdatedMethods); + AssertEx.Equal(new[] { 0x02000001 }, delta.UpdatedTypes); var lineEdit = delta.SequencePoints.Single(); Assert.Equal("file.cs", lineEdit.FileName); @@ -290,13 +293,13 @@ await proxy.StartDebuggingSessionAsync( { Assert.Equal("proj", solution.Projects.Single().Name); Assert.Equal(instructionId1, instructionId); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).Result); return new LinePositionSpan(new LinePosition(1, 2), new LinePosition(1, 5)); }; Assert.Equal(span1, await proxy.GetCurrentActiveStatementPositionAsync( localWorkspace.CurrentSolution, - solutionActiveStatementSpanProvider, + activeStatementSpanProvider, instructionId1, CancellationToken.None).ConfigureAwait(false)); @@ -312,32 +315,34 @@ await proxy.StartDebuggingSessionAsync( // GetBaseActiveStatementSpans + var activeStatementSpan1 = new ActiveStatementSpan(0, span1, ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.PartiallyExecuted, unmappedDocumentId: document1.Id); + mockEncService.GetBaseActiveStatementSpansImpl = (solution, documentIds) => { AssertEx.Equal(new[] { document1.Id }, documentIds); - return ImmutableArray.Create(ImmutableArray.Create((span1, ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.PartiallyExecuted))); + return ImmutableArray.Create(ImmutableArray.Create(activeStatementSpan1)); }; var baseActiveSpans = await proxy.GetBaseActiveStatementSpansAsync(localWorkspace.CurrentSolution, ImmutableArray.Create(document1.Id), CancellationToken.None).ConfigureAwait(false); - Assert.Equal((span1, ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.PartiallyExecuted), baseActiveSpans.Single().Single()); + Assert.Equal(activeStatementSpan1, baseActiveSpans.Single().Single()); // GetDocumentActiveStatementSpans mockEncService.GetAdjustedActiveStatementSpansImpl = (document, activeStatementSpanProvider) => { Assert.Equal("test.cs", document.Name); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(CancellationToken.None).Result); - return ImmutableArray.Create((span1, ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.PartiallyExecuted)); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document.Id, "test.cs", CancellationToken.None).Result); + return ImmutableArray.Create(activeStatementSpan1); }; - var documentActiveSpans = await proxy.GetAdjustedActiveStatementSpansAsync(document1, documentActiveStatementSpanProvider, CancellationToken.None).ConfigureAwait(false); - Assert.Equal((span1, ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.PartiallyExecuted), documentActiveSpans.Single()); + var documentActiveSpans = await proxy.GetAdjustedActiveStatementSpansAsync(document1, activeStatementSpanProvider, CancellationToken.None).ConfigureAwait(false); + Assert.Equal(activeStatementSpan1, documentActiveSpans.Single()); // GetDocumentActiveStatementSpans (default array) mockEncService.GetAdjustedActiveStatementSpansImpl = (document, _) => default; - documentActiveSpans = await proxy.GetAdjustedActiveStatementSpansAsync(document1, documentActiveStatementSpanProvider, CancellationToken.None).ConfigureAwait(false); + documentActiveSpans = await proxy.GetAdjustedActiveStatementSpansAsync(document1, activeStatementSpanProvider, CancellationToken.None).ConfigureAwait(false); Assert.True(documentActiveSpans.IsDefault); // OnSourceFileUpdatedAsync diff --git a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs index 4643bb76e6670..f46977f745bb9 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs @@ -49,6 +49,11 @@ public void ToDiagnostic() RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, RudeEditKind.SwitchBetweenLambdaAndLocalFunction, RudeEditKind.InsertMethodWithExplicitInterfaceSpecifier, + RudeEditKind.AddRecordPositionalParameter, + RudeEditKind.DeleteRecordPositionalParameter, + RudeEditKind.NotSupportedByRuntime, + RudeEditKind.MakeMethodAsync, + RudeEditKind.MakeMethodIterator }; var arg2 = new HashSet() diff --git a/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs b/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs index 295ab30e205a6..12f2987cb8091 100644 --- a/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs +++ b/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs @@ -58,7 +58,7 @@ public static void CodeStyleSettingEnumFactory(DayOfWeek defaultValue) private static Option2> CreateBoolOption(bool @default = false) { var option = CodeStyleOption2.Default; - option.Value = @default; + option = (CodeStyleOption2)((ICodeStyleOption)option).WithValue(@default); return new Option2>(feature: "TestFeature", name: "TestOption", defaultValue: option); @@ -68,7 +68,7 @@ private static Option2> CreateEnumOption(T @default) where T : notnull, Enum { var option = CodeStyleOption2.Default; - option.Value = @default; + option = (CodeStyleOption2)((ICodeStyleOption)option).WithValue(@default); return new Option2>(feature: "TestFeature", name: "TestOption", defaultValue: option); diff --git a/src/EditorFeatures/Test/Extensions/ISemanticSnapshotExtensionTests.cs b/src/EditorFeatures/Test/Extensions/ISemanticSnapshotExtensionTests.cs deleted file mode 100644 index b5aab9b2e3e1d..0000000000000 --- a/src/EditorFeatures/Test/Extensions/ISemanticSnapshotExtensionTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Extensions -{ - [UseExportProvider] - public class ISemanticSnapshotExtensionTests - { - [Fact] - public async Task TryGetSymbolTouchingPositionOnLeadingTrivia() - { - using var workspace = TestWorkspace.CreateCSharp( - @"using System; - class Program - { - static void Main() - { - $$#pragma warning disable 612 - Goo(); - #pragma warning restore 612 - } - }"); - var position = workspace.Documents.Single(d => d.CursorPosition.HasValue).CursorPosition.Value; - var snapshot = workspace.Documents.Single().GetTextBuffer().CurrentSnapshot; - - var document = workspace.CurrentSolution.GetDocument(workspace.Documents.Single().Id); - Assert.NotNull(document); - - var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position); - Assert.Null(symbol); - } - } -} diff --git a/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs b/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs index 349d6236857f4..38bcb96c02e7b 100644 --- a/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs +++ b/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs @@ -31,7 +31,7 @@ private class MockFindUsagesContext : FindUsagesContext { public readonly List Result = new List(); - public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition) + public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { lock (Result) { @@ -49,15 +49,15 @@ private class MockStreamingFindUsagesPresenter : IStreamingFindUsagesPresenter public MockStreamingFindUsagesPresenter(FindUsagesContext context) => _context = context; - public FindUsagesContext StartSearch(string title, bool supportsReferences, CancellationToken cancellationToken) - => _context; + public (FindUsagesContext, CancellationToken) StartSearch(string title, bool supportsReferences) + => (_context, CancellationToken.None); public void ClearAll() { } - public FindUsagesContext StartSearchWithCustomColumns(string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken) - => _context; + public (FindUsagesContext, CancellationToken) StartSearchWithCustomColumns(string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) + => (_context, CancellationToken.None); } [WpfFact, Trait(Traits.Feature, Traits.Features.FindReferences)] diff --git a/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs b/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs new file mode 100644 index 0000000000000..7f1c087caf2ba --- /dev/null +++ b/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs @@ -0,0 +1,1723 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.InheritanceMargin +{ + [Trait(Traits.Feature, Traits.Features.InheritanceMargin)] + [UseExportProvider] + public class InheritanceMarginTests + { + private const string SearchAreaTag = "SeachTag"; + + #region Helpers + + private static Task VerifyNoItemForDocumentAsync(string markup, string languageName) + => VerifyInSingleDocumentAsync(markup, languageName); + + private static Task VerifyInSingleDocumentAsync( + string markup, + string languageName, + params TestInheritanceMemberItem[] memberItems) + { + var workspaceFile = $@" + + + + {markup} + + +"; + + var cancellationToken = CancellationToken.None; + + using var testWorkspace = TestWorkspace.Create( + workspaceFile, + composition: EditorTestCompositions.EditorFeatures); + + var testHostDocument = testWorkspace.Documents[0]; + return VerifyTestMemberInDocumentAsync(testWorkspace, testHostDocument, memberItems, cancellationToken); + } + + private static async Task VerifyTestMemberInDocumentAsync( + TestWorkspace testWorkspace, + TestHostDocument testHostDocument, + TestInheritanceMemberItem[] memberItems, + CancellationToken cancellationToken) + { + var document = testWorkspace.CurrentSolution.GetRequiredDocument(testHostDocument.Id); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var searchingSpan = root.Span; + // Look for the search span, if not found, then pass the whole document span to the service. + if (testHostDocument.AnnotatedSpans.TryGetValue(SearchAreaTag, out var spans) && spans.IsSingle()) + { + searchingSpan = spans[0]; + } + + var service = document.GetRequiredLanguageService(); + var actualItems = await service.GetInheritanceMemberItemsAsync( + document, + searchingSpan, + cancellationToken).ConfigureAwait(false); + + var sortedActualItems = actualItems.OrderBy(item => item.LineNumber).ToImmutableArray(); + var sortedExpectedItems = memberItems.OrderBy(item => item.LineNumber).ToImmutableArray(); + Assert.Equal(sortedExpectedItems.Length, sortedActualItems.Length); + + for (var i = 0; i < sortedActualItems.Length; i++) + { + VerifyInheritanceMember(testWorkspace, sortedExpectedItems[i], sortedActualItems[i]); + } + } + + private static void VerifyInheritanceMember(TestWorkspace testWorkspace, TestInheritanceMemberItem expectedItem, InheritanceMarginItem actualItem) + { + Assert.Equal(expectedItem.LineNumber, actualItem.LineNumber); + Assert.Equal(expectedItem.MemberName, actualItem.DisplayTexts.JoinText()); + Assert.Equal(expectedItem.Targets.Length, actualItem.TargetItems.Length); + var expectedTargets = expectedItem.Targets + .Select(info => TestInheritanceTargetItem.Create(info, testWorkspace)) + .OrderBy(target => target.TargetSymbolName) + .ToImmutableArray(); + var sortedActualTargets = actualItem.TargetItems.OrderBy(target => target.DefinitionItem.DisplayParts.JoinText()) + .ToImmutableArray(); + for (var i = 0; i < expectedTargets.Length; i++) + { + VerifyInheritanceTarget(expectedTargets[i], sortedActualTargets[i]); + } + } + + private static void VerifyInheritanceTarget(TestInheritanceTargetItem expectedTarget, InheritanceTargetItem actualTarget) + { + Assert.Equal(expectedTarget.TargetSymbolName, actualTarget.DefinitionItem.DisplayParts.JoinText()); + Assert.Equal(expectedTarget.RelationshipToMember, actualTarget.RelationToMember); + + if (expectedTarget.IsInMetadata) + { + Assert.True(actualTarget.DefinitionItem.Properties.ContainsKey("MetadataSymbolKey")); + Assert.True(actualTarget.DefinitionItem.SourceSpans.IsEmpty); + } + else + { + var actualDocumentSpans = actualTarget.DefinitionItem.SourceSpans.OrderBy(documentSpan => documentSpan.SourceSpan.Start).ToImmutableArray(); + var expectedDocumentSpans = expectedTarget.DocumentSpans.OrderBy(documentSpan => documentSpan.SourceSpan.Start).ToImmutableArray(); + Assert.Equal(expectedDocumentSpans.Length, actualDocumentSpans.Length); + for (var i = 0; i < actualDocumentSpans.Length; i++) + { + Assert.Equal(expectedDocumentSpans[i].SourceSpan, actualDocumentSpans[i].SourceSpan); + Assert.Equal(expectedDocumentSpans[i].Document.FilePath, actualDocumentSpans[i].Document.FilePath); + } + } + } + + /// + /// Project of markup1 is referencing project of markup2 + /// + private static async Task VerifyInDifferentProjectsAsync( + (string markupInProject1, string languageName) markup1, + (string markupInProject2, string languageName) markup2, + TestInheritanceMemberItem[] memberItemsInMarkup1, + TestInheritanceMemberItem[] memberItemsInMarkup2) + { + var workspaceFile = + $@" + + + Assembly2 + + {markup1.markupInProject1} + + + + + {markup2.markupInProject2} + + +"; + + var cancellationToken = CancellationToken.None; + using var testWorkspace = TestWorkspace.Create( + workspaceFile, + composition: EditorTestCompositions.EditorFeatures); + + var testHostDocument1 = testWorkspace.Documents.Single(doc => doc.Project.AssemblyName.Equals("Assembly1")); + var testHostDocument2 = testWorkspace.Documents.Single(doc => doc.Project.AssemblyName.Equals("Assembly2")); + await VerifyTestMemberInDocumentAsync(testWorkspace, testHostDocument1, memberItemsInMarkup1, cancellationToken).ConfigureAwait(false); + await VerifyTestMemberInDocumentAsync(testWorkspace, testHostDocument2, memberItemsInMarkup2, cancellationToken).ConfigureAwait(false); + } + + private class TestInheritanceMemberItem + { + public readonly int LineNumber; + public readonly string MemberName; + public readonly ImmutableArray Targets; + + public TestInheritanceMemberItem( + int lineNumber, + string memberName, + ImmutableArray targets) + { + LineNumber = lineNumber; + MemberName = memberName; + Targets = targets; + } + } + + private class TargetInfo + { + public readonly string TargetSymbolDisplayName; + public readonly string? LocationTag; + public readonly InheritanceRelationship Relationship; + public readonly bool InMetadata; + + public TargetInfo( + string targetSymbolDisplayName, + string locationTag, + InheritanceRelationship relationship) + { + TargetSymbolDisplayName = targetSymbolDisplayName; + LocationTag = locationTag; + Relationship = relationship; + InMetadata = false; + } + + public TargetInfo( + string targetSymbolDisplayName, + InheritanceRelationship relationship, + bool inMetadata) + { + TargetSymbolDisplayName = targetSymbolDisplayName; + Relationship = relationship; + InMetadata = inMetadata; + LocationTag = null; + } + } + + private class TestInheritanceTargetItem + { + public readonly string TargetSymbolName; + public readonly InheritanceRelationship RelationshipToMember; + public readonly ImmutableArray DocumentSpans; + public readonly bool IsInMetadata; + + public TestInheritanceTargetItem( + string targetSymbolName, + InheritanceRelationship relationshipToMember, + ImmutableArray documentSpans, + bool isInMetadata) + { + TargetSymbolName = targetSymbolName; + RelationshipToMember = relationshipToMember; + DocumentSpans = documentSpans; + IsInMetadata = isInMetadata; + } + + public static TestInheritanceTargetItem Create( + TargetInfo targetInfo, + TestWorkspace testWorkspace) + { + if (targetInfo.InMetadata) + { + return new TestInheritanceTargetItem( + targetInfo.TargetSymbolDisplayName, + targetInfo.Relationship, + ImmutableArray.Empty, + isInMetadata: true); + } + else + { + using var _ = ArrayBuilder.GetInstance(out var builder); + // If the target is not in metadata, there must be a location tag to give the span! + Assert.True(targetInfo.LocationTag != null); + foreach (var testHostDocument in testWorkspace.Documents) + { + if (targetInfo.LocationTag != null) + { + var annotatedSpans = testHostDocument.AnnotatedSpans; + if (annotatedSpans.TryGetValue(targetInfo.LocationTag, out var spans)) + { + var document = testWorkspace.CurrentSolution.GetRequiredDocument(testHostDocument.Id); + builder.AddRange(spans.Select(span => new DocumentSpan(document, span))); + } + } + } + + return new TestInheritanceTargetItem( + targetInfo.TargetSymbolDisplayName, + targetInfo.Relationship, + builder.ToImmutable(), + isInMetadata: false); + } + } + } + + #endregion + + #region TestsForCSharp + + [Fact] + public Task TestCSharpClassWithErrorBaseType() + { + var markup = @" +public class Bar : SomethingUnknown +{ +}"; + return VerifyNoItemForDocumentAsync(markup, LanguageNames.CSharp); + } + + [Fact] + public Task TestCSharpReferencingMetadata() + { + var markup = @" +using System.Collections; +public class Bar : IEnumerable +{ + public IEnumerator GetEnumerator () { return null }; +}"; + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IEnumerable", + relationship: InheritanceRelationship.Implementing, + inMetadata: true))); + + var itemForGetEnumerator = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "IEnumerator Bar.GetEnumerator()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "IEnumerator IEnumerable.GetEnumerator()", + relationship: InheritanceRelationship.Implementing, + inMetadata: true))); + + return VerifyInSingleDocumentAsync(markup, LanguageNames.CSharp, itemForBar, itemForGetEnumerator); + } + + [Fact] + public Task TestCSharpClassImplementingInterface() + { + var markup = @" +interface {|target1:IBar|} { } +public class {|target2:Bar|} : IBar +{ +} + "; + + var itemOnLine2 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented))); + + var itemOnLine3 = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemOnLine2, + itemOnLine3); + } + + [Fact] + public Task TestCSharpInterfaceImplementingInterface() + { + var markup = @" +interface {|target1:IBar|} { } +interface {|target2:IBar2|} : IBar { } + "; + + var itemOnLine2 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar2", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented)) + ); + var itemOnLine3 = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "interface IBar2", + targets: ImmutableArray.Empty + .Add(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing)) + ); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemOnLine2, + itemOnLine3); + } + + [Fact] + public Task TestCSharpClassInheritsClass() + { + var markup = @" +class {|target2:A|} { } +class {|target1:B|} : A { } + "; + + var itemOnLine2 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "class A", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class B", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented)) + ); + var itemOnLine3 = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "class B", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class A", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing)) + ); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemOnLine2, + itemOnLine3); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("enum")] + [InlineData("interface")] + public Task TestCSharpTypeWithoutBaseType(string typeName) + { + var markup = $@" +public {typeName} Bar +{{ +}}"; + return VerifyNoItemForDocumentAsync(markup, LanguageNames.CSharp); + } + + [Theory] + [InlineData("public Bar() { }")] + [InlineData("public static void Bar3() { }")] + [InlineData("public static void ~Bar() { }")] + [InlineData("public static Bar operator +(Bar a, Bar b) => new Bar();")] + public Task TestCSharpSpecialMember(string memberDeclaration) + { + var markup = $@" +public abstract class {{|target1:Bar1|}} +{{}} +public class Bar : Bar1 +{{ + {{|{SearchAreaTag}:{memberDeclaration}|}} +}}"; + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "class Bar", + targets: ImmutableArray.Create( + new TargetInfo( + targetSymbolDisplayName: "class Bar1", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing)))); + } + + [Fact] + public Task TestCSharpMetadataInterface() + { + var markup = @" +using System.Collections; +public class Bar : IEnumerable +{ +}"; + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "class Bar", + targets: ImmutableArray.Create( + new TargetInfo( + targetSymbolDisplayName: "interface IEnumerable", + relationship: InheritanceRelationship.Implementing, + inMetadata: true)))); + } + + [Fact] + public Task TestCSharpEventDeclaration() + { + var markup = @" +using System; +interface {|target2:IBar|} +{ + event EventHandler {|target4:e|}; +} +public class {|target1:Bar|} : IBar +{ + public event EventHandler {|target3:e|} + { + add {} remove {} + } +}"; + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "interface IBar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "class Bar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + var itemForEventInInterface = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "event EventHandler IBar.e", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler Bar.e", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForEventInClass = new TestInheritanceMemberItem( + lineNumber: 9, + memberName: "event EventHandler Bar.e", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler IBar.e", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemForIBar, + itemForBar, + itemForEventInInterface, + itemForEventInClass); + } + + [Fact] + public Task TestCSharpEventFieldDeclarations() + { + var markup = @"using System; +interface {|target2:IBar|} +{ + event EventHandler {|target5:e1|}, {|target6:e2|}; +} +public class {|target1:Bar|} : IBar +{ + public event EventHandler {|target3:e1|}, {|target4:e2|}; +}"; + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface IBar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "class Bar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + var itemForE1InInterface = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "event EventHandler IBar.e1", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler Bar.e1", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForE2InInterface = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "event EventHandler IBar.e2", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler Bar.e2", + locationTag: "target4", + relationship: InheritanceRelationship.Implemented))); + + var itemForE1InClass = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "event EventHandler Bar.e1", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler IBar.e1", + locationTag: "target5", + relationship: InheritanceRelationship.Implementing))); + + var itemForE2InClass = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "event EventHandler Bar.e2", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler IBar.e2", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemForIBar, + itemForBar, + itemForE1InInterface, + itemForE2InInterface, + itemForE1InClass, + itemForE2InClass); + } + + [Fact] + public Task TestCSharpInterfaceMembers() + { + var markup = @"using System; +interface {|target1:IBar|} +{ + void {|target4:Foo|}(); + int {|target6:Poo|} { get; set; } + event EventHandler {|target8:Eoo|}; + int {|target9:this|}[int i] { get; set; } +} +public class {|target2:Bar|} : IBar +{ + public void {|target3:Foo|}() { } + public int {|target5:Poo|} { get; set; } + public event EventHandler {|target7:Eoo|}; + public int {|target10:this|}[int i] { get => 1; set { } } +}"; + var itemForEooInClass = new TestInheritanceMemberItem( + lineNumber: 13, + memberName: "event EventHandler Bar.Eoo", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler IBar.Eoo", + locationTag: "target8", + relationship: InheritanceRelationship.Implementing)) + ); + + var itemForEooInInterface = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "event EventHandler IBar.Eoo", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "event EventHandler Bar.Eoo", + locationTag: "target7", + relationship: InheritanceRelationship.Implemented)) + ); + + var itemForPooInInterface = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "int IBar.Poo { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "int Bar.Poo { get; set; }", + locationTag: "target5", + relationship: InheritanceRelationship.Implemented)) + ); + + var itemForPooInClass = new TestInheritanceMemberItem( + lineNumber: 12, + memberName: "int Bar.Poo { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "int IBar.Poo { get; set; }", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing)) + ); + + var itemForFooInInterface = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "void IBar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void Bar.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented)) + ); + + var itemForFooInClass = new TestInheritanceMemberItem( + lineNumber: 11, + memberName: "void Bar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void IBar.Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing)) + ); + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented)) + ); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 9, + memberName: "class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing)) + ); + + var itemForIndexerInClass = new TestInheritanceMemberItem( + lineNumber: 14, + memberName: "int Bar.this[int] { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "int IBar.this[int] { get; set; }", + locationTag: "target9", + relationship: InheritanceRelationship.Implementing)) + ); + + var itemForIndexerInInterface = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "int IBar.this[int] { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "int Bar.this[int] { get; set; }", + locationTag: "target10", + relationship: InheritanceRelationship.Implemented)) + ); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemForEooInClass, + itemForEooInInterface, + itemForPooInInterface, + itemForPooInClass, + itemForFooInInterface, + itemForFooInClass, + itemForIBar, + itemForBar, + itemForIndexerInInterface, + itemForIndexerInClass); + } + + [Theory] + [InlineData("abstract")] + [InlineData("virtual")] + public Task TestCSharpAbstractClassMembers(string modifier) + { + var markup = $@"using System; +public abstract class {{|target2:Bar|}} +{{ + public {modifier} void {{|target4:Foo|}}(); + public {modifier} int {{|target6:Poo|}} {{ get; set; }} + public {modifier} event EventHandler {{|target8:Eoo|}}; +}} +public class {{|target1:Bar2|}} : Bar +{{ + public override void {{|target3:Foo|}}() {{ }} + public override int {{|target5:Poo|}} {{ get; set; }} + public override event EventHandler {{|target7:Eoo|}}; +}} + "; + + var itemForEooInClass = new TestInheritanceMemberItem( + lineNumber: 12, + memberName: "override event EventHandler Bar2.Eoo", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: $"{modifier} event EventHandler Bar.Eoo", + locationTag: "target8", + relationship: InheritanceRelationship.Overriding))); + + var itemForEooInAbstractClass = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: $"{modifier} event EventHandler Bar.Eoo", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "override event EventHandler Bar2.Eoo", + locationTag: "target7", + relationship: InheritanceRelationship.Overridden))); + + var itemForPooInClass = new TestInheritanceMemberItem( + lineNumber: 11, + memberName: "override int Bar2.Poo { get; set; }", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: $"{modifier} int Bar.Poo {{ get; set; }}", + locationTag: "target6", + relationship: InheritanceRelationship.Overriding))); + + var itemForPooInAbstractClass = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: $"{modifier} int Bar.Poo {{ get; set; }}", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "override int Bar2.Poo { get; set; }", + locationTag: "target5", + relationship: InheritanceRelationship.Overridden))); + + var itemForFooInAbstractClass = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: $"{modifier} void Bar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "override void Bar2.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Overridden))); + + var itemForFooInClass = new TestInheritanceMemberItem( + lineNumber: 10, + memberName: "override void Bar2.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: $"{modifier} void Bar.Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Overriding))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar2", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar2 = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "class Bar2", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemForBar, + itemForBar2, + itemForFooInAbstractClass, + itemForFooInClass, + itemForPooInClass, + itemForPooInAbstractClass, + itemForEooInClass, + itemForEooInAbstractClass); + } + + [Theory] + [CombinatorialData] + public Task TestCSharpOverrideMemberCanFindImplementingInterface(bool testDuplicate) + { + var markup1 = @"using System; +public interface {|target4:IBar|} +{ + void {|target6:Foo|}(); +} +public class {|target1:Bar1|} : IBar +{ + public virtual void {|target2:Foo|}() { } +} +public class {|target5:Bar2|} : Bar1 +{ + public override void {|target3:Foo|}() { } +}"; + + var markup2 = @"using System; +public interface {|target4:IBar|} +{ + void {|target6:Foo|}(); +} +public class {|target1:Bar1|} : IBar +{ + public virtual void {|target2:Foo|}() { } +} +public class {|target5:Bar2|} : Bar1, IBar +{ + public override void {|target3:Foo|}() { } +}"; + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar1", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented), + new TargetInfo( + targetSymbolDisplayName: "class Bar2", + locationTag: "target5", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInIBar = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "void IBar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "virtual void Bar1.Foo()", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented), + new TargetInfo( + targetSymbolDisplayName: "override void Bar2.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar1 = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "class Bar1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "class Bar2", + locationTag: "target5", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInBar1 = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "virtual void Bar1.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void IBar.Foo()", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "override void Bar2.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Overridden))); + + var itemForBar2 = new TestInheritanceMemberItem( + lineNumber: 10, + memberName: "class Bar2", + targets: ImmutableArray.Create( + new TargetInfo( + targetSymbolDisplayName: "class Bar1", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInBar2 = new TestInheritanceMemberItem( + lineNumber: 12, + memberName: "override void Bar2.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void IBar.Foo()", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "virtual void Bar1.Foo()", + locationTag: "target2", + relationship: InheritanceRelationship.Overriding))); + + return VerifyInSingleDocumentAsync( + testDuplicate ? markup2 : markup1, + LanguageNames.CSharp, + itemForIBar, + itemForFooInIBar, + itemForBar1, + itemForFooInBar1, + itemForBar2, + itemForFooInBar2); + } + + [Fact] + public Task TestCSharpFindGenericsBaseType() + { + var lessThanToken = SecurityElement.Escape("<"); + var greaterThanToken = SecurityElement.Escape(">"); + var markup = $@" +public interface {{|target2:IBar|}}{lessThanToken}T{greaterThanToken} +{{ + void {{|target4:Foo|}}(); +}} + +public class {{|target1:Bar2|}} : IBar{lessThanToken}int{greaterThanToken}, IBar{lessThanToken}string{greaterThanToken} +{{ + public void {{|target3:Foo|}}(); +}}"; + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar2", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInIBar = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "void IBar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void Bar2.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + // Only have one IBar item + var itemForBar2 = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "class Bar2", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + // Only have one IBar.Foo item + var itemForFooInBar2 = new TestInheritanceMemberItem( + lineNumber: 9, + memberName: "void Bar2.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void IBar.Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.CSharp, + itemForIBar, + itemForFooInIBar, + itemForBar2, + itemForFooInBar2); + } + + #endregion + + #region TestsForVisualBasic + + [Fact] + public Task TestVisualBasicWithErrorBaseType() + { + var markup = @" +Namespace MyNamespace + Public Class Bar + Implements SomethingNotExist + End Class +End Namespace"; + + return VerifyNoItemForDocumentAsync(markup, LanguageNames.VisualBasic); + } + + [Fact] + public Task TestVisualBasicReferencingMetadata() + { + var markup = @" +Namespace MyNamespace + Public Class Bar + Implements System.Collections.IEnumerable + Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator + Throw New NotImplementedException() + End Function + End Class +End Namespace"; + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IEnumerable", + relationship: InheritanceRelationship.Implementing, + inMetadata: true))); + + var itemForGetEnumerator = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "Function Bar.GetEnumerator() As IEnumerator", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Function IEnumerable.GetEnumerator() As IEnumerator", + relationship: InheritanceRelationship.Implementing, + inMetadata: true))); + + return VerifyInSingleDocumentAsync(markup, LanguageNames.VisualBasic, itemForBar, itemForGetEnumerator); + } + + [Fact] + public Task TestVisualBasicClassImplementingInterface() + { + var markup = @" +Interface {|target2:IBar|} +End Interface +Class {|target1:Bar|} + Implements IBar +End Class"; + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "Class Bar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + itemForIBar, + itemForBar); + } + + [Fact] + public Task TestVisualBasicInterfaceImplementingInterface() + { + var markup = @" +Interface {|target2:IBar2|} +End Interface +Interface {|target1:IBar|} + Inherits IBar2 +End Interface"; + + var itemForIBar2 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar2", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "Interface IBar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar2", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + return VerifyInSingleDocumentAsync(markup, LanguageNames.VisualBasic, itemForIBar2, itemForIBar); + } + + [Fact] + public Task TestVisualBasicClassInheritsClass() + { + var markup = @" +Class {|target2:Bar2|} +End Class +Class {|target1:Bar|} + Inherits Bar2 +End Class"; + + var itemForBar2 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Class Bar2", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "Class Bar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar2", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + return VerifyInSingleDocumentAsync(markup, LanguageNames.VisualBasic, itemForBar2, itemForBar); + } + + [Theory] + [InlineData("Class")] + [InlineData("Structure")] + [InlineData("Enum")] + [InlineData("Interface")] + public Task TestVisualBasicTypeWithoutBaseType(string typeName) + { + var markup = $@" +{typeName} Bar +End {typeName}"; + + return VerifyNoItemForDocumentAsync(markup, LanguageNames.VisualBasic); + } + + [Fact] + public Task TestVisualBasicMetadataInterface() + { + var markup = @" +Imports System.Collections +Class Bar + Implements IEnumerable +End Class"; + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Class Bar", + targets: ImmutableArray.Create( + new TargetInfo( + targetSymbolDisplayName: "Interface IEnumerable", + relationship: InheritanceRelationship.Implementing, + inMetadata: true)))); + } + + [Fact] + public Task TestVisualBasicEventStatement() + { + var markup = @" +Interface {|target2:IBar|} + Event {|target4:e|} As EventHandler +End Interface +Class {|target1:Bar|} + Implements IBar + Public Event {|target3:e|} As EventHandler Implements IBar.e +End Class"; + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "Class Bar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + var itemForEventInInterface = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Event IBar.e As EventHandler", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Event Bar.e As EventHandler", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForEventInClass = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "Event Bar.e As EventHandler", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Event IBar.e As EventHandler", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + itemForIBar, + itemForBar, + itemForEventInInterface, + itemForEventInClass); + } + + [Fact] + public Task TestVisualBasicEventBlock() + { + var markup = @" +Interface {|target2:IBar|} + Event {|target4:e|} As EventHandler +End Interface +Class {|target1:Bar|} + Implements IBar + Public Custom Event {|target3:e|} As EventHandler Implements IBar.e + End Event +End Class"; + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "Class Bar", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + var itemForEventInInterface = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Event IBar.e As EventHandler", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Event Bar.e As EventHandler", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForEventInClass = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "Event Bar.e As EventHandler", + ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Event IBar.e As EventHandler", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + itemForIBar, + itemForBar, + itemForEventInInterface, + itemForEventInClass); + } + + [Fact] + public Task TestVisualBasicInterfaceMembers() + { + var markup = @" +Interface {|target2:IBar|} + Property {|target4:Poo|} As Integer + Function {|target6:Foo|}() As Integer +End Interface + +Class {|target1:Bar|} + Implements IBar + Public Property {|target3:Poo|} As Integer Implements IBar.Poo + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property + Public Function {|target5:Foo|}() As Integer Implements IBar.Foo + Return 1 + End Function +End Class"; + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "Class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + var itemForPooInInterface = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Property IBar.Poo As Integer", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Property Bar.Poo As Integer", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForPooInClass = new TestInheritanceMemberItem( + lineNumber: 9, + memberName: "Property Bar.Poo As Integer", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Property IBar.Poo As Integer", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInInterface = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "Function IBar.Foo() As Integer", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Function Bar.Foo() As Integer", + locationTag: "target5", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInClass = new TestInheritanceMemberItem( + lineNumber: 16, + memberName: "Function Bar.Foo() As Integer", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Function IBar.Foo() As Integer", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + itemForIBar, + itemForBar, + itemForPooInInterface, + itemForPooInClass, + itemForFooInInterface, + itemForFooInClass); + } + + [Fact] + public Task TestVisualBasicMustInheritClassMember() + { + var markup = @" +MustInherit Class {|target2:Bar1|} + Public MustOverride Sub {|target4:Foo|}() +End Class + +Class {|target1:Bar|} + Inherits Bar1 + Public Overrides Sub {|target3:Foo|}() + End Sub +End Class"; + var itemForBar1 = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Class Bar1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: $"Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "Class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar1", + locationTag: "target2", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInBar1 = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "MustOverride Sub Bar1.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Overrides Sub Bar.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Overridden))); + + var itemForFooInBar = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "Overrides Sub Bar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "MustOverride Sub Bar1.Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Overriding))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + itemForBar1, + itemForBar, + itemForFooInBar1, + itemForFooInBar); + } + + [Theory] + [CombinatorialData] + public Task TestVisualBasicOverrideMemberCanFindImplementingInterface(bool testDuplicate) + { + var markup1 = @" +Interface {|target4:IBar|} + Sub {|target6:Foo|}() +End Interface + +Class {|target1:Bar1|} + Implements IBar + Public Overridable Sub {|target2:Foo|}() Implements IBar.Foo + End Sub +End Class + +Class {|target5:Bar2|} + Inherits Bar1 + Public Overrides Sub {|target3:Foo|}() + End Sub +End Class"; + + var markup2 = @" +Interface {|target4:IBar|} + Sub {|target6:Foo|}() +End Interface + +Class {|target1:Bar1|} + Implements IBar + Public Overridable Sub {|target2:Foo|}() Implements IBar.Foo + End Sub +End Class + +Class {|target5:Bar2|} + Inherits Bar1 + Public Overrides Sub {|target3:Foo|}() + End Sub +End Class"; + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar1", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented), + new TargetInfo( + targetSymbolDisplayName: "Class Bar2", + locationTag: "target5", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInIBar = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Sub IBar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Overridable Sub Bar1.Foo()", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented), + new TargetInfo( + targetSymbolDisplayName: "Overrides Sub Bar2.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar1 = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "Class Bar1", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "Class Bar2", + locationTag: "target5", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInBar1 = new TestInheritanceMemberItem( + lineNumber: 8, + memberName: "Overridable Sub Bar1.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub IBar.Foo()", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "Overrides Sub Bar2.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Overridden))); + + var itemForBar2 = new TestInheritanceMemberItem( + lineNumber: 12, + memberName: "Class Bar2", + targets: ImmutableArray.Create( + new TargetInfo( + targetSymbolDisplayName: "Class Bar1", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target4", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInBar2 = new TestInheritanceMemberItem( + lineNumber: 14, + memberName: "Overrides Sub Bar2.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub IBar.Foo()", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing), + new TargetInfo( + targetSymbolDisplayName: "Overridable Sub Bar1.Foo()", + locationTag: "target2", + relationship: InheritanceRelationship.Overriding))); + + return VerifyInSingleDocumentAsync( + testDuplicate ? markup2 : markup1, + LanguageNames.VisualBasic, + itemForIBar, + itemForFooInIBar, + itemForBar1, + itemForFooInBar1, + itemForBar2, + itemForFooInBar2); + } + + [Fact] + public Task TestVisualBasicFindGenericsBaseType() + { + var markup = @" +Public Interface {|target5:IBar|}(Of T) + Sub {|target6:Foo|}() +End Interface + +Public Class {|target1:Bar|} + Implements IBar(Of Integer) + Implements IBar(Of String) + + Public Sub {|target3:Foo|}() Implements IBar(Of Integer).Foo + Throw New NotImplementedException() + End Sub + + Private Sub {|target4:IBar_Foo|}() Implements IBar(Of String).Foo + Throw New NotImplementedException() + End Sub +End Class"; + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 2, + memberName: "Interface IBar(Of T)", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar", + locationTag: "target1", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInIBar = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Sub IBar(Of T).Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub Bar.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implemented), + new TargetInfo( + targetSymbolDisplayName: "Sub Bar.IBar_Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Implemented))); + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "Class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar(Of T)", + locationTag: "target5", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInBar = new TestInheritanceMemberItem( + lineNumber: 10, + memberName: "Sub Bar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub IBar(Of T).Foo()", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing))); + + var itemForIBar_FooInBar = new TestInheritanceMemberItem( + lineNumber: 14, + memberName: "Sub Bar.IBar_Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub IBar(Of T).Foo()", + locationTag: "target6", + relationship: InheritanceRelationship.Implementing))); + + return VerifyInSingleDocumentAsync( + markup, + LanguageNames.VisualBasic, + itemForIBar, + itemForFooInIBar, + itemForBar, + itemForFooInBar, + itemForIBar_FooInBar); + } + + #endregion + + [Fact] + public Task TestCSharpProjectReferencingVisualBasicProject() + { + var markup1 = @" +using MyNamespace; +namespace BarNs +{ + public class {|target2:Bar|} : IBar + { + public void {|target4:Foo|}() { } + } +}"; + + var markup2 = @" +Namespace MyNamespace + Public Interface {|target1:IBar|} + Sub {|target3:Foo|}() + End Interface +End Namespace"; + + var itemForBar = new TestInheritanceMemberItem( + lineNumber: 5, + memberName: "class Bar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Interface IBar", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInMarkup1 = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "void Bar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub IBar.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implementing))); + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 3, + memberName: "Interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "class Bar", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInMarkup2 = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "Sub IBar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void Bar.Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Implemented))); + + return VerifyInDifferentProjectsAsync( + (markup1, LanguageNames.CSharp), + (markup2, LanguageNames.VisualBasic), + new[] { itemForBar, itemForFooInMarkup1 }, + new[] { itemForIBar, itemForFooInMarkup2 }); + } + + [Fact] + public Task TestVisualBasicProjectReferencingCSharpProject() + { + var markup1 = @" +Imports BarNs +Namespace MyNamespace + Public Class {|target2:Bar44|} + Implements IBar + + Public Sub {|target4:Foo|}() Implements IBar.Foo + End Sub + End Class +End Namespace"; + + var markup2 = @" +namespace BarNs +{ + public interface {|target1:IBar|} + { + void {|target3:Foo|}(); + } +}"; + + var itemForBar44 = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "Class Bar44", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "interface IBar", + locationTag: "target1", + relationship: InheritanceRelationship.Implementing))); + + var itemForFooInMarkup1 = new TestInheritanceMemberItem( + lineNumber: 7, + memberName: "Sub Bar44.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "void IBar.Foo()", + locationTag: "target3", + relationship: InheritanceRelationship.Implementing))); + + var itemForIBar = new TestInheritanceMemberItem( + lineNumber: 4, + memberName: "interface IBar", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Class Bar44", + locationTag: "target2", + relationship: InheritanceRelationship.Implemented))); + + var itemForFooInMarkup2 = new TestInheritanceMemberItem( + lineNumber: 6, + memberName: "void IBar.Foo()", + targets: ImmutableArray.Create(new TargetInfo( + targetSymbolDisplayName: "Sub Bar44.Foo()", + locationTag: "target4", + relationship: InheritanceRelationship.Implemented))); + + return VerifyInDifferentProjectsAsync( + (markup1, LanguageNames.VisualBasic), + (markup2, LanguageNames.CSharp), + new[] { itemForBar44, itemForFooInMarkup1 }, + new[] { itemForIBar, itemForFooInMarkup2 }); + } + } +} diff --git a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs index 101bb0709ee20..14eb9c357849b 100644 --- a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs +++ b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs @@ -78,11 +78,11 @@ public async Task DynamicallyAddAnalyzer() Assert.Equal(10, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, 1)] - [InlineData(BackgroundAnalysisScope.FullSolution, 1)] + [InlineData(BackgroundAnalysisScope.ActiveFile)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] + [InlineData(BackgroundAnalysisScope.FullSolution)] [Theory, WorkItem(747226, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/747226")] - internal async Task SolutionAdded_Simple(BackgroundAnalysisScope analysisScope, int expectedDocumentEvents) + internal async Task SolutionAdded_Simple(BackgroundAnalysisScope analysisScope) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionId = SolutionId.CreateNewId(); @@ -98,20 +98,24 @@ internal async Task SolutionAdded_Simple(BackgroundAnalysisScope analysisScope, }) }); + var expectedDocumentEvents = 1; + var worker = await ExecuteOperation(workspace, w => w.OnSolutionAdded(solutionInfo)); Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, 10)] - [InlineData(BackgroundAnalysisScope.FullSolution, 10)] + [InlineData(BackgroundAnalysisScope.ActiveFile)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] + [InlineData(BackgroundAnalysisScope.FullSolution)] [Theory] - internal async Task SolutionAdded_Complex(BackgroundAnalysisScope analysisScope, int expectedDocumentEvents) + internal async Task SolutionAdded_Complex(BackgroundAnalysisScope analysisScope) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); + var expectedDocumentEvents = 10; + var worker = await ExecuteOperation(workspace, w => w.OnSolutionAdded(solution)); Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } @@ -146,27 +150,30 @@ internal async Task Solution_Clear(BackgroundAnalysisScope analysisScope) Assert.Equal(10, worker.InvalidateDocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, 0, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, 10, 2)] - [InlineData(BackgroundAnalysisScope.FullSolution, 10, 2)] + [InlineData(BackgroundAnalysisScope.ActiveFile)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] + [InlineData(BackgroundAnalysisScope.FullSolution)] [Theory] - internal async Task Solution_Reload(BackgroundAnalysisScope analysisScope, int expectedDocumentEvents, int expectedProjectEvents) + internal async Task Solution_Reload(BackgroundAnalysisScope analysisScope) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); workspace.OnSolutionAdded(solution); await WaitWaiterAsync(workspace.ExportProvider); + var expectedDocumentEvents = 10; + var expectedProjectEvents = 2; + var worker = await ExecuteOperation(workspace, w => w.OnSolutionReloaded(solution)); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); Assert.Equal(expectedProjectEvents, worker.ProjectIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, 1)] - [InlineData(BackgroundAnalysisScope.FullSolution, 1)] + [InlineData(BackgroundAnalysisScope.ActiveFile)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] + [InlineData(BackgroundAnalysisScope.FullSolution)] [Theory] - internal async Task Solution_Change(BackgroundAnalysisScope analysisScope, int expectedDocumentEvents) + internal async Task Solution_Change(BackgroundAnalysisScope analysisScope) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -179,15 +186,17 @@ internal async Task Solution_Change(BackgroundAnalysisScope analysisScope, int e var changedSolution = solution.AddProject("P3", "P3", LanguageNames.CSharp).AddDocument("D1", "").Project.Solution; + var expectedDocumentEvents = 1; + var worker = await ExecuteOperation(workspace, w => w.ChangeSolution(changedSolution)); Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, 2)] - [InlineData(BackgroundAnalysisScope.FullSolution, 2)] + [InlineData(BackgroundAnalysisScope.ActiveFile)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] + [InlineData(BackgroundAnalysisScope.FullSolution)] [Theory] - internal async Task Project_Add(BackgroundAnalysisScope analysisScope, int expectedDocumentEvents) + internal async Task Project_Add(BackgroundAnalysisScope analysisScope) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -203,6 +212,8 @@ internal async Task Project_Add(BackgroundAnalysisScope analysisScope, int expec DocumentInfo.Create(DocumentId.CreateNewId(projectId), "D2") }); + var expectedDocumentEvents = 2; + var worker = await ExecuteOperation(workspace, w => w.OnProjectAdded(projectInfo)); Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } @@ -245,12 +256,12 @@ internal async Task Project_Change(BackgroundAnalysisScope analysisScope) Assert.Equal(1, worker.InvalidateDocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_AssemblyName_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_AssemblyName_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -266,16 +277,18 @@ internal async Task Project_AssemblyName_Change(BackgroundAnalysisScope analysis project = project.WithAssemblyName("newName"); var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, project.Solution)); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_DefaultNamespace_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_DefaultNamespace_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -291,16 +304,18 @@ internal async Task Project_DefaultNamespace_Change(BackgroundAnalysisScope anal project = project.WithDefaultNamespace("newNamespace"); var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, project.Solution)); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_AnalyzerOptions_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_AnalyzerOptions_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -316,16 +331,18 @@ internal async Task Project_AnalyzerOptions_Change(BackgroundAnalysisScope analy project = project.AddAdditionalDocument("a1", SourceText.From("")).Project; var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, project.Solution)); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_OutputFilePath_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_OutputFilePath_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -341,16 +358,18 @@ internal async Task Project_OutputFilePath_Change(BackgroundAnalysisScope analys var newSolution = workspace.CurrentSolution.WithProjectOutputFilePath(project.Id, "/newPath"); var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, newSolution)); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_OutputRefFilePath_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_OutputRefFilePath_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -366,16 +385,18 @@ internal async Task Project_OutputRefFilePath_Change(BackgroundAnalysisScope ana var newSolution = workspace.CurrentSolution.WithProjectOutputRefFilePath(project.Id, "/newPath"); var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, newSolution)); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_CompilationOutputInfo_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_CompilationOutputInfo_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -391,16 +412,18 @@ internal async Task Project_CompilationOutputInfo_Change(BackgroundAnalysisScope var newSolution = workspace.CurrentSolution.WithProjectCompilationOutputInfo(project.Id, new CompilationOutputInfo(assemblyPath: "/newPath")); var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, newSolution)); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Project_RunAnalyzers_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentEvents) + internal async Task Project_RunAnalyzers_Change(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(); @@ -421,6 +444,8 @@ internal async Task Project_RunAnalyzers_Change(BackgroundAnalysisScope analysis project = workspace.CurrentSolution.GetProject(project.Id); Assert.False(project.State.RunAnalyzers); + var expectedDocumentEvents = 5; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } @@ -455,9 +480,9 @@ public async Task Test_BackgroundAnalysisScopeOptionChanged_ActiveFile() var worker = await ExecuteOperation(workspace, w => w.TryApplyChanges(w.CurrentSolution.WithOptions(w.CurrentSolution.Options.WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.CSharp, newAnalysisScope)))); Assert.Equal(newAnalysisScope, SolutionCrawlerOptions.GetBackgroundAnalysisScope(workspace.Options, LanguageNames.CSharp)); - Assert.Equal(1, worker.SyntaxDocumentIds.Count); - Assert.Equal(1, worker.DocumentIds.Count); - Assert.Equal(1, worker.ProjectIds.Count); + Assert.Equal(10, worker.SyntaxDocumentIds.Count); + Assert.Equal(10, worker.DocumentIds.Count); + Assert.Equal(2, worker.ProjectIds.Count); } [Fact] @@ -479,29 +504,32 @@ public async Task Test_BackgroundAnalysisScopeOptionChanged_FullSolution() Assert.Equal(2, worker.ProjectIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, 0, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, 5, 1)] - [InlineData(BackgroundAnalysisScope.FullSolution, 5, 1)] + [InlineData(BackgroundAnalysisScope.ActiveFile)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] + [InlineData(BackgroundAnalysisScope.FullSolution)] [Theory] - internal async Task Project_Reload(BackgroundAnalysisScope analysisScope, int expectedDocumentEvents, int expectedProjectEvents) + internal async Task Project_Reload(BackgroundAnalysisScope analysisScope) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); workspace.OnSolutionAdded(solution); await WaitWaiterAsync(workspace.ExportProvider); + var expectedDocumentEvents = 5; + var expectedProjectEvents = 1; + var project = solution.Projects[0]; var worker = await ExecuteOperation(workspace, w => w.OnProjectReloaded(project)); Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); Assert.Equal(expectedProjectEvents, worker.ProjectIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 1, 6)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 1, 6)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Document_Add(BackgroundAnalysisScope analysisScope, bool activeDocument, int expectedDocumentSyntaxEvents, int expectedDocumentSemanticEvents) + internal async Task Document_Add(BackgroundAnalysisScope analysisScope, bool activeDocument) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -522,16 +550,19 @@ internal async Task Document_Add(BackgroundAnalysisScope analysisScope, bool act } }); + var expectedDocumentSyntaxEvents = 1; + var expectedDocumentSemanticEvents = 6; + Assert.Equal(expectedDocumentSyntaxEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 1, 0, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1, 0, 0)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 1, 0, 4)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 1, 0, 4)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Document_Remove(BackgroundAnalysisScope analysisScope, bool removeActiveDocument, int expectedDocumentInvalidatedEvents, int expectedDocumentSyntaxEvents, int expectedDocumentSemanticEvents) + internal async Task Document_Remove(BackgroundAnalysisScope analysisScope, bool removeActiveDocument) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -546,6 +577,10 @@ internal async Task Document_Remove(BackgroundAnalysisScope analysisScope, bool var worker = await ExecuteOperation(workspace, w => w.OnDocumentRemoved(document.Id)); + var expectedDocumentInvalidatedEvents = 1; + var expectedDocumentSyntaxEvents = 0; + var expectedDocumentSemanticEvents = 4; + Assert.Equal(expectedDocumentSyntaxEvents, worker.SyntaxDocumentIds.Count); Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); Assert.Equal(expectedDocumentInvalidatedEvents, worker.InvalidateDocumentIds.Count); @@ -576,12 +611,12 @@ internal async Task Document_Reload(BackgroundAnalysisScope analysisScope, bool Assert.Equal(0, worker.InvalidateDocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 1)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 1)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Document_Reanalyze(BackgroundAnalysisScope analysisScope, bool reanalyzeActiveDocument, int expectedReanalyzeCount) + internal async Task Document_Reanalyze(BackgroundAnalysisScope analysisScope, bool reanalyzeActiveDocument) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -615,16 +650,18 @@ internal async Task Document_Reanalyze(BackgroundAnalysisScope analysisScope, bo service.Unregister(workspace); - Assert.Equal(expectedReanalyzeCount, worker.SyntaxDocumentIds.Count); - Assert.Equal(expectedReanalyzeCount, worker.DocumentIds.Count); + var expectedReanalyzeDocumentCount = 1; + + Assert.Equal(expectedReanalyzeDocumentCount, worker.SyntaxDocumentIds.Count); + Assert.Equal(expectedReanalyzeDocumentCount, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 1)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 1)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory, WorkItem(670335, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/670335")] - internal async Task Document_Change(BackgroundAnalysisScope analysisScope, bool changeActiveDocument, int expectedDocumentEvents) + internal async Task Document_Change(BackgroundAnalysisScope analysisScope, bool changeActiveDocument) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -639,15 +676,17 @@ internal async Task Document_Change(BackgroundAnalysisScope analysisScope, bool var worker = await ExecuteOperation(workspace, w => w.ChangeDocument(document.Id, SourceText.From("//"))); + var expectedDocumentEvents = 1; + Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Document_AdditionalFileChange(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentSyntaxEvents, int expectedDocumentSemanticEvents) + internal async Task Document_AdditionalFileChange(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -660,6 +699,9 @@ internal async Task Document_AdditionalFileChange(BackgroundAnalysisScope analys await WaitWaiterAsync(workspace.ExportProvider); + var expectedDocumentSyntaxEvents = 5; + var expectedDocumentSemanticEvents = 5; + var ncfile = DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "D6"); var worker = await ExecuteOperation(workspace, w => w.OnAdditionalDocumentAdded(ncfile)); @@ -677,12 +719,12 @@ internal async Task Document_AdditionalFileChange(BackgroundAnalysisScope analys Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 5, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 5, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory] - internal async Task Document_AnalyzerConfigFileChange(BackgroundAnalysisScope analysisScope, bool firstDocumentActive, int expectedDocumentSyntaxEvents, int expectedDocumentSemanticEvents) + internal async Task Document_AnalyzerConfigFileChange(BackgroundAnalysisScope analysisScope, bool firstDocumentActive) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -695,6 +737,9 @@ internal async Task Document_AnalyzerConfigFileChange(BackgroundAnalysisScope an await WaitWaiterAsync(workspace.ExportProvider); + var expectedDocumentSyntaxEvents = 5; + var expectedDocumentSemanticEvents = 5; + var analyzerConfigDocFilePath = PathUtilities.CombineAbsoluteAndRelativePaths(Temp.CreateDirectory().Path, ".editorconfig"); var analyzerConfigFile = DocumentInfo.Create(DocumentId.CreateNewId(project.Id), ".editorconfig", filePath: analyzerConfigDocFilePath); @@ -713,12 +758,12 @@ internal async Task Document_AnalyzerConfigFileChange(BackgroundAnalysisScope an Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 1, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 1, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory, WorkItem(670335, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/670335")] - internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, bool activeDocument, int expectedDocumentSyntaxEvents, int expectedDocumentSemanticEvents) + internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, bool activeDocument) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -740,6 +785,9 @@ internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, service.Register(workspace); + var expectedDocumentSyntaxEvents = 1; + var expectedDocumentSemanticEvents = 5; + workspace.ChangeDocument(document.Id, SourceText.From("//")); if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0) { @@ -755,12 +803,12 @@ internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, Assert.Equal(expectedDocumentSemanticEvents, analyzer.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 0, 0)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 1, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 1, 5)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 1, 5)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory, WorkItem(670335, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/670335")] - internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope analysisScope, bool activeDocument, int expectedDocumentSyntaxEvents, int expectedDocumentSemanticEvents) + internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope analysisScope, bool activeDocument) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -773,6 +821,9 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope await WaitWaiterAsync(workspace.ExportProvider); + var expectedDocumentSyntaxEvents = 1; + var expectedDocumentSemanticEvents = 5; + var lazyWorker = Assert.Single(workspace.ExportProvider.GetExports()); Assert.Equal(Metadata.Crawler, lazyWorker.Metadata); var analyzer = Assert.IsType(Assert.IsAssignableFrom(lazyWorker.Value).Analyzer); @@ -850,12 +901,12 @@ public async Task Document_InvocationReasons() Assert.Equal(5, analyzer.DocumentIds.Count); } - [InlineData(BackgroundAnalysisScope.ActiveFile, false, 1, 1)] - [InlineData(BackgroundAnalysisScope.ActiveFile, true, 2, 1)] - [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false, 0, 0)] - [InlineData(BackgroundAnalysisScope.FullSolution, false, 0, 0)] + [InlineData(BackgroundAnalysisScope.ActiveFile, false)] + [InlineData(BackgroundAnalysisScope.ActiveFile, true)] + [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] + [InlineData(BackgroundAnalysisScope.FullSolution, false)] [Theory, WorkItem(670335, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/670335")] - internal async Task Document_ActiveDocumentChanged(BackgroundAnalysisScope analysisScope, bool hasActiveDocumentBefore, int expectedSourceSwitchDocumentEvents, int expectedNonSourceSwitchDocumentEvents) + internal async Task Document_ActiveDocumentChanged(BackgroundAnalysisScope analysisScope, bool hasActiveDocumentBefore) { using var workspace = WorkCoordinatorWorkspace.CreateWithAnalysisScope(analysisScope, SolutionCrawlerWorkspaceKind, incrementalAnalyzer: typeof(AnalyzerProviderNoWaitNoBlock)); var solution = GetInitialSolutionInfo_2Projects_10Documents(); @@ -871,10 +922,24 @@ internal async Task Document_ActiveDocumentChanged(BackgroundAnalysisScope analy await WaitWaiterAsync(workspace.ExportProvider); + var expectedSyntaxDocumentEvents = (analysisScope, hasActiveDocumentBefore) switch + { + (BackgroundAnalysisScope.ActiveFile, _) => 1, + (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution, _) => 0, + _ => throw ExceptionUtilities.Unreachable, + }; + + var expectedDocumentEvents = (analysisScope, hasActiveDocumentBefore) switch + { + (BackgroundAnalysisScope.ActiveFile, _) => 5, + (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution, _) => 0, + _ => throw ExceptionUtilities.Unreachable, + }; + // Switch to another active source document and verify expected document analysis callbacks var worker = await ExecuteOperation(workspace, w => MakeDocumentActive(secondDocument)); - Assert.Equal(expectedSourceSwitchDocumentEvents, worker.SyntaxDocumentIds.Count); - Assert.Equal(expectedSourceSwitchDocumentEvents, worker.DocumentIds.Count); + Assert.Equal(expectedSyntaxDocumentEvents, worker.SyntaxDocumentIds.Count); + Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); Assert.Equal(0, worker.InvalidateDocumentIds.Count); // Switch from an active source document to an active non-source document and verify no document analysis callbacks @@ -885,8 +950,8 @@ internal async Task Document_ActiveDocumentChanged(BackgroundAnalysisScope analy // Switch from an active non-source document to an active source document and verify document analysis callbacks worker = await ExecuteOperation(workspace, w => MakeDocumentActive(firstDocument)); - Assert.Equal(expectedNonSourceSwitchDocumentEvents, worker.SyntaxDocumentIds.Count); - Assert.Equal(expectedNonSourceSwitchDocumentEvents, worker.DocumentIds.Count); + Assert.Equal(expectedSyntaxDocumentEvents, worker.SyntaxDocumentIds.Count); + Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); Assert.Equal(0, worker.InvalidateDocumentIds.Count); } diff --git a/src/EditorFeatures/Test/SymbolFinder/FindSymbolAtPositionTests.cs b/src/EditorFeatures/Test/SymbolFinder/FindSymbolAtPositionTests.cs new file mode 100644 index 0000000000000..b7420be655ed2 --- /dev/null +++ b/src/EditorFeatures/Test/SymbolFinder/FindSymbolAtPositionTests.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Extensions +{ + [UseExportProvider] + public class FindSymbolAtPositionTests + { + private static Task FindSymbolAtPositionAsync(TestWorkspace workspace) + { + var position = workspace.Documents.Single(d => d.CursorPosition.HasValue).CursorPosition!.Value; + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.Single().Id); + return SymbolFinder.FindSymbolAtPositionAsync(document, position); + } + + [Fact] + public async Task PositionOnLeadingTrivia() + { + using var workspace = TestWorkspace.CreateCSharp( + @"using System; + class Program + { + static void Main() + { + $$#pragma warning disable 612 + Goo(); + #pragma warning restore 612 + } + }"); + var symbol = await FindSymbolAtPositionAsync(workspace); + Assert.Null(symbol); + } + + [Fact] + [WorkItem(53269, "https://github.com/dotnet/roslyn/issues/53269")] + public async Task PositionInCaseLabel() + { + using var workspace = TestWorkspace.CreateCSharp( + @"using System; + enum E { A, B } + class Program + { + static void Main() + { + E e = default; + switch (e) + { + case E.$$A: break; + } + } + }"); + + var fieldSymbol = Assert.IsAssignableFrom(await FindSymbolAtPositionAsync(workspace)); + Assert.Equal(TypeKind.Enum, fieldSymbol.ContainingType.TypeKind); + } + } +} diff --git a/src/EditorFeatures/Test/SymbolKey/SymbolKeyCrossLanguageTests.cs b/src/EditorFeatures/Test/SymbolKey/SymbolKeyCrossLanguageTests.cs new file mode 100644 index 0000000000000..53866aa99f613 --- /dev/null +++ b/src/EditorFeatures/Test/SymbolKey/SymbolKeyCrossLanguageTests.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.SymbolKeyTests +{ + [UseExportProvider] + public class SymbolKeyCrossLanguageTests + { + [Theory] + [InlineData("dynamic")] + [InlineData("int*")] + [InlineData("delegate*<int, void>")] + public async Task TestUnsupportedVBTypes(string parameterType) + { + using var workspace = TestWorkspace.Create( +@$" + + +public class C +{{ + public void M({parameterType} d) {{ }} +}} + + + + CSProject + + +"); + + var solution = workspace.CurrentSolution; + var csDocument = solution.Projects.Single(p => p.Language == LanguageNames.CSharp).Documents.Single(); + var semanticModel = await csDocument.GetRequiredSemanticModelAsync(CancellationToken.None); + var tree = semanticModel.SyntaxTree; + var root = tree.GetRoot(); + + var method = root.DescendantNodes().OfType().Single(); + var methodSymbol = semanticModel.GetDeclaredSymbol(method); + + var vbProject = solution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + var vbCompilation = await vbProject.GetRequiredCompilationAsync(CancellationToken.None); + + var resolved = SymbolKey.ResolveString(methodSymbol.GetSymbolKey().ToString(), vbCompilation, out var failureReason, CancellationToken.None); + Assert.NotNull(failureReason); + Assert.Null(resolved.GetAnySymbol()); + } + } +} diff --git a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs index 9b65caaa49f8c..95577776c4635 100644 --- a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs +++ b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.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.Collections.Generic; using System.Linq; @@ -64,15 +62,12 @@ static List> tagProducer(SnapshotSpan span, CancellationToken WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(LargeNumberOfSpans)} creates asynchronous taggers"); - var notificationService = workspace.GetService(); - var eventSource = CreateEventSource(); var taggerProvider = new TestTaggerProvider( workspace.ExportProvider.GetExportedValue(), tagProducer, eventSource, - asyncListener, - notificationService); + asyncListener); var document = workspace.Documents.First(); var textBuffer = document.GetTextBuffer(); @@ -93,9 +88,34 @@ static List> tagProducer(SnapshotSpan span, CancellationToken } [WpfFact] - public void TestSynchronousOutlining() + public void TestNotSynchronousOutlining() { using var workspace = TestWorkspace.CreateCSharp("class Program {\r\n\r\n}", composition: EditorTestCompositions.EditorFeaturesWpf); + WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestNotSynchronousOutlining)} creates asynchronous taggers"); + + var tagProvider = workspace.ExportProvider.GetExportedValue(); + + var document = workspace.Documents.First(); + var textBuffer = document.GetTextBuffer(); + var tagger = tagProvider.CreateTagger(textBuffer); + + using var disposable = (IDisposable)tagger; + // The very first all to get tags will not be synchronous as this contains no #region tag + var tags = tagger.GetTags(new NormalizedSnapshotSpanCollection(textBuffer.CurrentSnapshot.GetFullSpan())); + Assert.Equal(0, tags.Count()); + } + + [WpfFact] + public void TestSynchronousOutlining() + { + using var workspace = TestWorkspace.CreateCSharp(@" +#region x + +class Program +{ +} + +#endregion", composition: EditorTestCompositions.EditorFeaturesWpf); WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestSynchronousOutlining)} creates asynchronous taggers"); var tagProvider = workspace.ExportProvider.GetExportedValue(); @@ -105,9 +125,9 @@ public void TestSynchronousOutlining() var tagger = tagProvider.CreateTagger(textBuffer); using var disposable = (IDisposable)tagger; - // The very first all to get tags should return the single outlining span. - var tags = tagger.GetAllTags(new NormalizedSnapshotSpanCollection(textBuffer.CurrentSnapshot.GetFullSpan()), CancellationToken.None); - Assert.Equal(1, tags.Count()); + // The very first all to get tags will be synchronous because of the #region + var tags = tagger.GetTags(new NormalizedSnapshotSpanCollection(textBuffer.CurrentSnapshot.GetFullSpan())); + Assert.Equal(2, tags.Count()); } private static TestTaggerEventSource CreateEventSource() @@ -132,14 +152,15 @@ public TestTaggerProvider( IThreadingContext threadingContext, Callback callback, ITaggerEventSource eventSource, - IAsynchronousOperationListener asyncListener, - IForegroundNotificationService notificationService) - : base(threadingContext, asyncListener, notificationService) + IAsynchronousOperationListener asyncListener) + : base(threadingContext, asyncListener) { _callback = callback; _eventSource = eventSource; } + protected override TaggerDelay EventChangeDelay => TaggerDelay.NearImmediate; + protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) => _eventSource; @@ -161,7 +182,6 @@ protected override Task ProduceTagsAsync(TaggerContext context, Documen private sealed class TestTaggerEventSource : AbstractTaggerEventSource { public TestTaggerEventSource() - : base(delay: TaggerDelay.NearImmediate) { } diff --git a/src/EditorFeatures/Test/Threading/AsynchronousWorkerTests.cs b/src/EditorFeatures/Test/Threading/AsynchronousWorkerTests.cs deleted file mode 100644 index 2e1e11f64b592..0000000000000 --- a/src/EditorFeatures/Test/Threading/AsynchronousWorkerTests.cs +++ /dev/null @@ -1,263 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.Composition; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Threading -{ - [UseExportProvider] - public class AsynchronousWorkerTests - { - private readonly SynchronizationContext _foregroundSyncContext; - - public AsynchronousWorkerTests() - { - WpfTestRunner.RequireWpfFact($"Tests are testing {nameof(AsynchronousSerialWorkQueue)} which is designed to run methods on the UI thread"); - _foregroundSyncContext = SynchronizationContext.Current; - Assert.NotNull(_foregroundSyncContext); - } - - // Ensure a background action actually runs on the background. - [WpfFact] - public void TestBackgroundAction() - { - var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); - var threadingContext = exportProvider.GetExportedValue(); - var listenerProvider = exportProvider.GetExportedValue(); - - var worker = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test")); - var doneEvent = new AutoResetEvent(initialState: false); - - var actionRan = false; - worker.EnqueueBackgroundWork(() => - { - // Assert.NotNull(SynchronizationContext.Current); - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - actionRan = true; - doneEvent.Set(); - }, GetType().Name + ".TestBackgroundAction", CancellationToken.None); - - doneEvent.WaitOne(); - Assert.True(actionRan); - } - - [WpfFact] - public void TestMultipleBackgroundAction() - { - var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); - var threadingContext = exportProvider.GetExportedValue(); - var listenerProvider = exportProvider.GetExportedValue(); - - // Test that background actions don't run at the same time. - var worker = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test")); - var doneEvent = new AutoResetEvent(false); - - var action1Ran = false; - var action2Ran = false; - - worker.EnqueueBackgroundWork(() => - { - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - action1Ran = true; - - // Simulate work to ensure that if tasks overlap that we will - // see it. - Thread.Sleep(1000); - Assert.False(action2Ran); - }, "Test", CancellationToken.None); - - worker.EnqueueBackgroundWork(() => - { - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - action2Ran = true; - doneEvent.Set(); - }, "Test", CancellationToken.None); - - doneEvent.WaitOne(); - Assert.True(action1Ran); - Assert.True(action2Ran); - } - - [WpfFact] - public void TestBackgroundCancel1() - { - var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); - var threadingContext = exportProvider.GetExportedValue(); - var listenerProvider = exportProvider.GetExportedValue(); - - // Ensure that we can cancel a background action. - var worker = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test")); - - var taskRunningEvent = new AutoResetEvent(false); - var cancelEvent = new AutoResetEvent(false); - var doneEvent = new AutoResetEvent(false); - - var source = new CancellationTokenSource(); - var cancellationToken = source.Token; - - var actionRan = false; - - worker.EnqueueBackgroundWork(() => - { - actionRan = true; - - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - Assert.False(cancellationToken.IsCancellationRequested); - - taskRunningEvent.Set(); - cancelEvent.WaitOne(); - - Assert.True(cancellationToken.IsCancellationRequested); - - doneEvent.Set(); - }, "Test", source.Token); - - taskRunningEvent.WaitOne(); - - source.Cancel(); - cancelEvent.Set(); - - doneEvent.WaitOne(); - Assert.True(actionRan); - } - - [WpfFact] - public void TestBackgroundCancelOneAction() - { - var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); - var threadingContext = exportProvider.GetExportedValue(); - var listenerProvider = exportProvider.GetExportedValue(); - - // Ensure that when a background action is cancelled the next - // one starts (if it has a different cancellation token). - var worker = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test")); - - var taskRunningEvent = new AutoResetEvent(false); - var cancelEvent = new AutoResetEvent(false); - var doneEvent = new AutoResetEvent(false); - - var source1 = new CancellationTokenSource(); - var source2 = new CancellationTokenSource(); - var token1 = source1.Token; - var token2 = source2.Token; - - var action1Ran = false; - var action2Ran = false; - - worker.EnqueueBackgroundWork(() => - { - action1Ran = true; - - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - Assert.False(token1.IsCancellationRequested); - - taskRunningEvent.Set(); - cancelEvent.WaitOne(); - - token1.ThrowIfCancellationRequested(); - Assert.True(false); - }, "Test", source1.Token); - - worker.EnqueueBackgroundWork(() => - { - action2Ran = true; - - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - Assert.False(token2.IsCancellationRequested); - - taskRunningEvent.Set(); - cancelEvent.WaitOne(); - - doneEvent.Set(); - }, "Test", source2.Token); - - // Wait for the first task to start. - taskRunningEvent.WaitOne(); - - // Cancel it - source1.Cancel(); - cancelEvent.Set(); - - // Wait for the second task to start. - taskRunningEvent.WaitOne(); - cancelEvent.Set(); - - // Wait for the second task to complete. - doneEvent.WaitOne(); - Assert.True(action1Ran); - Assert.True(action2Ran); - } - - [WpfFact] - public void TestBackgroundCancelMultipleActions() - { - var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); - var threadingContext = exportProvider.GetExportedValue(); - var listenerProvider = exportProvider.GetExportedValue(); - - // Ensure that multiple background actions are cancelled if they - // use the same cancellation token. - var worker = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test")); - - var taskRunningEvent = new AutoResetEvent(false); - var cancelEvent = new AutoResetEvent(false); - var doneEvent = new AutoResetEvent(false); - - var source = new CancellationTokenSource(); - var cancellationToken = source.Token; - - var action1Ran = false; - var action2Ran = false; - - worker.EnqueueBackgroundWork(() => - { - action1Ran = true; - - Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current); - Assert.False(cancellationToken.IsCancellationRequested); - - taskRunningEvent.Set(); - cancelEvent.WaitOne(); - - cancellationToken.ThrowIfCancellationRequested(); - Assert.True(false); - }, "Test", source.Token); - - // We should not run this action. - worker.EnqueueBackgroundWork(() => - { - action2Ran = true; - Assert.False(true); - }, "Test", source.Token); - - taskRunningEvent.WaitOne(); - - source.Cancel(); - cancelEvent.Set(); - - try - { - worker.GetTestAccessor().WaitUntilCompletion(); - Assert.True(false); - } - catch (AggregateException ae) - { - Assert.IsAssignableFrom(ae.InnerException); - } - - Assert.True(action1Ran); - Assert.False(action2Ran); - } - } -} diff --git a/src/EditorFeatures/Test/Threading/ForegroundNotificationServiceTests.cs b/src/EditorFeatures/Test/Threading/ForegroundNotificationServiceTests.cs deleted file mode 100644 index 2010dce766824..0000000000000 --- a/src/EditorFeatures/Test/Threading/ForegroundNotificationServiceTests.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Implementation.ForegroundNotification; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Threading -{ - [UseExportProvider] - public class ForegroundNotificationServiceTests - { - private ForegroundNotificationService _service; - private bool _done; - - private ForegroundNotificationService Service - { - get - { - if (_service is null) - { - var threadingContext = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider().GetExportedValue(); - _service = new ForegroundNotificationService(threadingContext); - } - - return _service; - } - } - - [WpfFact] - public async Task Test_Enqueue() - { - var asyncToken = EmptyAsyncToken.Instance; - var ran = false; - - Service.RegisterNotification(() => { Thread.Sleep(100); }, asyncToken, CancellationToken.None); - Service.RegisterNotification(() => { /* do nothing */ }, asyncToken, CancellationToken.None); - Service.RegisterNotification(() => - { - ran = true; - _done = true; - }, asyncToken, CancellationToken.None); - - await PumpWait(); - - Assert.True(_done); - Assert.True(ran); - Assert.True(Service.IsEmpty_TestOnly); - } - - [WpfFact] - public async Task Test_Cancellation() - { - using var waitEvent = new AutoResetEvent(initialState: false); - var asyncToken = EmptyAsyncToken.Instance; - var ran = false; - - var source = new CancellationTokenSource(); - source.Cancel(); - - Service.RegisterNotification(() => { waitEvent.WaitOne(); }, asyncToken, CancellationToken.None); - Service.RegisterNotification(() => { ran = true; }, asyncToken, source.Token); - Service.RegisterNotification(() => { _done = true; }, asyncToken, CancellationToken.None); - - waitEvent.Set(); - await PumpWait(); - - Assert.False(ran); - Assert.True(Service.IsEmpty_TestOnly); - } - - [WpfFact] - public async Task Test_Delay() - { - // NOTE: Don't be tempted to use DateTime or Stopwatch to measure this - // Switched to Environment.TickCount use the same clock as the notification - // service, see: https://github.com/dotnet/roslyn/issues/7512. - - var asyncToken = EmptyAsyncToken.Instance; - - var startMilliseconds = Environment.TickCount; - int? elapsedMilliseconds = null; - - Service.RegisterNotification(() => - { - elapsedMilliseconds = Environment.TickCount - startMilliseconds; - - _done = true; - }, 50, asyncToken, CancellationToken.None); - - await PumpWait(); - - Assert.True(elapsedMilliseconds >= 50, $"Notification fired after {elapsedMilliseconds}, instead of 50."); - Assert.True(Service.IsEmpty_TestOnly); - } - - [WpfFact] - public async Task Test_HeavyMultipleCall() - { - var asyncToken = EmptyAsyncToken.Instance; - var count = 0; - - var loopCount = 100000; - - for (var i = 0; i < loopCount; i++) - { - var index = i; - var retry = false; - - Service.RegisterNotification(() => - { - if (retry) - { - return false; - } - - var source = new CancellationTokenSource(); - - Service.RegisterNotification(() => - { - for (var j = 0; j < 100; j++) - { - count++; - } - }, asyncToken, source.Token); - - if ((index % 10) == 0) - { - source.Cancel(); - - retry = true; - return retry; - } - - if (index == loopCount - 1) - { - Service.RegisterNotification(() => { _done = true; }, asyncToken, CancellationToken.None); - } - - return false; - }, asyncToken, CancellationToken.None); - } - - await PumpWait().ConfigureAwait(false); - Assert.True(_done); - Assert.Equal(9000000, count); - Assert.True(Service.IsEmpty_TestOnly); - } - - private async Task PumpWait() - { - while (!_done) - { - await Task.Delay(TimeSpan.FromMilliseconds(1)); - } - } - } -} diff --git a/src/EditorFeatures/Test/UnusedReferences/UnusedReferencesServiceTests.cs b/src/EditorFeatures/Test/UnusedReferences/UnusedReferencesRemoverTests.cs similarity index 100% rename from src/EditorFeatures/Test/UnusedReferences/UnusedReferencesServiceTests.cs rename to src/EditorFeatures/Test/UnusedReferences/UnusedReferencesRemoverTests.cs diff --git a/src/EditorFeatures/Test/Utilities/PatternMatcherTests.cs b/src/EditorFeatures/Test/Utilities/PatternMatcherTests.cs index 8e4290c5f105e..1be8e078fc092 100644 --- a/src/EditorFeatures/Test/Utilities/PatternMatcherTests.cs +++ b/src/EditorFeatures/Test/Utilities/PatternMatcherTests.cs @@ -10,8 +10,10 @@ using System.Globalization; using System.Linq; using System.Threading; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -421,23 +423,28 @@ public void TryMatchSingleWordPattern_CultureAwareSingleWordPreferCaseSensitiveE } } - private static ImmutableArray PartListToSubstrings(string identifier, ArrayBuilder parts) + private static ImmutableArray PartListToSubstrings(string identifier, in TemporaryArray parts) { - using var resultDisposer = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; foreach (var span in parts) - { result.Add(identifier.Substring(span.Start, span.Length)); - } - parts.Free(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static ImmutableArray BreakIntoCharacterParts(string identifier) - => PartListToSubstrings(identifier, StringBreaker.GetCharacterParts(identifier)); + { + using var parts = TemporaryArray.Empty; + StringBreaker.AddCharacterParts(identifier, ref parts.AsRef()); + return PartListToSubstrings(identifier, parts); + } private static ImmutableArray BreakIntoWordParts(string identifier) - => PartListToSubstrings(identifier, StringBreaker.GetWordParts(identifier)); + { + using var parts = TemporaryArray.Empty; + StringBreaker.AddWordParts(identifier, ref parts.AsRef()); + return PartListToSubstrings(identifier, parts); + } private static PatternMatch? TestNonFuzzyMatchCore(string candidate, string pattern) { @@ -462,8 +469,8 @@ private static IEnumerable TryMatchMultiWordPattern(string candida { MarkupTestFile.GetSpans(candidate, out candidate, out ImmutableArray expectedSpans); - using var matchesDisposer = ArrayBuilder.GetInstance(out var matches); - PatternMatcher.CreatePatternMatcher(pattern, includeMatchedSpans: true).AddMatches(candidate, matches); + using var matches = TemporaryArray.Empty; + PatternMatcher.CreatePatternMatcher(pattern, includeMatchedSpans: true).AddMatches(candidate, ref matches.AsRef()); if (matches.Count == 0) { @@ -472,9 +479,13 @@ private static IEnumerable TryMatchMultiWordPattern(string candida } else { - var actualSpans = matches.SelectMany(m => m.MatchedSpans).OrderBy(s => s.Start).ToList(); + var flattened = new List(); + foreach (var match in matches) + flattened.AddRange(match.MatchedSpans); + + var actualSpans = flattened.OrderBy(s => s.Start).ToList(); Assert.Equal(expectedSpans, actualSpans); - return matches.ToImmutable(); + return matches.ToImmutableAndClear(); } } } diff --git a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb index b76a33c1af59a..1dcd2d8899acd 100644 --- a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb +++ b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.Editor.Shared.Extensions Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.UnitTests @@ -42,7 +43,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Dim provider = New SemanticClassificationViewTaggerProvider( workspace.ExportProvider.GetExportedValue(Of IThreadingContext), - workspace.GetService(Of IForegroundNotificationService), workspace.GetService(Of ClassificationTypeMap), listenerProvider) @@ -106,13 +106,12 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Dim text = Await wrongDocument.GetTextAsync(CancellationToken.None) ' make sure we don't crash with wrong document - Dim result = New List(Of ClassifiedSpan)() + Dim result = New ArrayBuilder(Of ClassifiedSpan)() Await classificationService.AddSyntacticClassificationsAsync(wrongDocument, New TextSpan(0, text.Length), result, CancellationToken.None) Await classificationService.AddSemanticClassificationsAsync(wrongDocument, New TextSpan(0, text.Length), result, CancellationToken.None) End Using End Function -#Disable Warning BC40000 ' Type or member is obsolete Private Class NoCompilationEditorClassificationService Implements IClassificationService @@ -122,19 +121,30 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Public Sub New() End Sub - Public Sub AddLexicalClassifications(text As SourceText, textSpan As TextSpan, result As List(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements IClassificationService.AddLexicalClassifications + Public Sub AddLexicalClassifications(text As SourceText, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements IClassificationService.AddLexicalClassifications End Sub - Public Function AddSemanticClassificationsAsync(document As Document, textSpan As TextSpan, result As List(Of ClassifiedSpan), cancellationToken As CancellationToken) As Task Implements IClassificationService.AddSemanticClassificationsAsync + Public Sub AddSyntacticClassifications(workspace As Workspace, root As SyntaxNode, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Implements IClassificationService.AddSyntacticClassifications + End Sub + + Public Function AddSemanticClassificationsAsync(document As Document, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) As Task Implements IClassificationService.AddSemanticClassificationsAsync Return Task.CompletedTask End Function - Public Function AddSyntacticClassificationsAsync(document As Document, textSpan As TextSpan, result As List(Of ClassifiedSpan), cancellationToken As CancellationToken) As Task Implements IClassificationService.AddSyntacticClassificationsAsync + Public Function AddSyntacticClassificationsAsync(document As Document, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) As Task Implements IClassificationService.AddSyntacticClassificationsAsync Return Task.CompletedTask End Function Public Function AdjustStaleClassification(text As SourceText, classifiedSpan As ClassifiedSpan) As ClassifiedSpan Implements IClassificationService.AdjustStaleClassification End Function + + Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, timeout As TimeSpan, cancellationToken As CancellationToken) As ValueTask(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync + Return New ValueTask(Of TextChangeRange?) + End Function + + Public Function ComputeSyntacticChangeRange(workspace As Workspace, oldRoot As SyntaxNode, newRoot As SyntaxNode, timeout As TimeSpan, cancellationToken As CancellationToken) As TextChangeRange? Implements IClassificationService.ComputeSyntacticChangeRange + Return Nothing + End Function End Class End Class End Namespace diff --git a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb new file mode 100644 index 0000000000000..bbf46affd64f6 --- /dev/null +++ b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb @@ -0,0 +1,385 @@ +' 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 Microsoft.CodeAnalysis.Classification +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Text + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification + + Public Class SyntacticChangeRangeComputerTests + Private Shared Function TestCSharp(markup As String, newText As String) As Task + Return Test(markup, newText, LanguageNames.CSharp) + End Function + + Private Shared Async Function Test(markup As String, newText As String, language As String) As Task + Using workspace = TestWorkspace.Create(language, compilationOptions:=Nothing, parseOptions:=Nothing, markup) + Dim testDocument = workspace.Documents(0) + Dim startingDocument = workspace.CurrentSolution.GetDocument(testDocument.Id) + + Dim spans = testDocument.SelectedSpans + Assert.True(1 = spans.Count, "Test should have one spans in it representing the span to replace") + + Dim annotatedSpans = testDocument.AnnotatedSpans + Assert.True(1 = annotatedSpans.Count, "Test should have a single {||} span representing the change span in the final document") + Dim annotatedSpan = annotatedSpans.Single().Value.Single() + + Dim startingText = Await startingDocument.GetTextAsync() + Dim startingTree = Await startingDocument.GetSyntaxTreeAsync() + Dim startingRoot = Await startingTree.GetRootAsync() + + Dim endingText = startingText.Replace(spans(0), newText) + Dim endingTree = startingTree.WithChangedText(endingText) + Dim endingRoot = Await endingTree.GetRootAsync() + + Dim actualChange = SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(startingRoot, endingRoot, TimeSpan.MaxValue, Nothing) + Dim expectedChange = New TextChangeRange( + annotatedSpan, + annotatedSpan.Length + newText.Length - spans(0).Length) + Assert.True(expectedChange = actualChange, expectedChange.ToString() & " != " & actualChange.ToString() & vbCrLf & "Changed span was" & vbCrLf & startingText.ToString(actualChange.Span)) + End Using + End Function + + + Public Async Function TestIdentifierChangeInMethod1() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: Con[||]|}.WriteLine(0); + } + + void M3() + { + } +} +", "sole") + End Function + + + Public Async Function TestIdentifierChangeInMethod2() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: Con[|sole|]|}.WriteLine(0); + } + + void M3() + { + } +} +", "") + End Function + + + Public Async Function TestSplitClass1() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } +{|changed: +[||] + + void |}M2() + { + Console.WriteLine(0); + } + + void M3() + { + } +} +", "} class C2 {") + End Function + + + Public Async Function TestMergeClass() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } +{|changed: + +[|} class C2 {|] + + void |}M2() + { + Console.WriteLine(0); + } + + void M3() + { + } +} +", "") + End Function + + + Public Async Function TestExtendComment() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [||] + } + + void M3() + { + Console.WriteLine(""*/ Console.WriteLine("") +|} } + + void M4() + { + } +} +", "/*") + End Function + + + Public Async Function TestRemoveComment() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [|/*|] + } + + void M3() + { + Console.WriteLine(""*/ Console.WriteLine("") +|} } + + void M4() + { + } +} +", "") + End Function + + + Public Async Function TestExtendCommentToEndOfFile() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [||] + } + + void M3() + { + } + + void M4() + { + } +} +|}", "/*") + End Function + + + Public Async Function TestDeleteFullFile() As Task + Await TestCSharp( +"{|changed:[| +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + } + + void M3() + { + } + + void M4() + { + } +} +|]|}", "") + End Function + + + Public Async Function InsertFullFile() As Task + Await TestCSharp( +"{|changed:[||]|}", " +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + } + + void M3() + { + } + + void M4() + { + } +} +") + End Function + + + Public Async Function TestInsertDuplicateLineBelow() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + throw new NotImplementedException();[||] +{|changed:|} } + + void M3() + { + } +} +", " + throw new NotImplementedException();") + End Function + + + Public Async Function TestInsertDuplicateLineAbove() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + {[||] + throw new NotImplementedException(); +{|changed:|} } + + void M3() + { + } +} +", " + throw new NotImplementedException();") + End Function + + + Public Async Function TestDeleteDuplicateLineBelow() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + throw new NotImplementedException(); +{|changed: [|throw new NotImplementedException();|] + } +|} + void M3() + { + } +} +", "") + End Function + + + Public Async Function TestDeleteDuplicateLineAbove() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [|throw new NotImplementedException();|] + throw |}new NotImplementedException(); + } + + void M3() + { + } +} +", "") + End Function + End Class +End Namespace diff --git a/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb index 0032dd260d0f8..6fbeff7ac1a68 100644 --- a/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb @@ -741,6 +741,7 @@ End Module]]> Await TestAsync(input, expected, onAfterWorkspaceCreated:=Sub(w) w.SetTestLogger(AddressOf _outputHelper.WriteLine)) End Function + Public Async Function GenerateMethodUsingTypeConstraint_3BaseTypeConstraints_CommonDerived() As Task Dim input = @@ -750,7 +751,7 @@ End Module]]> Module Program Sub Main(args As String()) Dim list = New List(Of String) - list.AddRange($$goo()) + extensions.AddRange(list, $$goo()) End Sub End Module @@ -817,7 +818,7 @@ public class outer Module Program Sub Main(args As String()) Dim list = New List(Of String) - list.AddRange(goo()) + extensions.AddRange(list, goo()) End Sub Private Function goo() As MyStruct(Of outer.inner.derived3) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb index 79ff246fef36e..ee6ec6b0db7f8 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb @@ -73,12 +73,12 @@ class C Public Sub ClearAll() Implements IStreamingFindUsagesPresenter.ClearAll End Sub - Public Function StartSearch(title As String, supportsReferences As Boolean, cancellationToken As CancellationToken) As FindUsagesContext Implements IStreamingFindUsagesPresenter.StartSearch - Return _context + Public Function StartSearch(title As String, supportsReferences As Boolean) As (FindUsagesContext, CancellationToken) Implements IStreamingFindUsagesPresenter.StartSearch + Return (_context, CancellationToken.None) End Function - Public Function StartSearchWithCustomColumns(title As String, supportsReferences As Boolean, includeContainingTypeAndMemberColumns As Boolean, includeKindColumn As Boolean, cancellationToken As CancellationToken) As FindUsagesContext Implements IStreamingFindUsagesPresenter.StartSearchWithCustomColumns - Return _context + Public Function StartSearchWithCustomColumns(title As String, supportsReferences As Boolean, includeContainingTypeAndMemberColumns As Boolean, includeKindColumn As Boolean) As (FindUsagesContext, CancellationToken) Implements IStreamingFindUsagesPresenter.StartSearchWithCustomColumns + Return (_context, CancellationToken.None) End Function End Class End Class diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.FieldSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.FieldSymbols.vb index f2a5b1d4816c0..6a38780c96a1f 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.FieldSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.FieldSymbols.vb @@ -533,7 +533,7 @@ class Definition:Program End Function - Public Async Function TestField_UsedInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestField_UsedInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -556,7 +556,7 @@ class Definition:Program - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.IndexerSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.IndexerSymbols.vb index 392ea17d98e25..c457a693e7ca6 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.IndexerSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.IndexerSymbols.vb @@ -294,7 +294,7 @@ End Class End Function - Public Async Function TestCSharp_IndexerInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestCSharp_IndexerInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -317,7 +317,7 @@ class D - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LabelSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LabelSymbols.vb index be1468e96a7d8..cb44aa47992a0 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LabelSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LabelSymbols.vb @@ -88,7 +88,7 @@ End Module End Function - Public Async Function TestLabelInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestLabelInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -105,7 +105,7 @@ End Module - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb index 7564f4570ff4d..343010328d031 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb @@ -188,5 +188,44 @@ public class D : [|$$C|] Return TestAPIAndFeature(input, kind, host) End Function + + + + Public Async Function TestLinkedFiles_NamespaceInMetadataAndSource() As Task + Dim definition = + + + + + + + + + + Using workspace = TestWorkspace.Create(definition) + Dim invocationDocument = workspace.Documents.Single(Function(d) Not d.IsLinkFile) + Dim invocationPosition = invocationDocument.CursorPosition.Value + + Dim document = workspace.CurrentSolution.GetDocument(invocationDocument.Id) + Assert.NotNull(document) + + Dim symbol = Await SymbolFinder.FindSymbolAtPositionAsync(document, invocationPosition) + Dim references = Await SymbolFinder.FindReferencesAsync(symbol, document.Project.Solution, progress:=Nothing, documents:=Nothing) + + Assert.Equal(2, references.Count()) + Assert.Equal("System", references.ElementAt(0).Definition.ToString()) + Assert.Equal("System", references.ElementAt(1).Definition.ToString()) + End Using + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LocalFunctions.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LocalFunctions.vb index ff5d2be921f2e..fd70d9d0684ee 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LocalFunctions.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LocalFunctions.vb @@ -197,7 +197,7 @@ class Test End Function - Public Async Function TestLocalFunctionUsedInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestLocalFunctionUsedInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -214,7 +214,7 @@ class Test - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.NamedTypeSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.NamedTypeSymbols.vb index 03d2f45e0ffd7..bad2b344bfb5e 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.NamedTypeSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.NamedTypeSymbols.vb @@ -2533,7 +2533,7 @@ namespace N - Public Async Function TestNamedTypeUsedInSourceGeneratedOutput(kind As TestKind) As Task + Public Async Function TestNamedTypeUsedInSourceGeneratedOutput(kind As TestKind, host As TestHost) As Task Dim input = @@ -2541,7 +2541,7 @@ namespace N class D : [|C|] { } - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb index be74e04754d36..dabf62fca6418 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb @@ -107,6 +107,138 @@ class A Await TestAPIAndFeature(input, kind, host) End Function + + + Public Async Function TestCSharpFindReferencesOnEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + throw new System.NotImplementedException(); + public static bool operator !=(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnNotEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + throw new System.NotImplementedException(); + public static bool operator {|Definition:$$!=|}(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnGreaterThanOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + |] new A(); + } + public static bool operator {|Definition:$$>|}(A left, A right) => throw new System.NotImplementedException(); + public static bool operator <(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnLessThanOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + (A left, A right) => throw new System.NotImplementedException(); + public static bool operator {|Definition:$$<|}(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnGreaterThanOrEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + =|] new A(); + } + public static bool operator {|Definition:$$>=|}(A left, A right) => throw new System.NotImplementedException(); + public static bool operator <=(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnLessThanOrEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + =(A left, A right) => throw new System.NotImplementedException(); + public static bool operator {|Definition:$$<=|}(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + Public Async Function TestVisualBasicFindReferencesOnUnaryOperatorOverload(kind As TestKind, host As TestHost) As Task Dim input = @@ -326,7 +458,7 @@ class A End Function - Public Async Function TestCSharpFindOperatorUsedInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestCSharpFindOperatorUsedInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -348,7 +480,7 @@ class B - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb index 9af2045e94161..159df9d86c166 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OrdinaryMethodSymbols.vb @@ -3577,7 +3577,7 @@ End Class End Function - Public Async Function TestOrdinaryMethodUsedInSourceGenerator(kind As TestKind) As Task + Public Async Function TestOrdinaryMethodUsedInSourceGenerator(kind As TestKind, host As TestHost) As Task Dim input = @@ -3604,7 +3604,7 @@ End Class - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb index 3faef8f4a61e9..09db7620f9455 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ParameterSymbol.vb @@ -673,7 +673,7 @@ end class End Function - Public Async Function TestParameterReferencedInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestParameterReferencedInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -697,11 +697,11 @@ end class - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function - Public Async Function TestParameterDefinedInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestParameterDefinedInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -725,7 +725,7 @@ end class - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb index cc09e74a29f19..68ce3668624f5 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.PropertySymbols.vb @@ -1047,7 +1047,7 @@ namespace N End Function - Public Async Function TestCSharp_PropertyUseInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestCSharp_PropertyUseInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -1082,7 +1082,7 @@ namespace ConsoleApplication22 - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.RangeVariableSymbol.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.RangeVariableSymbol.vb index dec22007cec79..8c506f5f9b9b5 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.RangeVariableSymbol.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.RangeVariableSymbol.vb @@ -239,7 +239,7 @@ End Namespace End Function - Public Async Function TestCSharpRangeVariableUseInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestCSharpRangeVariableUseInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -255,7 +255,7 @@ class C - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index f0ad120697aef..fb13f7451a331 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -378,7 +378,7 @@ class Program End Function - Public Async Function TestTuplesUseInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestTuplesUseInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -409,7 +409,7 @@ partial class Program - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.XmlDocSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.XmlDocSymbols.vb index 3619fbd095492..c6b183d2003ca 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.XmlDocSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.XmlDocSymbols.vb @@ -1618,7 +1618,7 @@ End Class]]> End Function - Public Async Function TestCrefReferenceInSourceGeneratedDocument(kind As TestKind) As Task + Public Async Function TestCrefReferenceInSourceGeneratedDocument(kind As TestKind, host As TestHost) As Task Dim input = @@ -1641,7 +1641,7 @@ End Class]]> ]]> - Await TestAPIAndFeature(input, kind, TestHost.InProcess) ' TODO: support out of proc in tests: https://github.com/dotnet/roslyn/issues/50494 + Await TestAPIAndFeature(input, kind, host) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb index a844959deb7a0..a8cc26e9efcf4 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb @@ -73,7 +73,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Dim findRefsService = startDocument.GetLanguageService(Of IFindUsagesService) Dim context = New TestContext() - Await findRefsService.FindReferencesAsync(startDocument, cursorPosition, context) + Await findRefsService.FindReferencesAsync(startDocument, cursorPosition, context, CancellationToken.None) Dim expectedDefinitions = workspace.Documents.Where(Function(d) d.AnnotatedSpans.ContainsKey(DefinitionKey) AndAlso d.AnnotatedSpans(DefinitionKey).Any()). @@ -231,7 +231,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Return definition.DisplayIfNoReferences End Function - Public Overrides Function OnDefinitionFoundAsync(definition As DefinitionItem) As ValueTask + Public Overrides Function OnDefinitionFoundAsync(definition As DefinitionItem, cancellationToken As CancellationToken) As ValueTask SyncLock gate Me.Definitions.Add(definition) End SyncLock @@ -239,7 +239,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Return Nothing End Function - Public Overrides Function OnReferenceFoundAsync(reference As SourceReferenceItem) As ValueTask + Public Overrides Function OnReferenceFoundAsync(reference As SourceReferenceItem, cancellationToken As CancellationToken) As ValueTask SyncLock gate References.Add(reference) End SyncLock diff --git a/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb b/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb index 60215d012da30..22d596ea3da32 100644 --- a/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb +++ b/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb @@ -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. +Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.FindUsages Imports Microsoft.CodeAnalysis.Editor.GoToBase Imports Microsoft.CodeAnalysis.Remote.Testing @@ -15,7 +16,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase testHost:=TestHost.InProcess, Async Function(document As Document, position As Integer, context As SimpleFindUsagesContext) Dim gotoBaseService = document.GetLanguageService(Of IGoToBaseService) - Await gotoBaseService.FindBasesAsync(document, position, context) + Await gotoBaseService.FindBasesAsync(document, position, context, CancellationToken.None) End Function, shouldSucceed, metadataDefinitions) End Function diff --git a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb similarity index 79% rename from src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb rename to src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb index 2bcf957b57910..172913ebd782f 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb @@ -2,119 +2,10 @@ ' 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.Threading -Imports Microsoft.CodeAnalysis.Editor.CSharp.GoToDefinition -Imports Microsoft.CodeAnalysis.Editor.Host -Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.Editor.VisualBasic.GoToDefinition -Imports Microsoft.CodeAnalysis.Navigation -Imports Microsoft.VisualStudio.Text - Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition <[UseExportProvider]> - Public Class GoToDefinitionTests - Friend Shared Sub Test( - workspaceDefinition As XElement, - expectedResult As Boolean, - executeOnDocument As Func(Of Document, Integer, IThreadingContext, IStreamingFindUsagesPresenter, Boolean)) - Using workspace = TestWorkspace.Create(workspaceDefinition, composition:=GoToTestHelpers.Composition) - Dim solution = workspace.CurrentSolution - Dim cursorDocument = workspace.Documents.First(Function(d) d.CursorPosition.HasValue) - Dim cursorPosition = cursorDocument.CursorPosition.Value - - ' Set up mocks. The IDocumentNavigationService should be called if there is one, - ' location and the INavigableItemsPresenter should be called if there are - ' multiple locations. - - ' prepare a notification listener - Dim textView = cursorDocument.GetTextView() - Dim textBuffer = textView.TextBuffer - textView.Caret.MoveTo(New SnapshotPoint(textBuffer.CurrentSnapshot, cursorPosition)) - - Dim cursorBuffer = cursorDocument.GetTextBuffer() - Dim document = workspace.CurrentSolution.GetDocument(cursorDocument.Id) - - Dim mockDocumentNavigationService = DirectCast(workspace.Services.GetService(Of IDocumentNavigationService)(), MockDocumentNavigationService) - Dim mockSymbolNavigationService = DirectCast(workspace.Services.GetService(Of ISymbolNavigationService)(), MockSymbolNavigationService) - - Dim presenterCalled As Boolean = False - Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)() - Dim presenter = New MockStreamingFindUsagesPresenter(Sub() presenterCalled = True) - Dim actualResult = executeOnDocument(document, cursorPosition, threadingContext, presenter) - - Assert.Equal(expectedResult, actualResult) - - Dim expectedLocations As New List(Of FilePathAndSpan) - - For Each testDocument In workspace.Documents - For Each selectedSpan In testDocument.SelectedSpans - expectedLocations.Add(New FilePathAndSpan(testDocument.FilePath, selectedSpan)) - Next - Next - - expectedLocations.Sort() - - Dim context = presenter.Context - If expectedResult Then - If expectedLocations.Count = 0 Then - ' if there is not expected locations, it means symbol navigation is used - Assert.True(mockSymbolNavigationService._triedNavigationToSymbol) - Assert.Null(mockDocumentNavigationService._documentId) - Assert.False(presenterCalled) - Else - Assert.False(mockSymbolNavigationService._triedNavigationToSymbol) - - If mockDocumentNavigationService._triedNavigationToSpan Then - Dim definitionDocument = workspace.GetTestDocument(mockDocumentNavigationService._documentId) - Assert.Single(definitionDocument.SelectedSpans) - Assert.Equal(definitionDocument.SelectedSpans.Single(), mockDocumentNavigationService._span) - - ' The INavigableItemsPresenter should not have been called - Assert.False(presenterCalled) - Else - Assert.False(mockDocumentNavigationService._triedNavigationToPosition) - Assert.False(mockDocumentNavigationService._triedNavigationToLineAndOffset) - Assert.True(presenterCalled) - - Dim actualLocations As New List(Of FilePathAndSpan) - - Dim items = context.GetDefinitions() - - For Each location In items - For Each docSpan In location.SourceSpans - actualLocations.Add(New FilePathAndSpan(docSpan.Document.FilePath, docSpan.SourceSpan)) - Next - Next - - actualLocations.Sort() - Assert.Equal(expectedLocations, actualLocations) - - ' The IDocumentNavigationService should not have been called - Assert.Null(mockDocumentNavigationService._documentId) - End If - End If - Else - Assert.False(mockSymbolNavigationService._triedNavigationToSymbol) - Assert.Null(mockDocumentNavigationService._documentId) - Assert.False(presenterCalled) - End If - - End Using - End Sub - - Private Shared Sub Test(workspaceDefinition As XElement, Optional expectedResult As Boolean = True) - Test(workspaceDefinition, expectedResult, - Function(document, cursorPosition, threadingContext, presenter) - Dim goToDefService = If(document.Project.Language = LanguageNames.CSharp, - DirectCast(New CSharpGoToDefinitionService(threadingContext, presenter), IGoToDefinitionService), - New VisualBasicGoToDefinitionService(threadingContext, presenter)) - - Return goToDefService.TryGoToDefinition(document, cursorPosition, CancellationToken.None) - End Function) - End Sub - + Public Class CSharpGoToDefinitionTests + Inherits GoToDefinitionTestsBase #Region "P2P Tests" @@ -128,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition class CSharpClass { - VBCl$$ass vb + VB$$Class vb } @@ -223,30 +114,6 @@ class Program Test(workspace) End Sub - - - Public Sub TestVisualBasicGoToDefinitionOnAnonymousMember() - Dim workspace = - - - -public class MyClass1 - public property [|Prop1|] as integer -end class -class Program - sub Main() - dim instance = new MyClass1() - - dim x as new With { instance.$$Prop1 } - end sub -end class - - - - - Test(workspace) - End Sub - Public Sub TestCSharpGoToDefinitionSameClass() Dim workspace = @@ -1714,19 +1581,28 @@ class D #End Region -#Region "Normal Visual Basic Tests" - + - Public Sub TestVisualBasicGoToDefinition() + Public Sub TestCSharpTestAliasAndTarget1() Dim workspace = - + - Class [|SomeClass|] - End Class - Class OtherClass - Dim obj As Some$$Class - End Class +using [|AliasedSomething|] = X.Something; + +namespace X +{ + class Something { public Something() { } } +} + +class Program +{ + static void Main(string[] args) + { + $$AliasedSomething x = new AliasedSomething(); + X.Something y = new X.Something(); + } +} @@ -1734,14 +1610,28 @@ class D Test(workspace) End Sub + - - Public Sub TestVisualBasicLiteralGoToDefinition() + Public Sub TestCSharpTestAliasAndTarget2() Dim workspace = - + - Dim x as Integer = 12$$3 +using [|AliasedSomething|] = X.Something; + +namespace X +{ + class Something { public Something() { } } +} + +class Program +{ + static void Main(string[] args) + { + AliasedSomething x = new $$AliasedSomething(); + X.Something y = new X.Something(); + } +} @@ -1749,14 +1639,28 @@ class D Test(workspace) End Sub + - - Public Sub TestVisualBasicStringLiteralGoToDefinition() + Public Sub TestCSharpTestAliasAndTarget3() Dim workspace = - + - Dim x as String = "wo$$ow" +using AliasedSomething = X.Something; + +namespace X +{ + class [|Something|] { public Something() { } } +} + +class Program +{ + static void Main(string[] args) + { + AliasedSomething x = new AliasedSomething(); + X.$$Something y = new X.Something(); + } +} @@ -1764,19 +1668,28 @@ class D Test(workspace) End Sub - + - Public Sub TestVisualBasicPropertyBackingField() + Public Sub TestCSharpTestAliasAndTarget4() Dim workspace = - + -Class C - Property [|P|] As Integer - Sub M() - Me.$$_P = 10 - End Sub -End Class +using AliasedSomething = X.Something; + +namespace X +{ + class Something { public [|Something|]() { } } +} + +class Program +{ + static void Main(string[] args) + { + AliasedSomething x = new AliasedSomething(); + X.Something y = new X.$$Something(); + } +} @@ -1784,58 +1697,126 @@ End Class Test(workspace) End Sub +#Region "Show notification tests" + - Public Sub TestVisualBasicGoToDefinitionSameClass() + Public Sub TestShowNotificationCS() Dim workspace = - + - Class [|SomeClass|] - Dim obj As Some$$Class - End Class + class SomeClass { } + cl$$ass OtherClass + { + SomeClass obj; + } - Test(workspace) + Test(workspace, expectedResult:=False) End Sub + - Public Sub TestVisualBasicGoToDefinitionNestedClass() + Public Sub TestGoToDefinitionOnGlobalKeyword() Dim workspace = - + - Class Outer - Class [|Inner|] - End Class - Dim obj as In$$ner - End Class + class C + { + gl$$obal::System.String s; + } - Test(workspace) + Test(workspace, expectedResult:=False) End Sub +#End Region + +#Region "CSharp Query expressions Tests" + + Private Shared Function GetExpressionPatternDefinition(highlight As String, Optional index As Integer = 0) As String + Dim definition As String = +" +using System; +namespace QueryPattern +{ + public class C + { + public C Cast() => throw new NotImplementedException(); + } + + public class C : C + { + public C Where(Func predicate) => throw new NotImplementedException(); + public C Select(Func selector) => throw new NotImplementedException(); + public C SelectMany(Func> selector, Func resultSelector) => throw new NotImplementedException(); + public C Join(C inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) => throw new NotImplementedException(); + public C GroupJoin(C inner, Func outerKeySelector, Func innerKeySelector, Func, V> resultSelector) => throw new NotImplementedException(); + public O OrderBy(Func keySelector) => throw new NotImplementedException(); + public O OrderByDescending(Func keySelector) => throw new NotImplementedException(); + public C> GroupBy(Func keySelector) => throw new NotImplementedException(); + public C> GroupBy(Func keySelector, Func elementSelector) => throw new NotImplementedException(); + } + + public class O : C + { + public O ThenBy(Func keySelector) => throw new NotImplementedException(); + public O ThenByDescending(Func keySelector) => throw new NotImplementedException(); + } + + public class G : C + { + public K Key { get; } + } +} +" + If highlight = "" Then + Return definition + End If + Dim searchStartPosition As Integer = 0 + Dim searchFound As Integer + For i As Integer = 0 To index + searchFound = definition.IndexOf(highlight, searchStartPosition) + If searchFound < 0 Then + Exit For + End If + Next + If searchFound >= 0 Then + definition = definition.Insert(searchFound + highlight.Length, "|]") + definition = definition.Insert(searchFound, "[|") + Return definition + End If + Throw New InvalidOperationException("Highlight not found") + End Function + - Public Sub TestVisualBasicGotoDefinitionDifferentFiles() + Public Sub TestQuerySelect() Dim workspace = - - - Class OtherClass - Dim obj As SomeClass - End Class - + - Class OtherClass2 - Dim obj As Some$$Class - End Class + <%= GetExpressionPatternDefinition("Select") %> + + + QueryPattern - Class [|SomeClass|] - End Class + () + $$select i; + } +} +]]> @@ -1843,29 +1824,31 @@ End Class Test(workspace) End Sub + - Public Sub TestVisualBasicGotoDefinitionPartialClasses() + Public Sub TestQueryWhere() Dim workspace = - - - DummyClass - End Class - - - Partial Class [|OtherClass|] - Dim a As Integer - End Class - + - Partial Class [|OtherClass|] - Dim b As Integer - End Class + <%= GetExpressionPatternDefinition("Where") %> + + + QueryPattern - Class ConsumingClass - Dim obj As Other$$Class - End Class + () + $$where true + select i; + } +} +]]> @@ -1873,22 +1856,31 @@ End Class Test(workspace) End Sub + - Public Sub TestVisualBasicGotoDefinitionMethod() + Public Sub TestQuerySelectMany1() Dim workspace = - + - Class [|SomeClass|] - Dim x As Integer - End Class + <%= GetExpressionPatternDefinition("SelectMany") %> + + + QueryPattern - Class ConsumingClass - Sub goo() - Dim obj As Some$$Class - End Sub - End Class + () + $$from i2 in new C() + select i1; + } +} +]]> @@ -1896,29 +1888,31 @@ End Class Test(workspace) End Sub - + - Public Sub TestVisualBasicGotoDefinitionPartialMethod() + Public Sub TestQuerySelectMany2() Dim workspace = - + - Partial Class Customer - Private Sub [|OnNameChanged|]() - - End Sub - End Class + <%= GetExpressionPatternDefinition("SelectMany") %> + + + QueryPattern - Partial Class Customer - Sub New() - Dim x As New Customer() - x.OnNameChanged$$() - End Sub - Partial Private Sub OnNameChanged() - - End Sub - End Class + () + from i2 $$in new C() + select i1; + } +} +]]> @@ -1926,22 +1920,31 @@ End Class Test(workspace) End Sub + - Public Sub TestVisualBasicTouchLeft() + Public Sub TestQueryJoin1() Dim workspace = - + - Class [|SomeClass|] - Dim x As Integer - End Class + <%= GetExpressionPatternDefinition("Join") %> + + + QueryPattern - Class ConsumingClass - Sub goo() - Dim obj As $$SomeClass - End Sub - End Class + () + $$join i2 in new C() on i2 equals i1 + select i2; + } +} +]]> @@ -1949,22 +1952,31 @@ End Class Test(workspace) End Sub + - Public Sub TestVisualBasicTouchRight() + Public Sub TestQueryJoin2() Dim workspace = - + - Class [|SomeClass|] - Dim x As Integer - End Class + <%= GetExpressionPatternDefinition("Join") %> + + + QueryPattern - Class ConsumingClass - Sub goo() - Dim obj As SomeClass$$ - End Sub - End Class + () + join i2 $$in new C() on i1 equals i2 + select i2; + } +} +]]> @@ -1972,67 +1984,31 @@ End Class Test(workspace) End Sub - + - Public Sub TestVisualBasicMe() + Public Sub TestQueryJoin3() Dim workspace = - + -Class B - Sub New() - End Sub -End Class - -Class [|C|] - Inherits B - - Sub New() - MyBase.New() - MyClass.Goo() - $$Me.Bar() - End Sub - - Private Sub Bar() - End Sub - - Private Sub Goo() - End Sub -End Class + <%= GetExpressionPatternDefinition("Join") %> - - - Test(workspace) - End Sub - - - - Public Sub TestVisualBasicMyClass() - Dim workspace = - - + + QueryPattern -Class B - Sub New() - End Sub -End Class - -Class [|C|] - Inherits B - - Sub New() - MyBase.New() - $$MyClass.Goo() - Me.Bar() - End Sub - - Private Sub Bar() - End Sub - - Private Sub Goo() - End Sub -End Class + () + join i2 in new C() $$on i1 equals i2 + select i2; + } +} +]]> @@ -2040,56 +2016,31 @@ End Class Test(workspace) End Sub - + - Public Sub TestVisualBasicMyBase() + Public Sub TestQueryJoin4() Dim workspace = - + -Class [|B|] - Sub New() - End Sub -End Class - -Class C - Inherits B - - Sub New() - $$MyBase.New() - MyClass.Goo() - Me.Bar() - End Sub - - Private Sub Bar() - End Sub - - Private Sub Goo() - End Sub -End Class + <%= GetExpressionPatternDefinition("Join") %> - - - Test(workspace) - End Sub - - - Public Sub TestVisualBasicGoToOverridenSubDefinition() - Dim workspace = - - + + QueryPattern - Class Base - Overridable Sub [|Method|]() - End Sub - End Class - Class Derived - Inherits Base - - Overr$$ides Sub Method() - End Sub - End Class + () + join i2 in new C() on i1 $$equals i2 + select i2; + } +} +]]> @@ -2097,45 +2048,31 @@ End Class Test(workspace) End Sub + - Public Sub TestVisualBasicGoToOverridenFunctionDefinition() + Public Sub TestQueryGroupJoin1() Dim workspace = - + - Class Base - Overridable Function [|Method|]() As Integer - Return 1 - End Function - End Class - Class Derived - Inherits Base - - Overr$$ides Function Method() As Integer - Return 1 - End Function - End Class + <%= GetExpressionPatternDefinition("GroupJoin") %> - - - Test(workspace) - End Sub - - - Public Sub TestVisualBasicGoToOverridenPropertyDefinition() - Dim workspace = - - + + QueryPattern - Class Base - Overridable Property [|Number|] As Integer - End Class - Class Derived - Inherits Base - - Overr$$ides Property Number As Integer - End Class + () + $$join i2 in new C() on i1 equals i2 into g + select g; + } +} +]]> @@ -2143,44 +2080,31 @@ End Class Test(workspace) End Sub -#End Region - -#Region "Venus Visual Basic Tests" - + - Public Sub TestVisualBasicVenusGotoDefinition() + Public Sub TestQueryGroupJoin2() Dim workspace = - + - #ExternalSource ("Default.aspx", 1) - Class [|Program|] - Sub Main(args As String()) - Dim f As New Pro$$gram() - End Sub - End Class - #End ExternalSource + <%= GetExpressionPatternDefinition("GroupJoin") %> - - - Test(workspace) - End Sub - - - - Public Sub TestVisualBasicFilterGotoDefResultsFromHiddenCodeForUIPresenters() - Dim workspace = - - + + QueryPattern - Class [|Program|] - Sub Main(args As String()) - #ExternalSource ("Default.aspx", 1) - Dim f As New Pro$$gram() - End Sub - End Class - #End ExternalSource + () + join i2 $$in new C() on i1 equals i2 into g + select g; + } +} +]]> @@ -2188,44 +2112,63 @@ End Class Test(workspace) End Sub - + - Public Sub TestVisualBasicDoNotFilterGotoDefResultsFromHiddenCodeForApis() + Public Sub TestQueryGroupJoin3() Dim workspace = - + - Class [|Program|] - Sub Main(args As String()) - #ExternalSource ("Default.aspx", 1) - Dim f As New Pro$$gram() - End Sub - End Class - #End ExternalSource + <%= GetExpressionPatternDefinition("GroupJoin") %> + + + + QueryPattern + + () + join i2 in new C() $$on i1 equals i2 into g + select g; + } +} +]]> Test(workspace) End Sub -#End Region + - Public Sub TestVisualBasicTestThroughExecuteCommand() + Public Sub TestQueryGroupJoin4() Dim workspace = - + - Class [|SomeClass|] - Dim x As Integer - End Class + <%= GetExpressionPatternDefinition("GroupJoin") %> + + + QueryPattern - Class ConsumingClass - Sub goo() - Dim obj As SomeClass$$ - End Sub - End Class + () + join i2 in new C() on i1 $$equals i2 into g + select g; + } +} +]]> @@ -2233,28 +2176,30 @@ End Class Test(workspace) End Sub + - Public Sub TestVisualBasicGoToDefinitionOnExtensionMethod() + Public Sub TestQueryGroupBy1() Dim workspace = - + + + <%= GetExpressionPatternDefinition("GroupBy") %> + + + + QueryPattern - Public Sub TestExt(Of T)(ex As T) - End Sub - - Public Sub [|TestExt|](ex As string) - End Sub -End Module]]>] +using QueryPattern; +class Test +{ + static void M() + { + var qry = from i1 in new C() + $$group i1 by i1; + } +} +]]> @@ -2262,28 +2207,30 @@ End Module]]>] Test(workspace) End Sub - + - Public Sub TestCSharpTestAliasAndTarget1() + Public Sub TestQueryGroupBy2() Dim workspace = - + -using [|AliasedSomething|] = X.Something; - -namespace X -{ - class Something { public Something() { } } -} - -class Program + <%= GetExpressionPatternDefinition("GroupBy") %> + + + + QueryPattern + + () + group i1 $$by i1; } } +]]> @@ -2291,28 +2238,30 @@ class Program Test(workspace) End Sub - + - Public Sub TestCSharpTestAliasAndTarget2() + Public Sub TestQueryFromCast1() Dim workspace = - + -using [|AliasedSomething|] = X.Something; - -namespace X -{ - class Something { public Something() { } } -} - -class Program + <%= GetExpressionPatternDefinition("Cast") %> + + + + QueryPattern + + () + select i1; } } +]]> @@ -2320,28 +2269,30 @@ class Program Test(workspace) End Sub - + - Public Sub TestCSharpTestAliasAndTarget3() + Public Sub TestQueryFromCast2() Dim workspace = - + -using AliasedSomething = X.Something; - -namespace X -{ - class [|Something|] { public Something() { } } -} - -class Program + <%= GetExpressionPatternDefinition("Cast") %> + + + + QueryPattern + + () + select i1; } } +]]> @@ -2349,28 +2300,31 @@ class Program Test(workspace) End Sub - + - Public Sub TestCSharpTestAliasAndTarget4() + Public Sub TestQueryJoinCast1() Dim workspace = - + -using AliasedSomething = X.Something; - -namespace X -{ - class Something { public [|Something|]() { } } -} - -class Program + <%= GetExpressionPatternDefinition("Cast") %> + + + + QueryPattern + + () + join int i2 $$in new C() on i1 equals i2 + select i2; } } +]]> @@ -2378,23 +2332,31 @@ class Program Test(workspace) End Sub - + - Public Sub TestVisualBasicQueryRangeVariable() + Public Sub TestQueryJoinCast2() Dim workspace = - + -Imports System -Imports System.Collections.Generic -Imports System.Linq - -Module Program - Sub Main(args As String()) - Dim arr = New Integer() {4, 5} - Dim q3 = From [|num|] In arr Select $$num - End Sub -End Module + <%= GetExpressionPatternDefinition("Join") %> + + + + QueryPattern + + () + $$join int i2 in new C() on i1 equals i2 + select i2; + } +} +]]> @@ -2402,19 +2364,31 @@ End Module Test(workspace) End Sub - + - Public Sub TestVisualBasicGotoConstant() + Public Sub TestQuerySelectManyCast1() Dim workspace = - + + + <%= GetExpressionPatternDefinition("Cast") %> + + + + QueryPattern -Module M - Sub Main() -label1: GoTo $$200 -[|200|]: GoTo label1 - End Sub -End Module + () + from int i2 $$in new C() + select i2; + } +} +]]> @@ -2422,32 +2396,31 @@ End Module Test(workspace) End Sub - - + - Public Sub TestCrossLanguageParameterizedPropertyOverride() + Public Sub TestQuerySelectManyCast2() Dim workspace = - + -Public Class A - Public Overridable ReadOnly Property X(y As Integer) As Integer - [|Get|] - End Get - End Property -End Class + <%= GetExpressionPatternDefinition("SelectMany") %> - - VBProj + + QueryPattern -class B : A + () + $$from int i2 in new C() + select i2; } } +]]> @@ -2455,29 +2428,31 @@ class B : A Test(workspace) End Sub - + - Public Sub TestCrossLanguageNavigationToVBModuleMember() + Public Sub TestQueryOrderBySingleParameter() Dim workspace = - + -Public Module A - Public Sub [|M|]() - End Sub -End Module + <%= GetExpressionPatternDefinition("OrderBy") %> - - VBProj + + QueryPattern -class C + () + $$orderby i1 + select i1; } } +]]> @@ -2485,82 +2460,63 @@ class C Test(workspace) End Sub -#Region "Show notification tests" - + - Public Sub TestShowNotificationVB() + Public Sub TestQueryOrderBySingleParameterWithOrderClause() Dim workspace = - + - Class SomeClass - End Class - Cl$$ass OtherClass - Dim obj As SomeClass - End Class + <%= GetExpressionPatternDefinition("OrderByDescending") %> - - - Test(workspace, expectedResult:=False) - End Sub - - - Public Sub TestShowNotificationCS() - Dim workspace = - - + + QueryPattern - class SomeClass { } - cl$$ass OtherClass - { - SomeClass obj; - } + () + orderby i1 $$descending + select i1; + } +} +]]> - Test(workspace, expectedResult:=False) + Test(workspace) End Sub - + - Public Sub TestGoToDefinitionOnGlobalKeyword() + Public Sub TestQueryOrderByTwoParameterWithoutOrderClause() Dim workspace = - + - class C - { - gl$$obal::System.String s; - } + <%= GetExpressionPatternDefinition("ThenBy") %> - - - Test(workspace, expectedResult:=False) - End Sub - - - - Public Sub TestGoToDefinitionOnInferredFieldInitializer() - Dim workspace = - - + + QueryPattern -Public Class Class2 - Sub Test() - Dim var1 = New With {Key .var2 = "Bob", Class2.va$$r3} - End Sub - - Shared Property [|var3|]() As Integer - Get - End Get - Set(ByVal value As Integer) - End Set - End Property -End Class - + () + orderby i1,$$ i2 + select i1; + } +} +]]> @@ -2568,104 +2524,46 @@ End Class Test(workspace) End Sub - + - Public Sub TestGoToDefinitionGlobalImportAlias() + Public Sub TestQueryOrderByTwoParameterWithOrderClause() Dim workspace = - - VBAssembly - - Goo = Importable.ImportMe - + -Public Class Class2 - Sub Test() - Dim x as Go$$o - End Sub -End Class - + <%= GetExpressionPatternDefinition("ThenByDescending") %> - + + QueryPattern -Namespace Importable - Public Class [|ImportMe|] - End Class -End Namespace + () + orderby i1, i2 $$descending + select i1; + } +} +]]> Test(workspace) End Sub -#End Region - -#Region "CSharp Query expressions Tests" - - Private Shared Function GetExpressionPatternDefinition(highlight As String, Optional index As Integer = 0) As String - Dim definition As String = -" -using System; -namespace QueryPattern -{ - public class C - { - public C Cast() => throw new NotImplementedException(); - } - - public class C : C - { - public C Where(Func predicate) => throw new NotImplementedException(); - public C Select(Func selector) => throw new NotImplementedException(); - public C SelectMany(Func> selector, Func resultSelector) => throw new NotImplementedException(); - public C Join(C inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) => throw new NotImplementedException(); - public C GroupJoin(C inner, Func outerKeySelector, Func innerKeySelector, Func, V> resultSelector) => throw new NotImplementedException(); - public O OrderBy(Func keySelector) => throw new NotImplementedException(); - public O OrderByDescending(Func keySelector) => throw new NotImplementedException(); - public C> GroupBy(Func keySelector) => throw new NotImplementedException(); - public C> GroupBy(Func keySelector, Func elementSelector) => throw new NotImplementedException(); - } - - public class O : C - { - public O ThenBy(Func keySelector) => throw new NotImplementedException(); - public O ThenByDescending(Func keySelector) => throw new NotImplementedException(); - } - - public class G : C - { - public K Key { get; } - } -} -" - If highlight = "" Then - Return definition - End If - Dim searchStartPosition As Integer = 0 - Dim searchFound As Integer - For i As Integer = 0 To index - searchFound = definition.IndexOf(highlight, searchStartPosition) - If searchFound < 0 Then - Exit For - End If - Next - If searchFound >= 0 Then - definition = definition.Insert(searchFound + highlight.Length, "|]") - definition = definition.Insert(searchFound, "[|") - Return definition - End If - Throw New InvalidOperationException("Highlight not found") - End Function - Public Sub TestQuerySelect() + Public Sub TestQueryDegeneratedSelect() Dim workspace = - <%= GetExpressionPatternDefinition("Select") %> + <%= GetExpressionPatternDefinition("") %> @@ -2677,8 +2575,9 @@ class Test { static void M() { - var qry = from i in new C() - $$select i; + var qry = from i1 in new C() + where true + $$select i1; } } ]]> @@ -2686,17 +2585,17 @@ class Test - Test(workspace) + Test(workspace, False) End Sub - Public Sub TestQueryWhere() + Public Sub TestQueryLet() Dim workspace = - <%= GetExpressionPatternDefinition("Where") %> + <%= GetExpressionPatternDefinition("Select") %> @@ -2708,9 +2607,9 @@ class Test { static void M() { - var qry = from i in new C() - $$where true - select i; + var qry = from i1 in new C() + $$let i2=1 + select new { i1, i2 }; } } ]]> @@ -2720,32 +2619,77 @@ class Test Test(workspace) End Sub +#End Region - - Public Sub TestQuerySelectMany1() + Public Sub TestCSharpGoToOnBreakInSwitchStatement() Dim workspace = - + - <%= GetExpressionPatternDefinition("SelectMany") %> +class C +{ + void M(object o) + { + switch (o) + { + case string s: + bre$$ak; + default: + return; + }[||] + } +} - - QueryPattern + + + Test(workspace) + End Sub + + + Public Sub TestCSharpGoToOnContinueInSwitchStatement() + Dim workspace = + + - () - $$from i2 in new C() - select i1; + switch (o) + { + case string s: + cont$$inue; + default: + return; + } + } +} + + + + + Test(workspace, expectedResult:=False) + End Sub + + + Public Sub TestCSharpGoToOnBreakInDoStatement() + Dim workspace = + + + +class C +{ + void M() + { + do + { + bre$$ak; + } + while (true)[||] } } -]]> @@ -2753,31 +2697,23 @@ class Test Test(workspace) End Sub - - Public Sub TestQuerySelectMany2() + Public Sub TestCSharpGoToOnContinueInDoStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("SelectMany") %> - - - - QueryPattern + - () - from i2 $$in new C() - select i1; + [||]do + { + cont$$inue; + } + while (true); } } -]]> @@ -2785,31 +2721,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryJoin1() + Public Sub TestCSharpGoToOnBreakInForStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("Join") %> - - - - QueryPattern + - () - $$join i2 in new C() on i2 equals i1 - select i2; + for (int i = 0; ; ) + { + bre$$ak; + }[||] } } -]]> @@ -2817,31 +2744,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryJoin2() + Public Sub TestCSharpGoToOnContinueInForStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("Join") %> - - - - QueryPattern + - () - join i2 $$in new C() on i1 equals i2 - select i2; + [||]for (int i = 0; ; ) + { + cont$$inue; + } } } -]]> @@ -2849,31 +2767,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryJoin3() + Public Sub TestCSharpGoToOnBreakInForeachStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("Join") %> - - - - QueryPattern + - () - join i2 in new C() $$on i1 equals i2 - select i2; + foreach (int i in null) + { + bre$$ak; + }[||] } } -]]> @@ -2881,31 +2790,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryJoin4() + Public Sub TestCSharpGoToOnContinueInForeachStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("Join") %> - - - - QueryPattern + - () - join i2 in new C() on i1 $$equals i2 - select i2; + [||]foreach (int i in null) + { + cont$$inue; + } } } -]]> @@ -2913,31 +2813,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryGroupJoin1() + Public Sub TestCSharpGoToOnBreakInForeachVariableStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("GroupJoin") %> - - - - QueryPattern + - () - $$join i2 in new C() on i1 equals i2 into g - select g; + foreach (var (i, j) in null) + { + bre$$ak; + }[||] } } -]]> @@ -2945,31 +2836,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryGroupJoin2() + Public Sub TestCSharpGoToOnContinueInForeachVariableStatement() Dim workspace = - - - <%= GetExpressionPatternDefinition("GroupJoin") %> - - - - QueryPattern + - () - join i2 $$in new C() on i1 equals i2 into g - select g; + [||]foreach (var (i, j) in null) + { + cont$$inue; + } } } -]]> @@ -2977,31 +2859,26 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryGroupJoin3() + Public Sub TestCSharpGoToOnContinueInSwitchInForeach() Dim workspace = - - - <%= GetExpressionPatternDefinition("GroupJoin") %> - - - - QueryPattern + - () - join i2 in new C() $$on i1 equals i2 into g - select g; + [||]foreach (var (i, j) in null) + { + switch (1) + { + default: + cont$$inue; + } + } } } -]]> @@ -3009,155 +2886,160 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryGroupJoin4() + Public Sub TestCSharpGoToOnTopLevelContinue() Dim workspace = - + - <%= GetExpressionPatternDefinition("GroupJoin") %> +cont$$inue; - - QueryPattern + + + Test(workspace, expectedResult:=False) + End Sub + + + Public Sub TestCSharpGoToOnBreakInParenthesizedLambda() + Dim workspace = + + - () - join i2 in new C() on i1 $$equals i2 into g - select g; + switch (o) + { + case string s: + System.Action a = () => { bre$$ak; }; + break; + default: + return; + } } } -]]> - Test(workspace) + Test(workspace, expectedResult:=False) End Sub - - Public Sub TestQueryGroupBy1() + Public Sub TestCSharpGoToOnBreakInSimpleLambda() Dim workspace = - - - <%= GetExpressionPatternDefinition("GroupBy") %> - - - - QueryPattern + - () - $$group i1 by i1; + switch (o) + { + case string s: + System.Action a = _ => { bre$$ak; }; + break; + default: + return; + } } } -]]> - Test(workspace) + Test(workspace, expectedResult:=False) End Sub - - Public Sub TestQueryGroupBy2() + Public Sub TestCSharpGoToOnBreakInLocalFunction() Dim workspace = - + - <%= GetExpressionPatternDefinition("GroupBy") %> +class C +{ + void M(object o) + { + switch (o) + { + case string s: + void local() + { + System.Action a = _ => { bre$$ak; }; + } + break; + default: + return; + } + } +} - - QueryPattern + + + Test(workspace, expectedResult:=False) + End Sub + + + Public Sub TestCSharpGoToOnBreakInMethod() + Dim workspace = + + - () - group i1 $$by i1; + bre$$ak; } } -]]> - Test(workspace) + Test(workspace, expectedResult:=False) End Sub - - Public Sub TestQueryFromCast1() + Public Sub TestCSharpGoToOnBreakInAccessor() Dim workspace = - - - <%= GetExpressionPatternDefinition("Cast") %> - - - - QueryPattern + - () - select i1; + set { bre$$ak; } } } -]]> - Test(workspace) + Test(workspace, expectedResult:=False) End Sub - - Public Sub TestQueryFromCast2() + Public Sub TestCSharpGoToOnReturnInVoidMethod() Dim workspace = - - - <%= GetExpressionPatternDefinition("Cast") %> - - - - QueryPattern + - () - select i1; + for (int i = 0; ; ) + { + return$$; + } } } -]]> @@ -3165,31 +3047,23 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryJoinCast1() + Public Sub TestCSharpGoToOnReturnInIntMethod() Dim workspace = - - - <%= GetExpressionPatternDefinition("Cast") %> - - - - QueryPattern + - () - join int i2 $$in new C() on i1 equals i2 - select i2; + for (int i = 0; ; ) + { + return$$ 1; + } } } -]]> @@ -3197,31 +3071,25 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryJoinCast2() + Public Sub TestCSharpGoToOnReturnInVoidLambda() Dim workspace = - - - <%= GetExpressionPatternDefinition("Join") %> - - - - QueryPattern + - () - $$join int i2 in new C() on i1 equals i2 - select i2; + System.Action a = [||]() => + { + for (int i = 0; ; ) + { + return$$; + } + }; } } -]]> @@ -3229,31 +3097,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQuerySelectManyCast1() + Public Sub TestCSharpGoToOnReturnedExpression() Dim workspace = - - - <%= GetExpressionPatternDefinition("Cast") %> - - - - QueryPattern + - () - from int i2 $$in new C() - select i2; + for (int [|i|] = 0; ; ) + { + return $$i; + } } } -]]> @@ -3261,31 +3120,22 @@ class Test Test(workspace) End Sub - - Public Sub TestQuerySelectManyCast2() + Public Sub TestCSharpGoToOnReturnedConstantExpression() Dim workspace = - - - <%= GetExpressionPatternDefinition("SelectMany") %> - - - - QueryPattern + - () - $$from int i2 in new C() - select i2; + for (int i = 0; ; ) + { + return $$1; + } } } -]]> @@ -3293,31 +3143,19 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryOrderBySingleParameter() + Public Sub TestCSharpGoToOnYieldReturn_Return() Dim workspace = - - - <%= GetExpressionPatternDefinition("OrderBy") %> - - - - QueryPattern + - () - $$orderby i1 - select i1; + yield return$$ 1; } } -]]> @@ -3325,31 +3163,19 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryOrderBySingleParameterWithOrderClause() + Public Sub TestCSharpGoToOnYieldReturn_Yield() Dim workspace = - - - <%= GetExpressionPatternDefinition("OrderByDescending") %> - - - - QueryPattern + - () - orderby i1 $$descending - select i1; + yield$$ return 1; } } -]]> @@ -3357,31 +3183,21 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryOrderByTwoParameterWithoutOrderClause() + Public Sub TestCSharpGoToOnYieldReturn_Yield_Partial() Dim workspace = - - - <%= GetExpressionPatternDefinition("ThenBy") %> - - - - QueryPattern + - () - orderby i1,$$ i2 - select i1; + yield$$ return 1; } } -]]> @@ -3389,31 +3205,21 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryOrderByTwoParameterWithOrderClause() + Public Sub TestCSharpGoToOnYieldReturn_Yield_Partial_ReverseOrder() Dim workspace = - - - <%= GetExpressionPatternDefinition("ThenByDescending") %> - - - - QueryPattern + - () - orderby i1, i2 $$descending - select i1; + yield$$ return 1; } + + partial IEnumerable M(); } -]]> @@ -3421,70 +3227,45 @@ class Test Test(workspace) End Sub - - Public Sub TestQueryDegeneratedSelect() + Public Sub TestCSharpGoToOnYieldBreak_Yield() Dim workspace = - - - <%= GetExpressionPatternDefinition("") %> - - - - QueryPattern + - () - where true - $$select i1; + yield$$ break; } } -]]> - Test(workspace, False) + Test(workspace) End Sub - - Public Sub TestQueryLet() + Public Sub TestCSharpGoToOnYieldBreak_Break() Dim workspace = - - - <%= GetExpressionPatternDefinition("Select") %> - - - - QueryPattern + - () - $$let i2=1 - select new { i1, i2 }; + yield break$$; } } -]]> Test(workspace) End Sub -#End Region End Class End Namespace diff --git a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb new file mode 100644 index 0000000000000..d97d0b6a347f5 --- /dev/null +++ b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb @@ -0,0 +1,126 @@ +' 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.Threading +Imports Microsoft.CodeAnalysis.Editor.CSharp.GoToDefinition +Imports Microsoft.CodeAnalysis.Editor.Host +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Editor.VisualBasic.GoToDefinition +Imports Microsoft.CodeAnalysis.Navigation +Imports Microsoft.VisualStudio.Text + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition + Public Class GoToDefinitionTestsBase + Private Shared Sub Test( + workspaceDefinition As XElement, + expectedResult As Boolean, + executeOnDocument As Func(Of Document, Integer, IThreadingContext, IStreamingFindUsagesPresenter, Boolean)) + Using workspace = TestWorkspace.Create(workspaceDefinition, composition:=GoToTestHelpers.Composition) + Dim solution = workspace.CurrentSolution + Dim cursorDocument = workspace.Documents.First(Function(d) d.CursorPosition.HasValue) + Dim cursorPosition = cursorDocument.CursorPosition.Value + + ' Set up mocks. The IDocumentNavigationService should be called if there is one, + ' location and the INavigableItemsPresenter should be called if there are + ' multiple locations. + + ' prepare a notification listener + Dim textView = cursorDocument.GetTextView() + Dim textBuffer = textView.TextBuffer + textView.Caret.MoveTo(New SnapshotPoint(textBuffer.CurrentSnapshot, cursorPosition)) + + Dim cursorBuffer = cursorDocument.GetTextBuffer() + Dim document = workspace.CurrentSolution.GetDocument(cursorDocument.Id) + + Dim mockDocumentNavigationService = DirectCast(workspace.Services.GetService(Of IDocumentNavigationService)(), MockDocumentNavigationService) + Dim mockSymbolNavigationService = DirectCast(workspace.Services.GetService(Of ISymbolNavigationService)(), MockSymbolNavigationService) + + Dim presenterCalled As Boolean = False + Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)() + Dim presenter = New MockStreamingFindUsagesPresenter(Sub() presenterCalled = True) + Dim actualResult = executeOnDocument(document, cursorPosition, threadingContext, presenter) + + Assert.Equal(expectedResult, actualResult) + + Dim expectedLocations As New List(Of FilePathAndSpan) + + For Each testDocument In workspace.Documents + For Each selectedSpan In testDocument.SelectedSpans + expectedLocations.Add(New FilePathAndSpan(testDocument.FilePath, selectedSpan)) + Next + Next + + expectedLocations.Sort() + + Dim context = presenter.Context + If expectedResult Then + If expectedLocations.Count = 0 Then + ' if there is not expected locations, it means symbol navigation is used + Assert.True(mockSymbolNavigationService._triedNavigationToSymbol, "a navigation took place") + Assert.Null(mockDocumentNavigationService._documentId) + Assert.False(presenterCalled) + Else + Assert.False(mockSymbolNavigationService._triedNavigationToSymbol) + + If mockDocumentNavigationService._triedNavigationToSpan Then + Dim definitionDocument = workspace.GetTestDocument(mockDocumentNavigationService._documentId) + Assert.Single(definitionDocument.SelectedSpans) + Assert.Equal(definitionDocument.SelectedSpans.Single(), mockDocumentNavigationService._span) + + ' The INavigableItemsPresenter should not have been called + Assert.False(presenterCalled) + ElseIf mockDocumentNavigationService._triedNavigationToPosition Then + Dim definitionDocument = workspace.GetTestDocument(mockDocumentNavigationService._documentId) + Assert.Single(definitionDocument.SelectedSpans) + Dim expected = definitionDocument.SelectedSpans.Single() + Assert.True(expected.Length = 0) + Assert.Equal(expected.Start, mockDocumentNavigationService._position) + + ' The INavigableItemsPresenter should not have been called + Assert.False(presenterCalled) + Else + Assert.False(mockDocumentNavigationService._triedNavigationToLineAndOffset) + Assert.True(presenterCalled) + + Dim actualLocations As New List(Of FilePathAndSpan) + + Dim items = context.GetDefinitions() + + For Each location In items + For Each docSpan In location.SourceSpans + actualLocations.Add(New FilePathAndSpan(docSpan.Document.FilePath, docSpan.SourceSpan)) + Next + Next + + actualLocations.Sort() + Assert.Equal(expectedLocations, actualLocations) + + ' The IDocumentNavigationService should not have been called + Assert.Null(mockDocumentNavigationService._documentId) + End If + End If + Else + Assert.False(mockSymbolNavigationService._triedNavigationToSymbol) + Assert.Null(mockDocumentNavigationService._documentId) + Assert.False(presenterCalled) + End If + + End Using + End Sub + + Friend Shared Sub Test(workspaceDefinition As XElement, Optional expectedResult As Boolean = True) + Test(workspaceDefinition, expectedResult, + Function(document, cursorPosition, threadingContext, presenter) + Dim goToDefService = If(document.Project.Language = LanguageNames.CSharp, + DirectCast(New CSharpGoToDefinitionService(threadingContext, presenter), IGoToDefinitionService), + New VisualBasicGoToDefinitionService(threadingContext, presenter)) + + Return goToDefService.TryGoToDefinition(document, cursorPosition, CancellationToken.None) + End Function) + End Sub + + End Class +End Namespace diff --git a/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb new file mode 100644 index 0000000000000..aab33b5a34ef7 --- /dev/null +++ b/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb @@ -0,0 +1,1515 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition + <[UseExportProvider]> + Public Class VisualBasicGoToDefinitionTests + Inherits GoToDefinitionTestsBase +#Region "Normal Visual Basic Tests" + + + + Public Sub TestVisualBasicGoToDefinitionOnAnonymousMember() + Dim workspace = + + + +public class MyClass1 + public property [|Prop1|] as integer +end class +class Program + sub Main() + dim instance = new MyClass1() + + dim x as new With { instance.$$Prop1 } + end sub +end class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToDefinition() + Dim workspace = + + + + Class [|SomeClass|] + End Class + Class OtherClass + Dim obj As Some$$Class + End Class + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicLiteralGoToDefinition() + Dim workspace = + + + + Dim x as Integer = 12$$3 + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicStringLiteralGoToDefinition() + Dim workspace = + + + + Dim x as String = "wo$$ow" + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicPropertyBackingField() + Dim workspace = + + + +Class C + Property [|P|] As Integer + Sub M() + Me.$$_P = 10 + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToDefinitionSameClass() + Dim workspace = + + + + Class [|SomeClass|] + Dim obj As Some$$Class + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToDefinitionNestedClass() + Dim workspace = + + + + Class Outer + Class [|Inner|] + End Class + Dim obj as In$$ner + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGotoDefinitionDifferentFiles() + Dim workspace = + + + + Class OtherClass + Dim obj As SomeClass + End Class + + + Class OtherClass2 + Dim obj As Some$$Class + End Class + + + Class [|SomeClass|] + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGotoDefinitionPartialClasses() + Dim workspace = + + + + DummyClass + End Class + + + Partial Class [|OtherClass|] + Dim a As Integer + End Class + + + Partial Class [|OtherClass|] + Dim b As Integer + End Class + + + Class ConsumingClass + Dim obj As Other$$Class + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGotoDefinitionMethod() + Dim workspace = + + + + Class [|SomeClass|] + Dim x As Integer + End Class + + + Class ConsumingClass + Sub goo() + Dim obj As Some$$Class + End Sub + End Class + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicGotoDefinitionPartialMethod() + Dim workspace = + + + + Partial Class Customer + Private Sub [|OnNameChanged|]() + + End Sub + End Class + + + Partial Class Customer + Sub New() + Dim x As New Customer() + x.OnNameChanged$$() + End Sub + Partial Private Sub OnNameChanged() + + End Sub + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicTouchLeft() + Dim workspace = + + + + Class [|SomeClass|] + Dim x As Integer + End Class + + + Class ConsumingClass + Sub goo() + Dim obj As $$SomeClass + End Sub + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicTouchRight() + Dim workspace = + + + + Class [|SomeClass|] + Dim x As Integer + End Class + + + Class ConsumingClass + Sub goo() + Dim obj As SomeClass$$ + End Sub + End Class + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicMe() + Dim workspace = + + + +Class B + Sub New() + End Sub +End Class + +Class [|C|] + Inherits B + + Sub New() + MyBase.New() + MyClass.Goo() + $$Me.Bar() + End Sub + + Private Sub Bar() + End Sub + + Private Sub Goo() + End Sub +End Class + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicMyClass() + Dim workspace = + + + +Class B + Sub New() + End Sub +End Class + +Class [|C|] + Inherits B + + Sub New() + MyBase.New() + $$MyClass.Goo() + Me.Bar() + End Sub + + Private Sub Bar() + End Sub + + Private Sub Goo() + End Sub +End Class + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicMyBase() + Dim workspace = + + + +Class [|B|] + Sub New() + End Sub +End Class + +Class C + Inherits B + + Sub New() + $$MyBase.New() + MyClass.Goo() + Me.Bar() + End Sub + + Private Sub Bar() + End Sub + + Private Sub Goo() + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOverridenSubDefinition() + Dim workspace = + + + + Class Base + Overridable Sub [|Method|]() + End Sub + End Class + Class Derived + Inherits Base + + Overr$$ides Sub Method() + End Sub + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOverridenFunctionDefinition() + Dim workspace = + + + + Class Base + Overridable Function [|Method|]() As Integer + Return 1 + End Function + End Class + Class Derived + Inherits Base + + Overr$$ides Function Method() As Integer + Return 1 + End Function + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOverridenPropertyDefinition() + Dim workspace = + + + + Class Base + Overridable Property [|Number|] As Integer + End Class + Class Derived + Inherits Base + + Overr$$ides Property Number As Integer + End Class + + + + + Test(workspace) + End Sub +#End Region + +#Region "Venus Visual Basic Tests" + + + Public Sub TestVisualBasicVenusGotoDefinition() + Dim workspace = + + + + #ExternalSource ("Default.aspx", 1) + Class [|Program|] + Sub Main(args As String()) + Dim f As New Pro$$gram() + End Sub + End Class + #End ExternalSource + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicFilterGotoDefResultsFromHiddenCodeForUIPresenters() + Dim workspace = + + + + Class [|Program|] + Sub Main(args As String()) + #ExternalSource ("Default.aspx", 1) + Dim f As New Pro$$gram() + End Sub + End Class + #End ExternalSource + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicDoNotFilterGotoDefResultsFromHiddenCodeForApis() + Dim workspace = + + + + Class [|Program|] + Sub Main(args As String()) + #ExternalSource ("Default.aspx", 1) + Dim f As New Pro$$gram() + End Sub + End Class + #End ExternalSource + + + + + Test(workspace) + End Sub +#End Region + + + Public Sub TestVisualBasicTestThroughExecuteCommand() + Dim workspace = + + + + Class [|SomeClass|] + Dim x As Integer + End Class + + + Class ConsumingClass + Sub goo() + Dim obj As SomeClass$$ + End Sub + End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToDefinitionOnExtensionMethod() + Dim workspace = + + + + + Public Sub TestExt(Of T)(ex As T) + End Sub + + Public Sub [|TestExt|](ex As string) + End Sub +End Module]]>] + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicQueryRangeVariable() + Dim workspace = + + + +Imports System +Imports System.Collections.Generic +Imports System.Linq + +Module Program + Sub Main(args As String()) + Dim arr = New Integer() {4, 5} + Dim q3 = From [|num|] In arr Select $$num + End Sub +End Module + + + + + Test(workspace) + End Sub + + + + Public Sub TestVisualBasicGotoConstant() + Dim workspace = + + + +Module M + Sub Main() +label1: GoTo $$200 +[|200|]: GoTo label1 + End Sub +End Module + + + + + Test(workspace) + End Sub + + + + + Public Sub TestCrossLanguageParameterizedPropertyOverride() + Dim workspace = + + + +Public Class A + Public Overridable ReadOnly Property X(y As Integer) As Integer + [|Get|] + End Get + End Property +End Class + + + + VBProj + +class B : A +{ + public override int get_X(int y) + { + return base.$$get_X(y); + } +} + + + + + Test(workspace) + End Sub + + + + Public Sub TestCrossLanguageNavigationToVBModuleMember() + Dim workspace = + + + +Public Module A + Public Sub [|M|]() + End Sub +End Module + + + + VBProj + +class C +{ + static void N() + { + A.$$M(); + } +} + + + + + Test(workspace) + End Sub + +#Region "Show notification tests" + + + Public Sub TestShowNotificationVB() + Dim workspace = + + + + Class SomeClass + End Class + C$$lass OtherClass + Dim obj As SomeClass + End Class + + + + + Test(workspace, expectedResult:=False) + End Sub + + + + Public Sub TestGoToDefinitionOnInferredFieldInitializer() + Dim workspace = + + + +Public Class Class2 + Sub Test() + Dim var1 = New With {Key .var2 = "Bob", Class2.va$$r3} + End Sub + + Shared Property [|var3|]() As Integer + Get + End Get + Set(ByVal value As Integer) + End Set + End Property +End Class + + + + + + Test(workspace) + End Sub + + + + Public Sub TestGoToDefinitionGlobalImportAlias() + Dim workspace = + + + VBAssembly + + Goo = Importable.ImportMe + + +Public Class Class2 + Sub Test() + Dim x as Go$$o + End Sub +End Class + + + + + +Namespace Importable + Public Class [|ImportMe|] + End Class +End Namespace + + + + + Test(workspace) + End Sub +#End Region + + + Public Sub TestVisualBasicGoToOnExitSelect_Exit() + Dim workspace = + + + +Class C + Sub M(parameter As String) + Select Case parameter + Case "a" + Exit$$ Select + End Select[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitSelect_Select() + Dim workspace = + + + +Class C + Sub M(parameter As String) + Select Case parameter + Case "a" + Exit Select$$ + End Select[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitSub() + Dim workspace = + + + +Class C + Sub M() + Exit Sub$$ + End Sub[||] +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitFunction() + Dim workspace = + + + +Class C + Function M() As Integer + Exit Sub$$ + End Function[||] +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueWhile_Continue() + Dim workspace = + + + +Class C + Sub M() + [||]While True + Continue$$ While + End While + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueWhile_While() + Dim workspace = + + + +Class C + Sub M() + [||]While True + Continue While$$ + End While + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitWhile_While() + Dim workspace = + + + +Class C + Sub M() + While True + Exit While$$ + End While[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueFor_Continue() + Dim workspace = + + + +Class C + Sub M() + [||]For index As Integer = 1 To 5 + Continue$$ For + Next + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueFor_For() + Dim workspace = + + + +Class C + Sub M() + [||]For index As Integer = 1 To 5 + Continue For$$ + Next + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitFor_For() + Dim workspace = + + + +Class C + Sub M() + For index As Integer = 1 To 5 + Exit For$$ + Next[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueForEach_For() + Dim workspace = + + + +Class C + Sub M() + [||]For Each element In Nothing + Continue For$$ + Next + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitForEach_For() + Dim workspace = + + + +Class C + Sub M() + For Each element In Nothing + Exit For$$ + Next[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueDoWhileLoop_Do() + Dim workspace = + + + +Class C + Sub M() + [||]Do While True + Continue Do$$ + Loop + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitDoWhileLoop_Do() + Dim workspace = + + + +Class C + Sub M() + Do While True + Exit Do$$ + Loop[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueDoUntilLoop_Do() + Dim workspace = + + + +Class C + Sub M() + [||]Do Until True + Continue Do$$ + Loop + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitDoUntilLoop_Do() + Dim workspace = + + + +Class C + Sub M() + Do Until True + Exit Do$$ + Loop[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueDoLoopWhile_Do() + Dim workspace = + + + +Class C + Sub M() + [||]Do + Continue Do$$ + Loop While True + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnContinueDoLoopUntil_Do() + Dim workspace = + + + +Class C + Sub M() + [||]Do + Continue Do$$ + Loop Until True + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitTry() + Dim workspace = + + + +Class C + Sub M() + Try + Exit Try$$ + End Try[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitTryInCatch() + Dim workspace = + + + +Class C + Sub M() + Try + Catch Exception + Exit Try$$ + End Try[||] + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInSub() + Dim workspace = + + + +Class C + [||]Sub M() + Return$$ + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInSub_Partial() + Dim workspace = + + + +Class C + Partial Sub M() + End Sub + + [||]Partial Private Sub M() + Return$$ + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInSub_Partial_ReverseOrder() + Dim workspace = + + + +Class C + [||]Partial Private Sub M() + Return$$ + End Sub + + Partial Sub M() + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInSubLambda() + Dim workspace = + + + +Class C + Sub M() + Dim lambda = [||]Sub() + Return$$ + End Sub + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInFunction() + Dim workspace = + + + +Class C + [||]Function M() As Int + Return$$ 1 + End Function +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInFunction_OnValue() + Dim workspace = + + + +Class C + Function M([|x|] As Integer) As Integer + Return x$$ + End Function +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInIterator() + Dim workspace = + + + +Class C + [||]Public Iterator Function M() As IEnumerable(Of Integer) + Yield$$ 1 + End Function +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInIterator_OnValue() + Dim workspace = + + + +Class C + Public Iterator Function M([|x|] As Integer) As IEnumerable(Of Integer) + Yield x$$ + End Function +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInFunctionLambda() + Dim workspace = + + + +Class C + Sub M() + Dim lambda = [||]Function() As Int + Return$$ 1 + End Function + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInConstructor() + Dim workspace = + + + +Class C + [||]Sub New() + Return$$ + End Sub +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInOperator() + Dim workspace = + + + +Class C + [||]Public Shared Operator +(ByVal i As Integer) As Integer + Return$$ 1 + End Operator +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInGetAccessor() + Dim workspace = + + + +Class C + ReadOnly Property P() As Integer + [||]Get + Return$$ 1 + End Get + End Property +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInSetAccessor() + Dim workspace = + + + +Class C + ReadOnly Property P() As Integer + [||]Set + Return$$ + End Set + End Property +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitPropertyInGetAccessor() + Dim workspace = + + + +Class C + ReadOnly Property P() As Integer + [||]Get + Exit Property$$ + End Get + End Property +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnExitPropertyInSetAccessor() + Dim workspace = + + + +Class C + Property P() As Integer + [||]Set + Exit Property$$ + End Set + End Property +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInAddHandler() + Dim workspace = + + + +Class C + Public Custom Event Click As EventHandler + [||]AddHandler(ByVal value As EventHandler) + Return$$ + End AddHandler + End Event +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInRemoveHandler() + Dim workspace = + + + +Class C + Public Custom Event Click As EventHandler + [||]RemoveHandler(ByVal value As EventHandler) + Return$$ + End RemoveHandler + End Event +End Class + + + + + Test(workspace) + End Sub + + + Public Sub TestVisualBasicGoToOnReturnInRaiseEvent() + Dim workspace = + + + +Class C + Public Custom Event Click As EventHandler + [||]RaiseEvent(ByVal sender As Object, ByVal e As EventArgs) + Return$$ + End RaiseEvent + End Event +End Class + + + + + Test(workspace) + End Sub + + End Class +End Namespace diff --git a/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb b/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb index 90166dabc1ace..f1eca34f8ca03 100644 --- a/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb +++ b/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb @@ -24,7 +24,7 @@ Friend Class GoToHelpers Dim document = workspace.CurrentSolution.GetDocument(documentWithCursor.Id) - Dim context = New SimpleFindUsagesContext(CancellationToken.None) + Dim context = New SimpleFindUsagesContext() Await testingMethod(document, position, context) If Not shouldSucceed Then diff --git a/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb b/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb index 9e7dea3f36873..194417e33c655 100644 --- a/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb +++ b/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb @@ -4,6 +4,7 @@ Imports Microsoft.CodeAnalysis.Remote.Testing Imports Microsoft.CodeAnalysis.Editor.FindUsages +Imports System.Threading Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToImplementation <[UseExportProvider]> @@ -15,7 +16,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToImplementation host, Async Function(document As Document, position As Integer, context As SimpleFindUsagesContext) As Task Dim findUsagesService = document.GetLanguageService(Of IFindUsagesService) - Await findUsagesService.FindImplementationsAsync(document, position, context).ConfigureAwait(False) + Await findUsagesService.FindImplementationsAsync(document, position, context, CancellationToken.None).ConfigureAwait(False) End Function, shouldSucceed) End Function @@ -373,7 +374,7 @@ interface I { void $$M(); } -class C : I { public abstract void [|M|]() { } } +class C : I { public abstract void M() { } } class D : C { public override void [|M|]() { } }} interface I { void $$M(); } @@ -480,7 +481,7 @@ class D : C public virtual void $$[|M|]() { } } abstract class B : A { - public abstract override void [|M|](); + public abstract override void M(); } sealed class C1 : B { public override void [|M|]() { } @@ -667,5 +668,38 @@ public class StringCreator : IStringCreator Await TestAsync(workspace, host) End Function + + + + Public Async Function SkipIntermediaryAbstractMethodIfOverridden(host As TestHost) As Task + Dim workspace = + + + +class C : I { public abstract void M(); } +class D : C { public override void [|M|]() { } } +interface I { void $$M(); } + + + + + Await TestAsync(workspace, host) + End Function + + + + Public Async Function IncludeAbstractMethodIfNotOverridden(host As TestHost) As Task + Dim workspace = + + + +class C : I { public abstract void [|M|](); } +interface I { void $$M(); } + + + + + Await TestAsync(workspace, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 75a7d0edb2366..5ee6911ca8b23 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -9,7 +9,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.CSharp -Imports Microsoft.CodeAnalysis.Editor.CSharp.Formatting +Imports Microsoft.CodeAnalysis.CSharp.Formatting Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Host.Mef @@ -181,6 +181,35 @@ public static class Test End Using End Function + + + Public Async Function CompletionOnWithExpressionInitializer_AnonymousType(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + +class C +{ + void M() + { + var a = new { Property = 1 }; + _ = a $$ + } +} + , + showCompletionInArgumentLists:=showCompletionInArgumentLists, languageVersion:=LanguageVersion.Preview) + + state.SendTypeChars("w") + Await state.AssertSelectedCompletionItem(displayText:="with", isHardSelected:=False) + state.SendTab() + state.SendTypeChars(" { ") + Await state.AssertSelectedCompletionItem(displayText:="Property", isHardSelected:=False) + state.SendTypeChars("P") + Await state.AssertSelectedCompletionItem(displayText:="Property", isHardSelected:=True) + state.SendTypeChars(" = 2") + Await state.AssertNoCompletionSession() + Assert.Contains("with { Property = 2", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + End Using + End Function + @@ -2079,7 +2108,7 @@ class Program $$ , - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("using Sys") @@ -3667,7 +3696,7 @@ class C { int doodle; $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("doo;") state.AssertMatchesTextStartingAtLine(6, " doodle;") @@ -3703,7 +3732,7 @@ class C int doodle; } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) Dim textBufferFactoryService = state.GetExportedValue(Of ITextBufferFactoryService)() @@ -3743,7 +3772,7 @@ class C void goo(int x) { string.$$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("is") Await state.AssertSelectedCompletionItem("IsInterned") @@ -3764,7 +3793,7 @@ class C void goo(int x) { string.$$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("ı") Await state.AssertSelectedCompletionItem() @@ -3786,7 +3815,7 @@ class C void goo(int x) { var t = new $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("tarif") Await state.WaitForAsynchronousOperationsAsync() @@ -3811,7 +3840,7 @@ class C { IFADE ifade = null; $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("if") Await state.WaitForAsynchronousOperationsAsync() @@ -3836,7 +3865,7 @@ class C { İFADE ifade = null; $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("if") Await state.WaitForAsynchronousOperationsAsync() @@ -3859,7 +3888,7 @@ class C void goo(int x) { var obj = new $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("tarif") Await state.WaitForAsynchronousOperationsAsync() @@ -3883,7 +3912,7 @@ class C void goo(int x) { var obj = new $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("ifad") Await state.WaitForAsynchronousOperationsAsync() @@ -3907,7 +3936,7 @@ class C void goo(int x) { var obj = new $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("ifad") Await state.WaitForAsynchronousOperationsAsync() @@ -3932,7 +3961,7 @@ class C { IFADE ifade = null; $$]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("IF") Await state.WaitForAsynchronousOperationsAsync() @@ -3956,7 +3985,7 @@ class C void goo(int x) { İFADE ifade = null; - $$]]>, extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + $$]]>, extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("IF") Await state.WaitForAsynchronousOperationsAsync() @@ -3979,7 +4008,7 @@ class Program Cancel(x + 1, cancellationToken: $$) } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem("cancellationToken", isHardSelected:=True).ConfigureAwait(True) @@ -3999,7 +4028,7 @@ class Program args = $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("a") Await state.AssertSelectedCompletionItem("args", isHardSelected:=True).ConfigureAwait(True) @@ -4024,7 +4053,7 @@ class Program e = $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem("E", isHardSelected:=True).ConfigureAwait(True) @@ -4049,7 +4078,7 @@ class Program if (e == $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem("E", isHardSelected:=True).ConfigureAwait(True) @@ -4072,7 +4101,7 @@ class Program D cx2 = $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("c") Await state.AssertSelectedCompletionItem("cx", isHardSelected:=True).ConfigureAwait(True) @@ -4094,7 +4123,7 @@ class Program A cx2 = $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("c") Await state.AssertSelectedCompletionItem("cx", isHardSelected:=True).ConfigureAwait(True) @@ -4117,7 +4146,7 @@ class Program goo($$) // Not "Equals" } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem("f", isHardSelected:=True).ConfigureAwait(True) @@ -4139,7 +4168,7 @@ class Program C cx2 = $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("c") Await state.AssertSelectedCompletionItem("cx", isHardSelected:=True).ConfigureAwait(True) @@ -4162,7 +4191,7 @@ class Program int y = a$$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem("aaq", isHardSelected:=True).ConfigureAwait(True) @@ -4184,7 +4213,7 @@ class Program new[] { new { x = 1 } }.ToArr$$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() @@ -4209,7 +4238,7 @@ class Program } } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem("value", isHardSelected:=True).ConfigureAwait(True) @@ -4231,7 +4260,7 @@ class Program new[] { new { x = 1 } }.ToArr$$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() Await state.AssertSelectedCompletionItem(description:= @@ -4256,7 +4285,7 @@ class Program $$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendTypeChars("list") @@ -4282,7 +4311,7 @@ class Program Main(args$$ } }]]>, - extraExportedTypes:={GetType(CSharpEditorFormattingService)}.ToList(), + extraExportedTypes:={GetType(CSharpFormattingInteractionService)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) state.SendInvokeCompletionList() diff --git a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb index b03efe20b2bda..ff0b631b560b1 100644 --- a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb @@ -1158,5 +1158,140 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense ToolTipAssert.EqualContent(expected, container) End Function + + + + Public Async Function QuickInfoForUnderlyingEnumTypes() As Task + Dim workspace = + + + + public enum E$$ : byte { A, B } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + Assert.NotNull(intellisenseQuickInfo) + + Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.EnumerationPublic)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "enum"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.EnumName, "E", navigationAction:=Sub() Return, "E"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ":"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "byte", navigationAction:=Sub() Return, "byte")))) + ToolTipAssert.EqualContent(expected, container) + End Function + + + Public Async Function QuickInfoForRecordClass() As Task + Dim workspace = + + + + public sealed record class TestRecord(int X, int Y) { } + + class C + { + void M() + { + var x = new Test$$Record(1, 2); + } + } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + Assert.NotNull(intellisenseQuickInfo) + + Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPublic)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.RecordClassName, "TestRecord", navigationAction:=Sub() Return, "TestRecord"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."), + New ClassifiedTextRun(ClassificationTypeNames.RecordClassName, "TestRecord", navigationAction:=Sub() Return, "TestRecord.TestRecord(int X, int Y)"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "X", navigationAction:=Sub() Return, "int X"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ","), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "Y", navigationAction:=Sub() Return, "int Y"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")")))) + + ToolTipAssert.EqualContent(expected, container) + End Function + + + Public Async Function QuickInfoForRecordStructs() As Task + Dim workspace = + + + + public sealed record struct TestRecord(int X, int Y) { } + + class C + { + void M() + { + var x = new Test$$Record(1, 2); + } + } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + Assert.NotNull(intellisenseQuickInfo) + + Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPublic)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.RecordStructName, "TestRecord", navigationAction:=Sub() Return, "TestRecord"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."), + New ClassifiedTextRun(ClassificationTypeNames.RecordStructName, "TestRecord", navigationAction:=Sub() Return, "TestRecord.TestRecord(int X, int Y)"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "X", navigationAction:=Sub() Return, "int X"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ","), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "Y", navigationAction:=Sub() Return, "int Y"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "+"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Text, "1"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Text, FeaturesResources.overload), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")")))) + + ToolTipAssert.EqualContent(expected, container) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb b/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb index 06e6d5cc3e330..fc4536d45f85a 100644 --- a/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb +++ b/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb @@ -34,7 +34,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.KeywordHighlighting Dim tagProducer = New HighlighterViewTaggerProvider( workspace.ExportProvider.GetExportedValue(Of IThreadingContext), highlightingService, - workspace.GetService(Of IForegroundNotificationService), AsynchronousOperationListenerProvider.NullProvider) Dim context = New TaggerContext(Of KeywordHighlightTag)(document, snapshot, New SnapshotPoint(snapshot, caretPosition)) diff --git a/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb b/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb index 0979bd7ceec42..15828c2dd18a1 100644 --- a/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb +++ b/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb @@ -15,6 +15,7 @@ Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.Language.Intellisense Imports Microsoft.VisualStudio.Text +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigableSymbols @@ -117,7 +118,7 @@ End Class" Private Shared Function ExtractSymbol(workspace As TestWorkspace, position As Integer) As Task(Of INavigableSymbol) Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)() Dim presenter = New MockStreamingFindUsagesPresenter(Sub() Return) - Dim service = New NavigableSymbolService(TestWaitIndicator.Default, threadingContext, presenter) + Dim service = New NavigableSymbolService(workspace.ExportProvider.GetExportedValue(Of IUIThreadOperationExecutor)(), threadingContext, presenter) Dim view = workspace.Documents.First().GetTextView() Dim buffer = workspace.Documents.First().GetTextBuffer() Dim triggerSpan = New SnapshotSpan(buffer.CurrentSnapshot, New Span(position, 0)) diff --git a/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb b/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb index 237167c4a5cf3..160317af06e3a 100644 --- a/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb +++ b/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb @@ -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. +Imports System.Collections.Immutable Imports Microsoft.VisualStudio.Text.Editor Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar @@ -35,11 +36,12 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar End Sub - Public Sub PresentItems(projects As IList(Of NavigationBarProjectItem), - selectedProject As NavigationBarProjectItem, - typesWithMembers As IList(Of NavigationBarItem), - selectedType As NavigationBarItem, - selectedMember As NavigationBarItem) Implements INavigationBarPresenter.PresentItems + Public Sub PresentItems( + projects As ImmutableArray(Of NavigationBarProjectItem), + selectedProject As NavigationBarProjectItem, + typesWithMembers As ImmutableArray(Of NavigationBarItem), + selectedType As NavigationBarItem, + selectedMember As NavigationBarItem) Implements INavigationBarPresenter.PresentItems If _presentItemsCallback IsNot Nothing Then _presentItemsCallback() End If diff --git a/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb b/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb index d5c649aac4aef..cfb2f43dd403a 100644 --- a/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb +++ b/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb @@ -75,7 +75,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar End Function - Public Sub TestNavigationBarInCSharpLinkedFiles() + Public Async Function TestNavigationBarInCSharpLinkedFiles() As Task Using workspace = TestWorkspace.Create( @@ -117,6 +117,14 @@ class C Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, baseDocument.GetTextBuffer()) + controller.SetWorkspace(workspace) + + Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider).Value + Dim workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) + Dim navbarWaiter = listenerProvider.GetWaiter(FeatureAttribute.NavigationBar) + + Await navbarWaiter.ExpeditedWaitAsync() + memberName = Nothing mockPresenter.RaiseDropDownFocused() Assert.Equal("M1(int x)", memberName) @@ -124,15 +132,18 @@ class C workspace.SetDocumentContext(linkDocument.Id) + Await workspaceWaiter.ExpeditedWaitAsync() + Await navbarWaiter.ExpeditedWaitAsync() + memberName = Nothing mockPresenter.RaiseDropDownFocused() Assert.Equal("M2(int x)", memberName) Assert.Equal(projectGlyph, Glyph.CSharpProject) End Using - End Sub + End Function - Public Sub TestNavigationBarInVisualBasicLinkedFiles() + Public Async Function TestNavigationBarInVisualBasicLinkedFiles() As Task Using workspace = TestWorkspace.Create( @@ -175,6 +186,14 @@ End Class Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, baseDocument.GetTextBuffer()) + controller.SetWorkspace(workspace) + + Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider).Value + Dim workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) + Dim navbarWaiter = listenerProvider.GetWaiter(FeatureAttribute.NavigationBar) + + Await navbarWaiter.ExpeditedWaitAsync() + memberNames = Nothing mockPresenter.RaiseDropDownFocused() Assert.Contains("M1", memberNames) @@ -183,13 +202,16 @@ End Class workspace.SetDocumentContext(linkDocument.Id) + Await workspaceWaiter.ExpeditedWaitAsync() + Await navbarWaiter.ExpeditedWaitAsync() + memberNames = Nothing mockPresenter.RaiseDropDownFocused() Assert.Contains("M2", memberNames) Assert.DoesNotContain("M1", memberNames) Assert.Equal(projectGlyph, Glyph.BasicProject) End Using - End Sub + End Function Public Sub TestProjectItemsAreSortedCSharp() diff --git a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb index 9d970e3e0e006..c408f4ca61337 100644 --- a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb +++ b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb @@ -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. +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar @@ -37,8 +38,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar Dim snapshot = (Await document.GetTextAsync()).FindCorrespondingEditorTextSnapshot() Dim service = document.GetLanguageService(Of INavigationBarItemService)() - Dim actualItems = Await service.GetItemsAsync(document, Nothing) - actualItems.Do(Sub(i) i.InitializeTrackingSpans(snapshot)) + Dim actualItems = Await service.GetItemsAsync(document, snapshot, Nothing) AssertEqual(expectedItems, actualItems, document.GetLanguageService(Of ISyntaxFactsService)().IsCaseSensitive) End Using @@ -56,11 +56,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar Dim snapshot = (Await document.GetTextAsync()).FindCorrespondingEditorTextSnapshot() Dim service = document.GetLanguageService(Of INavigationBarItemService)() - Dim items = Await service.GetItemsAsync(document, Nothing) - items.Do(Sub(i) i.InitializeTrackingSpans(snapshot)) + Dim items = Await service.GetItemsAsync(document, snapshot, Nothing) Dim hostDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim model As New NavigationBarModel(items, VersionStamp.Create(), service) + Dim model As New NavigationBarModel(items.ToImmutableArray(), VersionStamp.Create(), service) Dim selectedItems = NavigationBarController.ComputeSelectedTypeAndMember(model, New SnapshotPoint(hostDocument.GetTextBuffer().CurrentSnapshot, hostDocument.CursorPosition.Value), Nothing) Dim isCaseSensitive = document.GetLanguageService(Of ISyntaxFactsService)().IsCaseSensitive @@ -90,8 +89,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar Dim service = document.GetLanguageService(Of INavigationBarItemService)() - Dim items = Await service.GetItemsAsync(document, Nothing) - items.Do(Sub(i) i.InitializeTrackingSpans(snapshot)) + Dim items = Await service.GetItemsAsync(document, snapshot, Nothing) Dim leftItem = items.Single(Function(i) i.Text = leftItemToSelectText) Dim rightItem = selectRightItem(leftItem.ChildItems) @@ -119,22 +117,24 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar Dim snapshot = (Await sourceDocument.GetTextAsync()).FindCorrespondingEditorTextSnapshot() Dim service = DirectCast(sourceDocument.GetLanguageService(Of INavigationBarItemService)(), AbstractEditorNavigationBarItemService) - Dim items = Await service.GetItemsAsync(sourceDocument, Nothing) - items.Do(Sub(i) i.InitializeTrackingSpans(snapshot)) + Dim items = Await service.GetItemsAsync(sourceDocument, snapshot, Nothing) Dim leftItem = items.Single(Function(i) i.Text = leftItemToSelectText) Dim rightItem = leftItem.ChildItems.Single(Function(i) i.Text = rightItemToSelectText) - Dim navigationPoint = service.GetSymbolItemNavigationPoint( - sourceDocument, DirectCast(DirectCast(rightItem, WrappedNavigationBarItem).UnderlyingItem, RoslynNavigationBarItem.SymbolItem), - CancellationToken.None).Value + Dim navigationPoint = Await service.GetNavigationLocationAsync( + sourceDocument, + rightItem, + DirectCast(DirectCast(rightItem, WrappedNavigationBarItem).UnderlyingItem, RoslynNavigationBarItem.SymbolItem), + snapshot, + cancellationToken:=Nothing) Dim expectedNavigationDocument = workspace.Documents.Single(Function(doc) doc.CursorPosition.HasValue) - Assert.Equal(expectedNavigationDocument.FilePath, navigationPoint.Tree.FilePath) + Assert.Equal(expectedNavigationDocument.Id, navigationPoint.documentId) Dim expectedNavigationPosition = expectedNavigationDocument.CursorPosition.Value - Assert.Equal(expectedNavigationPosition, navigationPoint.Position) - Assert.Equal(expectedVirtualSpace, navigationPoint.VirtualSpaces) + Assert.Equal(expectedNavigationPosition, navigationPoint.position) + Assert.Equal(expectedVirtualSpace, navigationPoint.virtualSpace) End Using End Function @@ -147,33 +147,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar AssertEqual(expectedItem, actualItem, isCaseSensitive) Next - - ' Ensure all the actual items that have navigation are distinct - Dim navigableItems = actualItems.Select(Function(i) DirectCast(i, WrappedNavigationBarItem).UnderlyingItem). - OfType(Of RoslynNavigationBarItem.SymbolItem). - ToList() - - Assert.True(navigableItems.Count() = navigableItems.Distinct(New NavigationBarItemNavigationSymbolComparer(isCaseSensitive)).Count(), "The items were not unique by SymbolID and index.") End Sub - Private Class NavigationBarItemNavigationSymbolComparer - Implements IEqualityComparer(Of RoslynNavigationBarItem.SymbolItem) - - Private ReadOnly _symbolIdComparer As IEqualityComparer(Of SymbolKey) - - Public Sub New(ignoreCase As Boolean) - _symbolIdComparer = If(ignoreCase, SymbolKey.GetComparer(ignoreCase:=True, ignoreAssemblyKeys:=False), SymbolKey.GetComparer(ignoreCase:=False, ignoreAssemblyKeys:=False)) - End Sub - - Public Function IEqualityComparer_Equals(x As RoslynNavigationBarItem.SymbolItem, y As RoslynNavigationBarItem.SymbolItem) As Boolean Implements IEqualityComparer(Of RoslynNavigationBarItem.SymbolItem).Equals - Return _symbolIdComparer.Equals(x.NavigationSymbolId, y.NavigationSymbolId) AndAlso x.NavigationSymbolIndex = y.NavigationSymbolIndex - End Function - - Public Function IEqualityComparer_GetHashCode(obj As RoslynNavigationBarItem.SymbolItem) As Integer Implements IEqualityComparer(Of RoslynNavigationBarItem.SymbolItem).GetHashCode - Return _symbolIdComparer.GetHashCode(obj.NavigationSymbolId) Xor obj.NavigationSymbolIndex - End Function - End Class - Private Sub AssertEqual(expectedItem As ExpectedItem, actualItem As NavigationBarItem, isCaseSensitive As Boolean) If expectedItem Is Nothing AndAlso actualItem Is Nothing Then Return diff --git a/src/EditorFeatures/Test2/Peek/PeekTests.vb b/src/EditorFeatures/Test2/Peek/PeekTests.vb index d2234a8e616d0..e8155757d9549 100644 --- a/src/EditorFeatures/Test2/Peek/PeekTests.vb +++ b/src/EditorFeatures/Test2/Peek/PeekTests.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.VisualStudio.Imaging.Interop Imports Microsoft.VisualStudio.Language.Intellisense Imports Microsoft.VisualStudio.Text +Imports Microsoft.VisualStudio.Utilities Imports Moq Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Peek @@ -206,7 +207,7 @@ End Module Dim peekableItemSource As New PeekableItemSource(textBuffer, workspace.GetService(Of IPeekableItemFactory), New MockPeekResultFactory(workspace.GetService(Of IPersistentSpanFactory)), - workspace.GetService(Of IWaitIndicator)) + workspace.GetService(Of IUIThreadOperationExecutor)) Dim peekableSession As New Mock(Of IPeekSession)(MockBehavior.Strict) Dim triggerPoint = New SnapshotPoint(document.GetTextBuffer().CurrentSnapshot, document.CursorPosition.Value) diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb index 56e96e5dd81d6..c2a2fec97b5a0 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb @@ -2,7 +2,6 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.ReferenceHighlighting Imports Microsoft.CodeAnalysis.Editor.Shared.Extensions Imports Microsoft.CodeAnalysis.Editor.Shared.Options @@ -12,7 +11,6 @@ Imports Microsoft.CodeAnalysis.Editor.Tagging Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Remote.Testing Imports Microsoft.CodeAnalysis.Shared.TestHooks -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Text Imports Roslyn.Utilities @@ -29,7 +27,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReferenceHighlighting Dim tagProducer = New ReferenceHighlightingViewTaggerProvider( workspace.ExportProvider.GetExportedValue(Of IThreadingContext), - workspace.GetService(Of IForegroundNotificationService), AsynchronousOperationListenerProvider.NullProvider) Dim hostDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb index fdc0f82d615e8..6c94879c8aa9a 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb @@ -808,5 +808,55 @@ class C Await VerifyHighlightsAsync(input, testHost) End Function + + + + Public Async Function TestNotOnNewInObjectCreation(testHost As TestHost) As Task + Dim input = + + + +namespace X +{ + class B + { + public void M() + { + $$new B(); + new B(); + } + } +} + + + + + Await VerifyHighlightsAsync(input, testHost) + End Function + + + + Public Async Function TestOnTypeInObjectCreation(testHost As TestHost) As Task + Dim input = + + + +namespace X +{ + class {|Definition:B|} + { + public void M() + { + new $${|Reference:B|}(); + new {|Reference:B|}(); + } + } +} + + + + + Await VerifyHighlightsAsync(input, testHost) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb index b0e3c1c56987c..429f4425b3234 100644 --- a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb +++ b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb @@ -32,5 +32,40 @@ public class GeneratedClass End Using End Sub + + + + Public Sub RenameWithCascadeIntoGeneratedFile(host As RenameTestHost) + Using result = RenameEngineResult.Create(_outputHelper, + + + +public interface IInterface +{ + int [|$$Property|] { get; set; } +} + +public partial class GeneratedClass : IInterface { } + + + , host:=host, renameTo:="A", sourceGenerator:=New GeneratorThatImplementsInterfaceMethod()) + + End Using + End Sub + + Private Class GeneratorThatImplementsInterfaceMethod + Implements ISourceGenerator + + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + Dim [interface] = context.Compilation.GetTypeByMetadataName("IInterface") + Dim memberName = [interface].MemberNames.Single() + + Dim text = "public partial class GeneratedClass { public int " + memberName + " { get; set; } }" + context.AddSource("Implementation.cs", text) + End Sub + End Class End Class End Namespace diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index fc0aa14ccf3d0..260a14bb4a1bf 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -51,10 +51,22 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename renameTo As String, host As RenameTestHost, Optional changedOptionSet As Dictionary(Of OptionKey, Object) = Nothing, - Optional expectFailure As Boolean = False) As RenameEngineResult - Dim workspace = TestWorkspace.CreateWorkspace(workspaceXml, composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(NoCompilationContentTypeLanguageService), GetType(NoCompilationContentTypeDefinitions))) + Optional expectFailure As Boolean = False, + Optional sourceGenerator As ISourceGenerator = Nothing) As RenameEngineResult + + Dim composition = EditorTestCompositions.EditorFeatures.AddParts(GetType(NoCompilationContentTypeLanguageService), GetType(NoCompilationContentTypeDefinitions)) + + If host = RenameTestHost.OutOfProcess_SingleCall OrElse host = RenameTestHost.OutOfProcess_SplitCall Then + composition = composition.WithTestHostParts(Remote.Testing.TestHost.OutOfProcess) + End If + + Dim workspace = TestWorkspace.CreateWorkspace(workspaceXml, composition:=composition) workspace.SetTestLogger(AddressOf helper.WriteLine) + If sourceGenerator IsNot Nothing Then + workspace.OnAnalyzerReferenceAdded(workspace.CurrentSolution.ProjectIds.Single(), New TestGeneratorReference(sourceGenerator)) + End If + Dim engineResult As RenameEngineResult = Nothing Try If workspace.Documents.Where(Function(d) d.CursorPosition.HasValue).Count <> 1 Then diff --git a/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs b/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs index ad740c69167ea..72fec43873ec8 100644 --- a/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs +++ b/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs @@ -43,7 +43,6 @@ protected async Task TestBraceHighlightingAsync( var provider = new BraceHighlightingViewTaggerProvider( workspace.GetService(), GetBraceMatchingService(workspace), - workspace.GetService(), AsynchronousOperationListenerProvider.NullProvider); var testDocument = workspace.Documents.First(); diff --git a/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs b/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs index 63acd0d915a6c..587f144698837 100644 --- a/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs +++ b/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs @@ -246,21 +246,19 @@ protected static async Task> GetSemanticClassific { var service = document.GetRequiredLanguageService(); - var result = new List(); + using var _ = ArrayBuilder.GetInstance(out var result); await service.AddSemanticClassificationsAsync(document, span, result, CancellationToken.None); - return result.ToImmutableArray(); + return result.ToImmutable(); } protected static async Task> GetSyntacticClassificationsAsync(Document document, TextSpan span) { - var tree = await document.GetSyntaxTreeAsync(); - + var root = await document.GetSyntaxRootAsync(); var service = document.GetLanguageService(); - var results = ArrayBuilder.GetInstance(); - - service.AddSyntacticClassifications(tree, span, results, CancellationToken.None); - return results.ToImmutableAndFree(); + using var _ = ArrayBuilder.GetInstance(out var results); + service.AddSyntacticClassifications(root, span, results, CancellationToken.None); + return results.ToImmutable(); } protected static async Task> GetAllClassificationsAsync(Document document, TextSpan span) diff --git a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs index a1488330e57be..0a05fc8cdad27 100644 --- a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs +++ b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs @@ -34,6 +34,10 @@ public static FormattedClassification Class(string text) public static FormattedClassification Record(string text) => New(text, ClassificationTypeNames.RecordClassName); + [DebuggerStepThrough] + public static FormattedClassification RecordStruct(string text) + => New(text, ClassificationTypeNames.RecordStructName); + [DebuggerStepThrough] public static FormattedClassification Delegate(string text) => New(text, ClassificationTypeNames.DelegateName); diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index dafd58921fba1..1e28d3b35171f 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -502,7 +502,7 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document } else { - await VerifyCustomCommitWorkerAsync(service, document, firstItem, completionList.Span, codeBeforeCommit, expectedCodeAfterCommit, commitChar); + await VerifyCustomCommitWorkerAsync(service, document, firstItem, codeBeforeCommit, expectedCodeAfterCommit, commitChar); } } @@ -527,7 +527,6 @@ private async Task VerifyCustomCommitWorkerAsync( CompletionServiceWithProviders service, Document document, RoslynCompletion.CompletionItem completionItem, - TextSpan completionListSpan, string codeBeforeCommit, string expectedCodeAfterCommit, char? commitChar = null) @@ -548,9 +547,8 @@ private async Task VerifyCustomCommitWorkerAsync( var textView = workspaceFixture.Target.CurrentDocument.GetTextView(); var options = await document.GetOptionsAsync().ConfigureAwait(false); - var disallowAddingImports = options.GetOption(CompletionServiceOptions.DisallowAddingImports); - var commit = await service.GetChangeAsync(document, completionItem, completionListSpan, commitChar, disallowAddingImports, CancellationToken.None); + var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); var text = await document.GetTextAsync(); var newText = text.WithChanges(commit.TextChange); @@ -637,7 +635,7 @@ private async Task VerifyProviderCommitCheckResultsAsync( if (commitChar == '\t' || CommitManager.IsCommitCharacter(service.GetRules(), firstItem, commitChar)) { - var textChange = (await service.GetChangeAsync(document, firstItem, completionList.Span, commitChar, disallowAddingImports: false, CancellationToken.None)).TextChange; + var textChange = (await service.GetChangeAsync(document, firstItem, commitChar, CancellationToken.None)).TextChange; // Adjust TextChange to include commit character, so long as it isn't TAB. if (commitChar != '\t') diff --git a/src/EditorFeatures/Test/EditAndContinue/Helpers/ActiveStatementTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs similarity index 58% rename from src/EditorFeatures/Test/EditAndContinue/Helpers/ActiveStatementTestHelpers.cs rename to src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs index ffbe0170f526e..0f99facf7e053 100644 --- a/src/EditorFeatures/Test/EditAndContinue/Helpers/ActiveStatementTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs @@ -6,71 +6,36 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; -using Microsoft.CodeAnalysis.Test.Utilities; -using System.IO; +using Microsoft.CodeAnalysis.CSharp; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { internal static class ActiveStatementTestHelpers { - internal static ImmutableArray GetActiveStatementDebugInfos( + public static ImmutableArray GetActiveStatementDebugInfosCSharp( string[] markedSources, - string extension = ".cs", + string[]? filePaths = null, int[]? methodRowIds = null, Guid[]? modules = null, int[]? methodVersions = null, int[]? ilOffsets = null, ActiveStatementFlags[]? flags = null) { - IEnumerable<(TextSpan Span, int Id, SourceText Text, string DocumentName, DocumentId DocumentId)> EnumerateAllSpans() - { - var sourceIndex = 0; - foreach (var markedSource in markedSources) - { - var documentName = Path.Combine(TempRoot.Root, TestWorkspace.GetDefaultTestSourceDocumentName(sourceIndex, extension)); - var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId(), documentName); - var text = SourceText.From(markedSource); - - foreach (var (span, id) in ActiveStatementsDescription.GetActiveSpans(markedSource)) - { - yield return (span, id, text, documentName, documentId); - } - - sourceIndex++; - } - } - - IEnumerable Enumerate() - { - var moduleId = new Guid("00000000-0000-0000-0000-000000000001"); - var threadId = new Guid("00000000-0000-0000-0000-000000000010"); + return ActiveStatementsDescription.GetActiveStatementDebugInfos( + (source, path) => SyntaxFactory.ParseSyntaxTree(source, path: path), + markedSources, + filePaths, + extension: ".cs", + methodRowIds, + modules, + methodVersions, + ilOffsets, + flags); - var index = 0; - foreach (var (span, id, text, documentName, documentId) in EnumerateAllSpans().OrderBy(s => s.Id)) - { - yield return new ManagedActiveStatementDebugInfo( - new ManagedInstructionId( - new ManagedMethodId( - (modules != null) ? modules[index] : moduleId, - new ManagedModuleMethodId( - token: 0x06000000 | (methodRowIds != null ? methodRowIds[index] : index + 1), - version: (methodVersions != null) ? methodVersions[index] : 1)), - ilOffset: (ilOffsets != null) ? ilOffsets[index] : 0), - documentName: documentName, - sourceSpan: text.Lines.GetLinePositionSpan(span).ToSourceSpan(), - flags: (flags != null) ? flags[index] : ((id == 0 ? ActiveStatementFlags.IsLeafFrame : ActiveStatementFlags.IsNonLeafFrame) | ActiveStatementFlags.MethodUpToDate)); - - index++; - } - } - - return Enumerate().ToImmutableArray(); } - public static string Delete(string src, string marker) { while (true) @@ -118,7 +83,7 @@ public static string Update(string src, string marker) => InsertNewLines(Delete(src, marker), marker); public static string InspectActiveStatement(ActiveStatement statement) - => $"{statement.Ordinal}: {statement.Span} flags=[{statement.Flags}] pdid={statement.PrimaryDocumentId.DebugName} docs=[{string.Join(",", statement.DocumentIds.Select(d => d.DebugName))}]"; + => $"{statement.Ordinal}: {statement.FileSpan} flags=[{statement.Flags}]"; public static string InspectActiveStatementAndInstruction(ActiveStatement statement) => InspectActiveStatement(statement) + " " + statement.InstructionId.GetDebuggerDisplay(); @@ -137,5 +102,12 @@ public static string InspectExceptionRegionUpdate(ManagedExceptionRegionUpdate r public static string GetFirstLineText(LinePositionSpan span, SourceText text) => text.Lines[span.Start.Line].ToString().Trim(); + + public static string InspectSequencePointUpdates(SequencePointUpdates updates) + => $"{updates.FileName}: [{string.Join(", ", updates.LineUpdates.Select(u => $"{u.OldLine} -> {u.NewLine}"))}]"; + + public static IEnumerable Inspect(this IEnumerable updates) + => updates.Select(InspectSequencePointUpdates); + } } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs index 9d56beab861eb..f329fc8af81e2 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs @@ -5,8 +5,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; @@ -16,45 +20,156 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { internal class ActiveStatementsDescription { - public readonly ActiveStatement[] OldStatements; - public readonly TextSpan[] NewSpans; - public readonly ImmutableArray[] OldRegions; - public readonly ImmutableArray[] NewRegions; - public readonly TextSpan[]? OldTrackingSpans; + internal static readonly ActiveStatementsDescription Empty = new(); + + public readonly ImmutableArray OldStatements; + public readonly ActiveStatementsMap OldStatementsMap; + public readonly ImmutableArray NewMappedSpans; + public readonly ImmutableArray> NewMappedRegions; + public readonly ImmutableArray OldUnmappedTrackingSpans; private ActiveStatementsDescription() { - OldStatements = Array.Empty(); - NewSpans = Array.Empty(); - OldRegions = Array.Empty>(); - NewRegions = Array.Empty>(); - OldTrackingSpans = null; + OldStatements = ImmutableArray.Empty; + NewMappedSpans = ImmutableArray.Empty; + OldStatementsMap = ActiveStatementsMap.Empty; + NewMappedRegions = ImmutableArray>.Empty; + OldUnmappedTrackingSpans = ImmutableArray.Empty; } - private static readonly DocumentId s_dummyDocumentId = DocumentId.CreateNewId(ProjectId.CreateNewId()); - - public ActiveStatementsDescription(string oldSource, string newSource) + public ActiveStatementsDescription(string oldMarkedSource, string newMarkedSource, Func syntaxTreeFactory, ActiveStatementFlags[]? flags) { - var oldText = SourceText.From(oldSource); + var oldSource = ClearTags(oldMarkedSource); + var newSource = ClearTags(newMarkedSource); + + var oldTree = syntaxTreeFactory(oldSource); + var newTree = syntaxTreeFactory(newSource); + + var oldDocumentMap = new Dictionary>(); + OldStatements = CreateActiveStatementMapFromMarkers(oldMarkedSource, oldTree, flags, oldDocumentMap); + + OldStatementsMap = new ActiveStatementsMap( + documentPathMap: oldDocumentMap.ToImmutableDictionary(e => e.Key, e => e.Value.OrderBy(ActiveStatementsMap.Comparer).ToImmutableArray()), + instructionMap: ActiveStatementsMap.Empty.InstructionMap); + + var newActiveStatementMarkers = GetActiveSpans(newMarkedSource).ToArray(); - OldStatements = GetActiveSpans(oldSource).Aggregate( - new List(), - (list, s) => SetListItem(list, s.Id, CreateActiveStatement(s.Span, s.Id, oldText, s_dummyDocumentId))).ToArray(); + var activeStatementCount = Math.Max(OldStatements.Length, (newActiveStatementMarkers.Length == 0) ? -1 : newActiveStatementMarkers.Max(m => m.Id)); - NewSpans = GetActiveSpans(newSource).Aggregate( - new List(), - (list, s) => SetListItem(list, s.Id, s.Span)).ToArray(); + var newMappedSpans = new ArrayBuilder(); + var newMappedRegions = new ArrayBuilder>(); + var newExceptionRegionMarkers = GetExceptionRegions(newMarkedSource); + + newMappedSpans.ZeroInit(activeStatementCount); + newMappedRegions.ZeroInit(activeStatementCount); + + // initialize with deleted spans (they will retain their file path): + foreach (var oldStatement in OldStatements) + { + newMappedSpans[oldStatement.Statement.Ordinal] = new SourceFileSpan(oldStatement.Statement.FilePath, default); + newMappedRegions[oldStatement.Statement.Ordinal] = ImmutableArray.Empty; + } + + // update with spans marked in the new source: + foreach (var (unmappedSpan, ordinal) in newActiveStatementMarkers) + { + newMappedSpans[ordinal] = newTree.GetMappedLineSpan(unmappedSpan); + newMappedRegions[ordinal] = (ordinal < newExceptionRegionMarkers.Length) ? + newExceptionRegionMarkers[ordinal].SelectAsArray(span => (SourceFileSpan)newTree.GetMappedLineSpan(span)) : + ImmutableArray.Empty; + } - OldRegions = GetExceptionRegions(oldSource, OldStatements.Length); - NewRegions = GetExceptionRegions(newSource, NewSpans.Length); + NewMappedSpans = newMappedSpans.ToImmutable(); + NewMappedRegions = newMappedRegions.ToImmutable(); // Tracking spans are marked in the new source since the editor moves them around as the user // edits the source and we get their positions when analyzing the new source. - // The EnC analyzer uses old trackign spans as hints to find matching nodes. - OldTrackingSpans = GetTrackingSpans(newSource, OldStatements.Length); + // The EnC analyzer uses old tracking spans as hints to find matching nodes. + var newText = newTree.GetText(); + OldUnmappedTrackingSpans = GetTrackingSpans(newMarkedSource, activeStatementCount). + SelectAsArray(s => newText.Lines.GetLinePositionSpan(s)); + } + + internal static ImmutableArray CreateActiveStatementMapFromMarkers( + string markedSource, + SyntaxTree tree, + ActiveStatementFlags[]? flags, + Dictionary> documentMap) + { + var activeStatementMarkers = GetActiveSpans(markedSource).ToArray(); + var exceptionRegionMarkers = GetExceptionRegions(markedSource); + + return activeStatementMarkers.Aggregate( + new List(), + (list, marker) => + { + var (unmappedSpan, ordinal) = marker; + var mappedSpan = tree.GetMappedLineSpan(unmappedSpan); + var documentActiveStatements = documentMap.GetOrAdd(mappedSpan.Path, path => new List()); + + var statementFlags = (flags != null) ? flags[ordinal] : + ((ordinal == 0) ? ActiveStatementFlags.IsLeafFrame : ActiveStatementFlags.IsNonLeafFrame) | ActiveStatementFlags.MethodUpToDate; + + var exceptionRegions = (ordinal < exceptionRegionMarkers.Length) ? + exceptionRegionMarkers[ordinal].SelectAsArray(unmappedRegionSpan => (SourceFileSpan)tree.GetMappedLineSpan(unmappedRegionSpan)) : + ImmutableArray.Empty; + + var unmappedActiveStatement = new UnmappedActiveStatement( + unmappedSpan, + new ActiveStatement( + ordinal, + statementFlags, + mappedSpan, + instructionId: default), + new ActiveStatementExceptionRegions(exceptionRegions, isActiveStatementCovered: true)); + + documentActiveStatements.Add(unmappedActiveStatement.Statement); + return SetListItem(list, ordinal, unmappedActiveStatement); + }).ToImmutableArray(); } - internal static readonly ActiveStatementsDescription Empty = new ActiveStatementsDescription(); + internal static ImmutableArray GetActiveStatementDebugInfos( + Func syntaxTreeFactory, + string[] markedSources, + string[]? filePaths = null, + string? extension = null, + int[]? methodRowIds = null, + Guid[]? modules = null, + int[]? methodVersions = null, + int[]? ilOffsets = null, + ActiveStatementFlags[]? flags = null) + { + var moduleId = new Guid("00000000-0000-0000-0000-000000000001"); + var map = new Dictionary>(); + + var activeStatements = new ArrayBuilder(); + + var sourceIndex = 0; + foreach (var markedSource in markedSources) + { + var documentName = filePaths?[sourceIndex] ?? Path.Combine(TempRoot.Root, TestWorkspace.GetDefaultTestSourceDocumentName(sourceIndex, extension)); + var tree = syntaxTreeFactory(ClearTags(markedSource), documentName); + var statements = CreateActiveStatementMapFromMarkers(markedSource, tree, flags, map); + + activeStatements.AddRange(statements.Where(s => s.Statement != null).Select(s => s.Statement)); + sourceIndex++; + } + + activeStatements.Sort((x, y) => x.Ordinal.CompareTo(y.Ordinal)); + + return activeStatements.SelectAsArray(statement => + new ManagedActiveStatementDebugInfo( + new ManagedInstructionId( + new ManagedMethodId( + (modules != null) ? modules[statement.Ordinal] : moduleId, + new ManagedModuleMethodId( + token: 0x06000000 | (methodRowIds != null ? methodRowIds[statement.Ordinal] : statement.Ordinal + 1), + version: (methodVersions != null) ? methodVersions[statement.Ordinal] : 1)), + ilOffset: (ilOffsets != null) ? ilOffsets[statement.Ordinal] : 0), + documentName: statement.FilePath, + sourceSpan: statement.Span.ToSourceSpan(), + flags: statement.Flags)); + } internal static string ClearTags(string source) => s_tags.Replace(source, m => new string(' ', m.Length)); @@ -90,12 +205,10 @@ internal static IEnumerable GetIds(Match match) internal static int[] GetIds(string ids) => ids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); - internal static IEnumerable> GetDottedIds(Match match) - { - return from ids in match.Groups["Id"].Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - let parts = ids.Split('.') - select ValueTuple.Create(int.Parse(parts[0]), int.Parse(parts[1])); - } + internal static IEnumerable<(int, int)> GetDottedIds(Match match) + => from ids in match.Groups["Id"].Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + let parts = ids.Split('.') + select (int.Parse(parts[0]), int.Parse(parts[1])); private static IEnumerable<(TextSpan Span, int[] Ids)> GetSpansRecursive(Regex regex, string contentGroupName, string markedSource, int offset) { @@ -126,27 +239,12 @@ internal static IEnumerable> GetDottedIds(Match match) } } - internal static ActiveStatement CreateActiveStatement(ActiveStatementFlags flags, LinePositionSpan span, DocumentId documentId) - => new ActiveStatement( - ordinal: 0, - primaryDocumentOrdinal: 0, - ImmutableArray.Create(documentId), - flags, - span, - instructionId: default); - - internal static ActiveStatement CreateActiveStatement(TextSpan span, int id, SourceText text, DocumentId documentId) - => CreateActiveStatement( - (id == 0) ? ActiveStatementFlags.IsLeafFrame : ActiveStatementFlags.IsNonLeafFrame, - text.Lines.GetLinePositionSpan(span), - documentId); - - internal static TextSpan[]? GetTrackingSpans(string src, int count) + internal static TextSpan[] GetTrackingSpans(string src, int count) { var matches = s_trackingStatementPattern.Matches(src); if (matches.Count == 0) { - return null; + return Array.Empty(); } var result = new TextSpan[count]; @@ -165,31 +263,58 @@ internal static ActiveStatement CreateActiveStatement(TextSpan span, int id, Sou return result; } - internal static ImmutableArray[] GetExceptionRegions(string src, int activeStatementCount) + internal static ImmutableArray> GetExceptionRegions(string markedSource) { - var matches = ExceptionRegionPattern.Matches(src); - var result = new List[activeStatementCount]; + var matches = ExceptionRegionPattern.Matches(markedSource); + var plainSource = ClearTags(markedSource); + + var result = new List>(); for (var i = 0; i < matches.Count; i++) { var exceptionRegion = matches[i].Groups["ExceptionRegion"]; - foreach (var id in GetDottedIds(matches[i])) + foreach (var (activeStatementId, exceptionRegionId) in GetDottedIds(matches[i])) { - var activeStatementId = id.Item1; - var exceptionRegionId = id.Item2; + EnsureSlot(result, activeStatementId); + result[activeStatementId] ??= new List(); + EnsureSlot(result[activeStatementId], exceptionRegionId); - if (result[activeStatementId] == null) - { - result[activeStatementId] = new List(); - } + var regionText = plainSource.AsSpan().Slice(exceptionRegion.Index, exceptionRegion.Length); + var start = IndexOfDifferent(regionText, ' '); + var length = LastIndexOfDifferent(regionText, ' ') - start + 1; - EnsureSlot(result[activeStatementId], exceptionRegionId); - result[activeStatementId][exceptionRegionId] = new TextSpan(exceptionRegion.Index, exceptionRegion.Length); + result[activeStatementId][exceptionRegionId] = new TextSpan(exceptionRegion.Index + start, length); + } + } + + return result.Select(r => r.AsImmutableOrEmpty()).ToImmutableArray(); + } + + public static int IndexOfDifferent(ReadOnlySpan span, char c) + { + for (var i = 0; i < span.Length; i++) + { + if (span[i] != c) + { + return i; + } + } + + return -1; + } + + public static int LastIndexOfDifferent(ReadOnlySpan span, char c) + { + for (var i = span.Length - 1; i >= 0; i--) + { + if (span[i] != c) + { + return i; } } - return result.Select(r => r.AsImmutableOrEmpty()).ToArray(); + return -1; } public static List SetListItem(List list, int i, T item) diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index eaae72b51290e..fe39597f7c5a2 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -5,23 +5,17 @@ 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; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; using Roslyn.Utilities; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Xunit; using static Microsoft.CodeAnalysis.EditAndContinue.AbstractEditAndContinueAnalyzer; @@ -29,105 +23,71 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { internal abstract class EditAndContinueTestHelpers { + public static readonly EditAndContinueCapabilities BaselineCapabilities = EditAndContinueCapabilities.Baseline; + public static readonly EditAndContinueCapabilities Net5RuntimeCapabilities = EditAndContinueCapabilities.Baseline | + EditAndContinueCapabilities.AddInstanceFieldToExistingType | + EditAndContinueCapabilities.AddStaticFieldToExistingType | + EditAndContinueCapabilities.AddMethodToExistingType | + EditAndContinueCapabilities.NewTypeDefinition; + public abstract AbstractEditAndContinueAnalyzer Analyzer { get; } public abstract SyntaxNode FindNode(SyntaxNode root, TextSpan span); - public abstract SyntaxTree ParseText(string source); public abstract ImmutableArray GetDeclarators(ISymbol method); public abstract string LanguageName { get; } public abstract TreeComparer TopSyntaxComparer { get; } - internal void VerifyUnchangedDocument( - string source, - ActiveStatement[] oldActiveStatements, - TextSpan[] expectedNewActiveStatements, - ImmutableArray[] expectedNewExceptionRegions) - { - var text = SourceText.From(source); - var tree = ParseText(source); - var root = tree.GetRoot(); - - tree.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); - - var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId("TestEnCProject"), "TestEnCDocument"); - - var actualNewActiveStatements = ImmutableArray.CreateBuilder(oldActiveStatements.Length); - actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; - - var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(oldActiveStatements.Length); - actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; - - Analyzer.GetTestAccessor().AnalyzeUnchangedDocument( - oldActiveStatements.AsImmutable(), - text, - root, - actualNewActiveStatements, - actualNewExceptionRegions); - - // check active statements: - 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], text); - } - } - - private void VerifyActiveStatementsAndExceptionRegions( + private void VerifyDocumentActiveStatementsAndExceptionRegions( ActiveStatementsDescription description, - IReadOnlyList oldActiveStatements, - SourceText oldText, - SourceText newText, - SyntaxNode oldRoot, - IReadOnlyList actualNewActiveStatements, - IReadOnlyList> actualNewExceptionRegions) + SyntaxTree oldTree, + SyntaxTree newTree, + ImmutableArray actualNewActiveStatements, + ImmutableArray> actualNewExceptionRegions) { // check active statements: - AssertSpansEqual(description.NewSpans, actualNewActiveStatements.Select(s => s.Span), newText); + AssertSpansEqual(description.NewMappedSpans, actualNewActiveStatements.OrderBy(x => x.Ordinal).Select(s => s.FileSpan), newTree); + + var oldRoot = oldTree.GetRoot(); // check old exception regions: - for (var i = 0; i < oldActiveStatements.Count; i++) + foreach (var oldStatement in description.OldStatements) { var oldRegions = Analyzer.GetExceptionRegions( - oldText, oldRoot, - oldActiveStatements[i].Span, - isNonLeaf: oldActiveStatements[i].IsNonLeaf, - out _); + oldStatement.UnmappedSpan, + isNonLeaf: oldStatement.Statement.IsNonLeaf, + CancellationToken.None); - AssertSpansEqual(description.OldRegions[i], oldRegions, oldText); + AssertSpansEqual(oldStatement.ExceptionRegions.Spans, oldRegions.Spans, oldTree); } // check new exception regions: - Assert.Equal(description.NewRegions.Length, actualNewExceptionRegions.Count); - for (var i = 0; i < description.NewRegions.Length; i++) + if (!actualNewExceptionRegions.IsDefault) { - AssertSpansEqual(description.NewRegions[i], actualNewExceptionRegions[i], newText); + Assert.Equal(actualNewActiveStatements.Length, actualNewExceptionRegions.Length); + Assert.Equal(description.NewMappedRegions.Length, actualNewExceptionRegions.Length); + for (var i = 0; i < actualNewActiveStatements.Length; i++) + { + var activeStatement = actualNewActiveStatements[i]; + AssertSpansEqual(description.NewMappedRegions[activeStatement.Ordinal], actualNewExceptionRegions[i], newTree); + } } } internal void VerifyLineEdits( EditScript editScript, - IEnumerable expectedLineEdits, + IEnumerable expectedLineEdits, IEnumerable expectedNodeUpdates, RudeEditDiagnosticDescription[] expectedDiagnostics) { - 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 newText = SourceText.From(editScript.Match.NewRoot.SyntaxTree.ToString()); var diagnostics = new ArrayBuilder(); var editMap = BuildEditMap(editScript); var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new ArrayBuilder(); + var actualLineEdits = new ArrayBuilder(); Analyzer.GetTestAccessor().AnalyzeTrivia( - oldText, - newText, editScript.Match, editMap, triviaEdits, @@ -137,13 +97,29 @@ internal void VerifyLineEdits( VerifyDiagnostics(expectedDiagnostics, diagnostics, newText); - AssertEx.Equal(expectedLineEdits, actualLineEdits, itemSeparator: ",\r\n"); + // check files are matching: + AssertEx.Equal( + expectedLineEdits.Select(e => e.FileName), + actualLineEdits.Select(e => e.FileName), + itemSeparator: ",\r\n"); + + // check lines are matching: + _ = expectedLineEdits.Zip(actualLineEdits, (expected, actual) => + { + AssertEx.Equal( + expected.LineUpdates, + actual.LineUpdates, + itemSeparator: ",\r\n", + itemInspector: s => $"new({s.OldLine}, {s.NewLine})"); + + return true; + }).ToArray(); var actualNodeUpdates = triviaEdits.Select(e => e.NewNode.ToString().ToLines().First()); AssertEx.Equal(expectedNodeUpdates, actualNodeUpdates, itemSeparator: ",\r\n"); } - internal void VerifySemantics(EditScript[] editScripts, TargetFramework targetFramework, DocumentAnalysisResultsDescription[] expectedResults) + internal void VerifySemantics(EditScript[] editScripts, TargetFramework targetFramework, DocumentAnalysisResultsDescription[] expectedResults, EditAndContinueCapabilities? capabilities = null) { Assert.True(editScripts.Length == expectedResults.Length); var documentCount = expectedResults.Length; @@ -166,10 +142,8 @@ internal void VerifySemantics(EditScript[] editScripts, TargetFramew { var expectedResult = expectedResults[documentIndex]; - var oldActiveStatements = expectedResult.ActiveStatements.OldStatements.ToImmutableArray(); - var includeFirstLineInDiagnostics = expectedResult.Diagnostics.Any(d => d.FirstLine != null) == true; - var newActiveStatementSpans = expectedResult.ActiveStatements.OldTrackingSpans.ToImmutableArrayOrEmpty(); + var newActiveStatementSpans = expectedResult.ActiveStatements.OldUnmappedTrackingSpans; // 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]; @@ -185,7 +159,7 @@ internal void VerifySemantics(EditScript[] editScripts, TargetFramew Contract.ThrowIfNull(oldModel); Contract.ThrowIfNull(newModel); - var result = Analyzer.AnalyzeDocumentAsync(oldProject, oldActiveStatements, newDocument, newActiveStatementSpans, CancellationToken.None).Result; + var result = Analyzer.AnalyzeDocumentAsync(oldProject, expectedResult.ActiveStatements.OldStatementsMap, newDocument, newActiveStatementSpans, capabilities ?? Net5RuntimeCapabilities, CancellationToken.None).Result; var oldText = oldDocument.GetTextSynchronously(default); var newText = newDocument.GetTextSynchronously(default); @@ -193,25 +167,35 @@ internal void VerifySemantics(EditScript[] editScripts, TargetFramew if (!expectedResult.SemanticEdits.IsDefault) { - VerifySemanticEdits(expectedResult.SemanticEdits, result.SemanticEdits, oldModel.Compilation, newModel.Compilation, oldRoot, newRoot); - - allEdits.AddRange(result.SemanticEdits); + if (result.HasChanges) + { + VerifySemanticEdits(expectedResult.SemanticEdits, result.SemanticEdits, oldModel.Compilation, newModel.Compilation, oldRoot, newRoot); + + allEdits.AddRange(result.SemanticEdits); + } + else + { + Assert.True(expectedResult.SemanticEdits.IsEmpty); + Assert.True(result.SemanticEdits.IsDefault); + } } - if (expectedResult.Diagnostics.IsEmpty) + if (!result.HasChanges) { - VerifyActiveStatementsAndExceptionRegions( - expectedResult.ActiveStatements, - oldActiveStatements, - oldText, - newText, - oldRoot, - result.ActiveStatements, - result.ExceptionRegions); + Assert.True(result.ExceptionRegions.IsDefault); + Assert.True(result.ActiveStatements.IsDefault); } else { - Assert.True(result.ExceptionRegions.IsDefault); + // exception regions not available in presence of rude edits: + Assert.Equal(!expectedResult.Diagnostics.IsEmpty, result.ExceptionRegions.IsDefault); + + VerifyDocumentActiveStatementsAndExceptionRegions( + expectedResult.ActiveStatements, + oldTree, + newTree, + result.ActiveStatements, + result.ExceptionRegions); } } @@ -321,22 +305,31 @@ private void CreateProjects(EditScript[] editScripts, AdhocWorkspace newProject = newSolution.Projects.Single(); } - private static void AssertSpansEqual(IEnumerable expected, IEnumerable actual, SourceText newText) + private static void AssertSpansEqual(IEnumerable expected, IEnumerable actual, SyntaxTree newTree) { AssertEx.Equal( expected, - actual.Select(span => newText.Lines.GetTextSpan(span)), + actual, itemSeparator: "\r\n", - itemInspector: s => DisplaySpan(newText, s)); + itemInspector: span => DisplaySpan(newTree, span)); } - private static string DisplaySpan(SourceText source, TextSpan span) - => span + ": [" + source.GetSubText(span).ToString().Replace("\r\n", " ") + "]"; + private static string DisplaySpan(SyntaxTree tree, SourceFileSpan span) + { + if (tree.FilePath != span.Path) + { + return span.ToString(); + } + + var text = tree.GetText(); + var code = text.GetSubText(text.Lines.GetTextSpan(span.Span)).ToString().Replace("\r\n", " "); + return $"{span}: [{code}]"; + } internal static IEnumerable> GetMethodMatches(AbstractEditAndContinueAnalyzer analyzer, Match bodyMatch) { Dictionary? lazyActiveOrMatchedLambdas = null; - var map = analyzer.GetTestAccessor().ComputeMap(bodyMatch, Array.Empty(), ref lazyActiveOrMatchedLambdas, new ArrayBuilder()); + var map = analyzer.GetTestAccessor().ComputeMap(bodyMatch, new ArrayBuilder(), ref lazyActiveOrMatchedLambdas, new ArrayBuilder()); var result = new Dictionary(); foreach (var pair in map.Forward) diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs index bbc05c64384cf..9989d4abcc3cf 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs @@ -16,21 +16,21 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests internal class MockEditAndContinueWorkspaceService : IEditAndContinueWorkspaceService { - public Func, ImmutableArray>>? GetBaseActiveStatementSpansImpl; - public Func? GetCurrentActiveStatementPositionImpl; + public Func, ImmutableArray>>? GetBaseActiveStatementSpansImpl; + public Func? GetCurrentActiveStatementPositionImpl; - public Func>? GetAdjustedActiveStatementSpansImpl; + public Func>? GetAdjustedActiveStatementSpansImpl; public Action? StartDebuggingSessionImpl; public ActionOut>? EndDebuggingSessionImpl; - public Func? HasChangesImpl; - public Func? EmitSolutionUpdateImpl; + public Func? HasChangesImpl; + public Func? EmitSolutionUpdateImpl; public Func? IsActiveStatementInExceptionRegionImpl; public Action? OnSourceFileUpdatedImpl; public ActionOut>? CommitSolutionUpdateImpl; public ActionOut>? BreakStateEnteredImpl; public Action? DiscardSolutionUpdateImpl; - public Func>? GetDocumentDiagnosticsImpl; + public Func>? GetDocumentDiagnosticsImpl; public void BreakStateEntered(out ImmutableArray documentsToReanalyze) { @@ -47,7 +47,7 @@ public void CommitSolutionUpdate(out ImmutableArray documentsToReana public void DiscardSolutionUpdate() => DiscardSolutionUpdateImpl?.Invoke(); - public ValueTask EmitSolutionUpdateAsync(Solution solution, SolutionActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) + public ValueTask EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) => new((EmitSolutionUpdateImpl ?? throw new NotImplementedException()).Invoke(solution, activeStatementSpanProvider)); public void EndDebuggingSession(out ImmutableArray documentsToReanalyze) @@ -56,19 +56,19 @@ public void EndDebuggingSession(out ImmutableArray documentsToReanal EndDebuggingSessionImpl?.Invoke(out documentsToReanalyze); } - public ValueTask>> GetBaseActiveStatementSpansAsync(Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) + public ValueTask>> GetBaseActiveStatementSpansAsync(Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) => new((GetBaseActiveStatementSpansImpl ?? throw new NotImplementedException()).Invoke(solution, documentIds)); - public ValueTask GetCurrentActiveStatementPositionAsync(Solution solution, SolutionActiveStatementSpanProvider activeStatementSpanProvider, ManagedInstructionId instructionId, CancellationToken cancellationToken) + public ValueTask GetCurrentActiveStatementPositionAsync(Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, ManagedInstructionId instructionId, CancellationToken cancellationToken) => new((GetCurrentActiveStatementPositionImpl ?? throw new NotImplementedException()).Invoke(solution, activeStatementSpanProvider, instructionId)); - public ValueTask> GetAdjustedActiveStatementSpansAsync(Document document, DocumentActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) + public ValueTask> GetAdjustedActiveStatementSpansAsync(TextDocument document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) => new((GetAdjustedActiveStatementSpansImpl ?? throw new NotImplementedException()).Invoke(document, activeStatementSpanProvider)); - public ValueTask> GetDocumentDiagnosticsAsync(Document document, DocumentActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) + public ValueTask> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) => new((GetDocumentDiagnosticsImpl ?? throw new NotImplementedException()).Invoke(document, activeStatementSpanProvider)); - public ValueTask HasChangesAsync(Solution solution, SolutionActiveStatementSpanProvider activeStatementSpanProvider, string? sourceFilePath, CancellationToken cancellationToken) + public ValueTask HasChangesAsync(Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, string? sourceFilePath, CancellationToken cancellationToken) => new((HasChangesImpl ?? throw new NotImplementedException()).Invoke(solution, activeStatementSpanProvider, sourceFilePath)); public ValueTask IsActiveStatementInExceptionRegionAsync(Solution solution, ManagedInstructionId instructionId, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/MockManagedEditAndContinueDebuggerService.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/MockManagedEditAndContinueDebuggerService.cs index 260f3d7d31648..f9653116c1afb 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/MockManagedEditAndContinueDebuggerService.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/MockManagedEditAndContinueDebuggerService.cs @@ -35,6 +35,9 @@ public Task GetAvailabilityAsync(Guid mvid, throw new NotImplementedException(); } + public Task> GetCapabilitiesAsync(CancellationToken cancellationToken) + => Task.FromResult(ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition")); + public Task PrepareModuleForUpdateAsync(Guid mvid, CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/src/EditorFeatures/TestUtilities/EditorFactory.cs b/src/EditorFeatures/TestUtilities/EditorFactory.cs index 2bca665fb98ed..5c46ca25a16ed 100644 --- a/src/EditorFeatures/TestUtilities/EditorFactory.cs +++ b/src/EditorFeatures/TestUtilities/EditorFactory.cs @@ -15,7 +15,7 @@ namespace Roslyn.Test.EditorUtilities { public static class EditorFactory { - public static ITextBuffer CreateBuffer( + public static ITextBuffer2 CreateBuffer( ExportProvider exportProvider, params string[] lines) { @@ -24,7 +24,7 @@ public static ITextBuffer CreateBuffer( return CreateBuffer(exportProvider, contentType, lines); } - public static ITextBuffer CreateBuffer( + public static ITextBuffer2 CreateBuffer( ExportProvider exportProvider, IContentType contentType, params string[] lines) @@ -34,7 +34,7 @@ public static ITextBuffer CreateBuffer( // The overload of CreateTextBuffer that takes just a string doesn't initialize the whitespace tracking logic in the editor, // so calls to IIndentationManagerService won't work correctly. Tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1005541. using var reader = new StringReader(text); - return exportProvider.GetExportedValue().CreateTextBuffer(reader, contentType); + return (ITextBuffer2)exportProvider.GetExportedValue().CreateTextBuffer(reader, contentType); } public static DisposableTextView CreateView( diff --git a/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs b/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs index daeba74604bda..aa31e10d3d84e 100644 --- a/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs +++ b/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs @@ -64,9 +64,7 @@ public static class EditorTestCompositions typeof(TextEditorResources).Assembly, typeof(EditorFeaturesResources).Assembly, typeof(CSharp.CSharpEditorResources).Assembly, - typeof(VisualBasic.VBEditorResources).Assembly) - .AddParts( - typeof(TestWaitIndicator)); + typeof(VisualBasic.VBEditorResources).Assembly); public static readonly TestComposition EditorFeaturesWpf = EditorFeatures .AddAssemblies( @@ -76,7 +74,10 @@ public static class EditorTestCompositions .AddAssemblies( typeof(IInteractiveWindow).Assembly) .AddParts( - typeof(TestInteractiveWindowEditorFactoryService)); + typeof(TestInteractiveWindowEditorFactoryService), +#pragma warning disable CS0618 // Type or member is obsolete + typeof(TestWaitIndicator)); +#pragma warning restore CS0618 // Type or member is obsolete public static readonly TestComposition LanguageServerProtocol = EditorFeatures .AddAssemblies( diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 0ab121bd2eed6..347ddf2d2d659 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -102,6 +102,14 @@ public Task> MapSpansAsync(Document document, I return Task.FromResult(mappedResult); } + + public Task> GetMappedTextChangesAsync( + Document oldDocument, + Document newDocument, + CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } protected class OrderLocations : Comparer diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index 958ecb522b95a..700d8aacadbba 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -38,7 +38,9 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo [UseExportProvider] public abstract class AbstractNavigateToTests { - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestDocumentTrackingServiceFactory)); + protected static readonly TestComposition DefaultComposition = EditorTestCompositions.EditorFeatures; + protected static readonly TestComposition FirstVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocIsVisibleDocumentTrackingService.Factory)); + protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); protected INavigateToItemProvider _provider; protected NavigateToTestAggregator _aggregator; @@ -66,31 +68,61 @@ public abstract class AbstractNavigateToTests protected abstract TestWorkspace CreateWorkspace(string content, ExportProvider exportProvider); protected abstract string Language { get; } - protected async Task TestAsync(TestHost testHost, string content, Func body) + public enum Composition { - await TestAsync(content, body, testHost, null); - await TestAsync(content, body, testHost, w => new FirstDocIsVisibleDocumentTrackingService(w.Workspace)); - await TestAsync(content, body, testHost, w => new FirstDocIsActiveAndVisibleDocumentTrackingService(w.Workspace)); + Default, + FirstVisible, + FirstActiveAndVisible, + } + + protected async Task TestAsync(TestHost testHost, Composition composition, string content, Func body) + { + var testComposition = composition switch + { + Composition.Default => DefaultComposition, + Composition.FirstVisible => FirstVisibleComposition, + Composition.FirstActiveAndVisible => FirstActiveAndVisibleComposition, + _ => throw ExceptionUtilities.UnexpectedValue(composition), + }; + + await TestAsync(content, body, testHost, testComposition); + } + + protected async Task TestAsync(TestHost testHost, Composition composition, XElement content, Func body) + { + var testComposition = composition switch + { + Composition.Default => DefaultComposition, + Composition.FirstVisible => FirstVisibleComposition, + Composition.FirstActiveAndVisible => FirstActiveAndVisibleComposition, + _ => throw ExceptionUtilities.UnexpectedValue(composition), + }; + + await TestAsync(content, body, testHost, testComposition); } private async Task TestAsync( string content, Func body, TestHost testHost, - Func createTrackingService) + TestComposition composition) + { + using var workspace = CreateWorkspace(content, testHost, composition); + await body(workspace); + } + + protected async Task TestAsync( + XElement content, Func body, TestHost testHost, + TestComposition composition) { - using var workspace = CreateWorkspace(content, testHost, createTrackingService); + using var workspace = CreateWorkspace(content, testHost, composition); await body(workspace); } private protected TestWorkspace CreateWorkspace( XElement workspaceElement, TestHost testHost, - Func createTrackingService) + TestComposition composition) { - var exportProvider = s_composition.WithTestHostParts(testHost).ExportProviderFactory.CreateExportProvider(); - - // must be set before the workspace is created since the constructor accesses IDocumentTrackingService - var documentTrackingServiceFactory = exportProvider.GetExportedValue(); - documentTrackingServiceFactory.FactoryMethod = createTrackingService; + var exportProvider = composition.WithTestHostParts(testHost).ExportProviderFactory.CreateExportProvider(); var workspace = TestWorkspace.Create(workspaceElement, exportProvider: exportProvider); InitializeWorkspace(workspace); @@ -100,13 +132,9 @@ private protected TestWorkspace CreateWorkspace( private protected TestWorkspace CreateWorkspace( string content, TestHost testHost, - Func createTrackingService) + TestComposition composition) { - var exportProvider = s_composition.WithTestHostParts(testHost).ExportProviderFactory.CreateExportProvider(); - - // must be set before the workspace is created since the constructor accesses IDocumentTrackingService - var documentTrackingServiceFactory = exportProvider.GetExportedValue(); - documentTrackingServiceFactory.FactoryMethod = createTrackingService; + var exportProvider = composition.WithTestHostParts(testHost).ExportProviderFactory.CreateExportProvider(); var workspace = CreateWorkspace(content, exportProvider); InitializeWorkspace(workspace); @@ -194,9 +222,12 @@ private class FirstDocIsVisibleDocumentTrackingService : IDocumentTrackingServic { private readonly Workspace _workspace; - public FirstDocIsVisibleDocumentTrackingService(Workspace workspace) + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + private FirstDocIsVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; + public bool SupportsDocumentTracking => true; + public event EventHandler ActiveDocumentChanged { add { } remove { } } public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } @@ -205,15 +236,32 @@ public DocumentId TryGetActiveDocument() public ImmutableArray GetVisibleDocuments() => ImmutableArray.Create(_workspace.CurrentSolution.Projects.First().DocumentIds.First()); + + [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] + public class Factory : IWorkspaceServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory() + { + } + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new FirstDocIsVisibleDocumentTrackingService(workspaceServices.Workspace); + } } private class FirstDocIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService { private readonly Workspace _workspace; - public FirstDocIsActiveAndVisibleDocumentTrackingService(Workspace workspace) + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + private FirstDocIsActiveAndVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; + public bool SupportsDocumentTracking => true; + public event EventHandler ActiveDocumentChanged { add { } remove { } } public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } @@ -222,26 +270,20 @@ public DocumentId TryGetActiveDocument() public ImmutableArray GetVisibleDocuments() => ImmutableArray.Create(_workspace.CurrentSolution.Projects.First().DocumentIds.First()); - } - - [Export] - [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] - public sealed class TestDocumentTrackingServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentTrackingServiceFactory() - => FactoryMethod = null; - internal Func FactoryMethod + [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] + public class Factory : IWorkspaceServiceFactory { - get; - set; - } + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory() + { + } - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => FactoryMethod?.Invoke(workspaceServices); + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new FirstDocIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); + } } } } diff --git a/src/EditorFeatures/TestUtilities/ReassignedVariable/AbstractReassignedVariableTests.cs b/src/EditorFeatures/TestUtilities/ReassignedVariable/AbstractReassignedVariableTests.cs new file mode 100644 index 0000000000000..1924220db42c5 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/ReassignedVariable/AbstractReassignedVariableTests.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.ReassignedVariable; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReassignedVariable +{ + [UseExportProvider] + public abstract class AbstractReassignedVariableTests + { + protected abstract TestWorkspace CreateWorkspace(string markup); + + protected async Task TestAsync(string markup) + { + using var workspace = CreateWorkspace(markup); + + var project = workspace.CurrentSolution.Projects.Single(); + var language = project.Language; + var document = project.Documents.Single(); + var text = await document.GetTextAsync(); + + var service = document.GetRequiredLanguageService(); + var result = await service.GetLocationsAsync(document, new TextSpan(0, text.Length), CancellationToken.None); + + var expectedSpans = workspace.Documents.Single().SelectedSpans.OrderBy(s => s.Start); + var actualSpans = result.OrderBy(s => s.Start); + + Assert.Equal(expectedSpans, actualSpans); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs index 84f1c7f04a50c..91f7f5d0df0ec 100644 --- a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.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.Collections.Immutable; using System.Composition; @@ -14,9 +12,7 @@ namespace Microsoft.CodeAnalysis.Editor.Test [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] internal sealed class TestDocumentTrackingService : IDocumentTrackingService { - private readonly object _gate = new object(); - private event EventHandler _activeDocumentChangedEventHandler; - private DocumentId _activeDocumentId; + private DocumentId? _activeDocumentId; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -24,24 +20,9 @@ public TestDocumentTrackingService() { } - public event EventHandler ActiveDocumentChanged - { - add - { - lock (_gate) - { - _activeDocumentChangedEventHandler += value; - } - } + public bool SupportsDocumentTracking => true; - remove - { - lock (_gate) - { - _activeDocumentChangedEventHandler -= value; - } - } - } + public event EventHandler? ActiveDocumentChanged; public event EventHandler NonRoslynBufferTextChanged { @@ -49,13 +30,13 @@ public event EventHandler NonRoslynBufferTextChanged remove { } } - public void SetActiveDocument(DocumentId newActiveDocumentId) + public void SetActiveDocument(DocumentId? newActiveDocumentId) { _activeDocumentId = newActiveDocumentId; - _activeDocumentChangedEventHandler?.Invoke(this, newActiveDocumentId); + ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); } - public DocumentId TryGetActiveDocument() + public DocumentId? TryGetActiveDocument() => _activeDocumentId; public ImmutableArray GetVisibleDocuments() diff --git a/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs b/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs index e26f814167709..61c7d54fea578 100644 --- a/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs +++ b/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs @@ -8,41 +8,25 @@ using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Threading; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Shared.Utilities; using VisualStudioIndicator = Microsoft.VisualStudio.Language.Intellisense.Utilities; namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities { - [Export(typeof(IWaitIndicator))] + // This type should be removed once the Roslyn repoa has been updated to + // consume https://github.com/dotnet/interactive-window/pull/202 + [Obsolete("This implementation of the obsolete editor API only exists to keep interactive window tests running.")] [Export(typeof(VisualStudioIndicator.IWaitIndicator))] - public sealed class TestWaitIndicator : IWaitIndicator, VisualStudioIndicator.IWaitIndicator + public sealed class TestWaitIndicator : VisualStudioIndicator.IWaitIndicator { public static readonly TestWaitIndicator Default = new TestWaitIndicator(); - private readonly IWaitContext _waitContext; - private readonly Microsoft.VisualStudio.Language.Intellisense.Utilities.IWaitContext _platformWaitContext = new UncancellableWaitContext(); + private readonly VisualStudioIndicator.IWaitContext _platformWaitContext = new UncancellableWaitContext(); [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public TestWaitIndicator() - => _waitContext = new UncancellableWaitContext(); - - IWaitContext IWaitIndicator.StartWait(string title, string message, bool allowCancel, bool showProgress) - => _waitContext; - - WaitIndicatorResult IWaitIndicator.Wait(string title, string message, bool allowCancel, bool showProgress, Action action) { - try - { - action(_waitContext); - } - catch (OperationCanceledException) - { - return WaitIndicatorResult.Canceled; - } - - return WaitIndicatorResult.Completed; } VisualStudioIndicator.IWaitContext VisualStudioIndicator.IWaitIndicator.StartWait(string title, string message, bool allowCancel) @@ -62,7 +46,7 @@ VisualStudioIndicator.WaitIndicatorResult VisualStudioIndicator.IWaitIndicator.W return VisualStudioIndicator.WaitIndicatorResult.Completed; } - private sealed class UncancellableWaitContext : IWaitContext, VisualStudioIndicator.IWaitContext + private sealed class UncancellableWaitContext : VisualStudioIndicator.IWaitContext { public CancellationToken CancellationToken { diff --git a/src/EditorFeatures/TestUtilities/Utilities/TestWaitContext.cs b/src/EditorFeatures/TestUtilities/Utilities/TestWaitContext.cs deleted file mode 100644 index f15aa5635fccf..0000000000000 --- a/src/EditorFeatures/TestUtilities/Utilities/TestWaitContext.cs +++ /dev/null @@ -1,77 +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.Threading; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities -{ - public sealed class TestWaitContext : IWaitContext - { - private readonly CancellationTokenSource _cancellationTokenSource; - private readonly int _maxUpdates; - private int _updates; - private readonly IProgressTracker _progressTracker; - - public TestWaitContext(int maxUpdates) - { - _cancellationTokenSource = new CancellationTokenSource(); - _maxUpdates = maxUpdates; - _progressTracker = new ProgressTracker((_1, _2, _3) => UpdateProgress()); - } - - IProgressTracker IWaitContext.ProgressTracker => _progressTracker; - - public int Updates - { - get { return _updates; } - } - - public CancellationToken CancellationToken - { - get { return _cancellationTokenSource.Token; } - } - - private void UpdateProgress() - { - var result = Interlocked.Increment(ref _updates); - if (result > _maxUpdates) - { - _cancellationTokenSource.Cancel(); - } - } - - public bool AllowCancel - { - get - { - return false; - } - - set - { - } - } - - public string Message - { - get - { - return ""; - } - - set - { - } - } - - public void Dispose() - { - } - } -} diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs index 7b5fc9ddb57a7..4612eb1c0fe90 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs @@ -36,9 +36,9 @@ public class TestHostDocument private TestHostProject? _project; /// - /// The for this document. Null if not yet created. + /// The for this document. Null if not yet created. /// - private ITextBuffer? _textBuffer; + private ITextBuffer2? _textBuffer; /// /// The when the buffer was first created, which can be used for tracking changes to the current buffer. @@ -111,7 +111,7 @@ internal TestHostDocument( bool isLinkFile = false, IDocumentServiceProvider? documentServiceProvider = null, ImmutableArray roles = default, - ITextBuffer? textBuffer = null, + ITextBuffer2? textBuffer = null, bool isSourceGenerated = false) { Contract.ThrowIfNull(filePath); @@ -237,7 +237,7 @@ public IWpfTextView GetTextView() return _textView; } - public ITextBuffer GetTextBuffer() + public ITextBuffer2 GetTextBuffer() { var workspace = (TestWorkspace?)_languageServiceProvider?.WorkspaceServices.Workspace; diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 848599ce56493..cb70f6b50ac27 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -52,7 +52,7 @@ public partial class TestWorkspace : Workspace private readonly BackgroundParser _backgroundParser; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; - private readonly Dictionary _createdTextBuffers = new Dictionary(); + private readonly Dictionary _createdTextBuffers = new(); private readonly string _workspaceKind; public TestWorkspace(ExportProvider? exportProvider = null, TestComposition? composition = null, string? workspaceKind = WorkspaceKind.Host, bool disablePartialSolutions = true, bool ignoreUnchangeableDocumentsWhenApplyingChanges = true) @@ -505,7 +505,7 @@ public TestHostDocument CreateProjectionBufferDocument( path, mappedCaretLocation, mappedSpans, - textBuffer: projectionBuffer); + textBuffer: (ITextBuffer2)projectionBuffer); this.ProjectionDocuments.Add(projectionDocument); return projectionDocument; @@ -728,7 +728,7 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj return true; } - internal ITextBuffer GetOrCreateBufferForPath(string? filePath, IContentType contentType, string languageName, string initialText) + internal ITextBuffer2 GetOrCreateBufferForPath(string? filePath, IContentType contentType, string languageName, string initialText) { // If we don't have a file path we'll just make something up for the purpose of this dictionary so all // buffers are still held onto. This isn't a file name used in the workspace itself so it's unobservable. diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockNavigableItemsPresenter.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockNavigableItemsPresenter.vb index 64e6eb4019f66..7f7f85144f576 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockNavigableItemsPresenter.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockNavigableItemsPresenter.vb @@ -11,7 +11,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Friend Class MockStreamingFindUsagesPresenter Implements IStreamingFindUsagesPresenter - Public ReadOnly Context As New SimpleFindUsagesContext(CancellationToken.None) + Public ReadOnly Context As New SimpleFindUsagesContext() Private ReadOnly _action As Action Public Sub New(action As Action) @@ -22,13 +22,13 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Throw New NotImplementedException() End Sub - Public Function StartSearch(title As String, alwaysShowDeclarations As Boolean, cancellationToken As CancellationToken) As FindUsagesContext Implements IStreamingFindUsagesPresenter.StartSearch + Public Function StartSearch(title As String, alwaysShowDeclarations As Boolean) As (FindUsagesContext, CancellationToken) Implements IStreamingFindUsagesPresenter.StartSearch _action() - Return Context + Return (Context, CancellationToken.None) End Function - Public Function StartSearchWithCustomColumns(title As String, supportsReferences As Boolean, includeContainingTypeAndMemberColumns As Boolean, includeKindColumn As Boolean, cancellationToken As CancellationToken) As FindUsagesContext Implements IStreamingFindUsagesPresenter.StartSearchWithCustomColumns - Return Context + Public Function StartSearchWithCustomColumns(title As String, supportsReferences As Boolean, includeContainingTypeAndMemberColumns As Boolean, includeKindColumn As Boolean) As (FindUsagesContext, CancellationToken) Implements IStreamingFindUsagesPresenter.StartSearchWithCustomColumns + Return (Context, CancellationToken.None) End Function End Class End Namespace diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb index cbe98430b3127..392863ffccfb8 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb @@ -6,6 +6,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.FindUsages Imports Microsoft.CodeAnalysis.Navigation Imports Microsoft.CodeAnalysis.Options +Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Friend Class MockSymbolNavigationService @@ -20,9 +21,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Return True End Function - Public Function TrySymbolNavigationNotify(symbol As ISymbol, project As Project, cancellationToken As CancellationToken) As Boolean Implements ISymbolNavigationService.TrySymbolNavigationNotify + Public Function TrySymbolNavigationNotifyAsync(symbol As ISymbol, project As Project, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TrySymbolNavigationNotifyAsync _triedSymbolNavigationNotify = True - Return True + Return SpecializedTasks.True End Function Public Function WouldNavigateToSymbol(definitionItem As DefinitionItem, solution As Solution, cancellationToken As CancellationToken, ByRef filePath As String, ByRef lineNumber As Integer, ByRef charOffset As Integer) As Boolean Implements ISymbolNavigationService.WouldNavigateToSymbol diff --git a/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb b/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb index ec1aa30fe1fc6..631dfb334c914 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb @@ -53,13 +53,14 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Return True End Function - Public Function TrySymbolNavigationNotify(symbol As ISymbol, - project As Project, - cancellationToken As CancellationToken) As Boolean Implements ISymbolNavigationService.TrySymbolNavigationNotify + Public Function TrySymbolNavigationNotifyAsync( + symbol As ISymbol, + project As Project, + cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TrySymbolNavigationNotifyAsync Me.TrySymbolNavigationNotifyProvidedSymbol = symbol Me.TrySymbolNavigationNotifyProvidedProject = project - Return TrySymbolNavigationNotifyReturnValue + Return Task.FromResult(TrySymbolNavigationNotifyReturnValue) End Function Public Function WouldNavigateToSymbol(definitionItem As DefinitionItem, diff --git a/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/AutomaticEndConstructCorrector.vb b/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/AutomaticEndConstructCorrector.vb index 63315eea1ffcd..787df1a7aac7e 100644 --- a/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/AutomaticEndConstructCorrector.vb +++ b/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/AutomaticEndConstructCorrector.vb @@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Text +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticEndConstructCorrection ''' @@ -16,16 +17,16 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticEndConstructCorrect Partial Friend Class AutomaticEndConstructCorrector Private ReadOnly _buffer As ITextBuffer Private ReadOnly _session As Session - Private ReadOnly _waitIndicator As IWaitIndicator + Private ReadOnly _uiThreadOperationExecutor As IUIThreadOperationExecutor Private _previousDocument As Document Private _referencingViews As Integer - Public Sub New(subjectBuffer As ITextBuffer, waitIndicator As IWaitIndicator) + Public Sub New(subjectBuffer As ITextBuffer, uiThreadOperationExecutor As IUIThreadOperationExecutor) Contract.ThrowIfNull(subjectBuffer) Me._buffer = subjectBuffer - Me._waitIndicator = waitIndicator + Me._uiThreadOperationExecutor = uiThreadOperationExecutor Me._session = New Session(subjectBuffer) Me._previousDocument = Nothing @@ -67,10 +68,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticEndConstructCorrect End Sub Private Sub OnTextBufferChanged(sender As Object, e As TextContentChangedEventArgs) - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( "IntelliSense", - allowCancel:=True, - action:=Sub(c) StartSession(e, c.CancellationToken)) + defaultDescription:="", + allowCancellation:=True, + showProgress:=False, + action:=Sub(c) StartSession(e, c.UserCancellationToken)) ' clear previous document _previousDocument = Nothing diff --git a/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/ViewCreationListener.vb b/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/ViewCreationListener.vb index 3943c6cb5cbdc..da9b1c4403168 100644 --- a/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/ViewCreationListener.vb +++ b/src/EditorFeatures/VisualBasic/AutomaticEndConstructCorrection/ViewCreationListener.vb @@ -20,12 +20,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticEndConstructCorrect Friend Class ViewCreationListener Implements ITextViewConnectionListener - Private ReadOnly _waitIndicator As IWaitIndicator + Private ReadOnly _uiThreadOperationExecutor As IUIThreadOperationExecutor - Public Sub New(waitIndicator As IWaitIndicator) - Me._waitIndicator = waitIndicator + Public Sub New(uiThreadOperationExecutor As IUIThreadOperationExecutor) + _uiThreadOperationExecutor = uiThreadOperationExecutor End Sub Public Sub SubjectBuffersConnected( @@ -47,11 +47,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticEndConstructCorrect End Sub Private Sub AddConstructPairTo(buffers As IEnumerable(Of ITextBuffer)) - buffers.Do(Sub(b) b.Properties.GetOrCreateSingletonProperty(Function() New AutomaticEndConstructCorrector(b, _waitIndicator)).Connect()) + buffers.Do(Sub(b) b.Properties.GetOrCreateSingletonProperty(Function() New AutomaticEndConstructCorrector(b, _uiThreadOperationExecutor)).Connect()) End Sub Private Sub RemoveConstructPairFrom(buffers As IEnumerable(Of ITextBuffer)) - buffers.Do(Sub(b) b.Properties.GetOrCreateSingletonProperty(Function() New AutomaticEndConstructCorrector(b, _waitIndicator)).Disconnect()) + buffers.Do(Sub(b) b.Properties.GetOrCreateSingletonProperty(Function() New AutomaticEndConstructCorrector(b, _uiThreadOperationExecutor)).Disconnect()) buffers.Where( Function(b) b.Properties.GetProperty(Of AutomaticEndConstructCorrector)(GetType(AutomaticEndConstructCorrector)).IsDisconnected).Do( diff --git a/src/EditorFeatures/VisualBasic/ContentType/ContentTypeDefinitions.vb b/src/EditorFeatures/VisualBasic/ContentType/ContentTypeDefinitions.vb index 6bebfc2b5e817..fb73a16dfe3b8 100644 --- a/src/EditorFeatures/VisualBasic/ContentType/ContentTypeDefinitions.vb +++ b/src/EditorFeatures/VisualBasic/ContentType/ContentTypeDefinitions.vb @@ -11,10 +11,14 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ContentType ''' ''' Definition of the primary VB content type. + ''' Also adds the LSP base content type to ensure the LSP client activates On VB files. + ''' From Microsoft.VisualStudio.LanguageServer.Client.CodeRemoteContentDefinition.CodeRemoteBaseTypeName + ''' We cannot directly reference the LSP client package in EditorFeatures as it is a VS dependency. ''' + Public ReadOnly VisualBasicContentTypeDefinition As ContentTypeDefinition diff --git a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb index 81f3d9f4fd8e6..09b229ab96a33 100644 --- a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb @@ -3,10 +3,9 @@ ' See the LICENSE file in the project root for more information. Imports System.ComponentModel.Composition -Imports System.Diagnostics.CodeAnalysis +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Editor.Host Imports Microsoft.CodeAnalysis.Editor.Implementation.DocumentationComments -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Commanding Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion Imports Microsoft.VisualStudio.Text.Operations @@ -19,16 +18,16 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.DocumentationComments Friend Class DocumentationCommentCommandHandler - Inherits AbstractDocumentationCommentCommandHandler(Of DocumentationCommentTriviaSyntax, DeclarationStatementSyntax) + Inherits AbstractDocumentationCommentCommandHandler - - + + Public Sub New( - waitIndicator As IWaitIndicator, + uiThreadOperationExecutor As IUIThreadOperationExecutor, undoHistoryRegistry As ITextUndoHistoryRegistry, editorOperationsFactoryService As IEditorOperationsFactoryService) - MyBase.New(waitIndicator, undoHistoryRegistry, editorOperationsFactoryService) + MyBase.New(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService) End Sub Protected Overrides ReadOnly Property ExteriorTriviaText As String diff --git a/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb b/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb index c2e7413f2a293..7d82719bb8af4 100644 --- a/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb +++ b/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb @@ -4,7 +4,6 @@ Imports System.Composition Imports Microsoft.CodeAnalysis.Editor.FindUsages -Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Host.Mef Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.FindUsages diff --git a/src/EditorFeatures/VisualBasic/InheritanceMargin/VisualBasicInheritanceMarginService.vb b/src/EditorFeatures/VisualBasic/InheritanceMargin/VisualBasicInheritanceMarginService.vb new file mode 100644 index 0000000000000..39eb6d82cfd05 --- /dev/null +++ b/src/EditorFeatures/VisualBasic/InheritanceMargin/VisualBasicInheritanceMarginService.vb @@ -0,0 +1,102 @@ +' 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.Collections.Immutable +Imports System.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.InheritanceMargin +Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.InheritanceMarginService + + + Friend Class VisualBasicInheritanceMarginService + Inherits AbstractInheritanceMarginService + + + + Public Sub New() + End Sub + + Protected Overrides Function GetMembers(nodesToSearch As IEnumerable(Of SyntaxNode)) As ImmutableArray(Of SyntaxNode) + Dim typeBlockNodes = nodesToSearch.OfType(Of TypeBlockSyntax) + + Dim builder As ArrayBuilder(Of SyntaxNode) = Nothing + Using ArrayBuilder(Of SyntaxNode).GetInstance(builder) + builder.AddRange(typeBlockNodes.Select(Function(node) node.BlockStatement)) + + For Each node In typeBlockNodes + For Each member In node.Members + 'Sub and Function + Dim methodBlockNode = TryCast(member, MethodBlockSyntax) + If methodBlockNode IsNot Nothing Then + builder.Add(methodBlockNode.BlockStatement) + End If + + Dim methodStatementNode = TryCast(member, MethodStatementSyntax) + If methodStatementNode IsNot Nothing Then + builder.Add(methodStatementNode) + End If + + 'Property + Dim propertyBlockNode = TryCast(member, PropertyBlockSyntax) + If propertyBlockNode IsNot Nothing Then + builder.Add(propertyBlockNode.PropertyStatement) + End If + + Dim propertyStatementNode = TryCast(member, PropertyStatementSyntax) + If propertyStatementNode IsNot Nothing Then + builder.Add(propertyStatementNode) + End If + + 'Custom event + Dim eventDeclarationNode = TryCast(member, EventBlockSyntax) + If eventDeclarationNode IsNot Nothing Then + builder.Add(eventDeclarationNode.EventStatement) + End If + + 'One line event + If TypeOf member Is EventStatementSyntax Then + builder.Add(member) + End If + Next + Next + + Return builder.ToImmutable() + End Using + End Function + + Protected Overrides Function GetDeclarationToken(declarationNode As SyntaxNode) As SyntaxToken + + Dim typeStatementNode = TryCast(declarationNode, TypeStatementSyntax) + If typeStatementNode IsNot Nothing Then + Return typeStatementNode.Identifier + End If + + Dim propertyStatementNode = TryCast(declarationNode, PropertyStatementSyntax) + If propertyStatementNode IsNot Nothing Then + Return propertyStatementNode.Identifier + End If + + Dim eventBlockNode = TryCast(declarationNode, EventBlockSyntax) + If eventBlockNode IsNot Nothing Then + Return eventBlockNode.EventStatement.Identifier + End If + + Dim eventStatementNode = TryCast(declarationNode, EventStatementSyntax) + If eventStatementNode IsNot Nothing Then + Return eventStatementNode.Identifier + End If + + Dim methodStatementNode = TryCast(declarationNode, MethodStatementSyntax) + If methodStatementNode IsNot Nothing Then + Return methodStatementNode.Identifier + End If + + Throw ExceptionUtilities.UnexpectedValue(declarationNode) + End Function + End Class +End Namespace + diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb index 4b52bc55b7868..f0d92246d9f49 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb @@ -7,6 +7,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities +Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions @@ -228,7 +229,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ' Do the paste in the same transaction as the commit/format nextHandler() - If Not args.SubjectBuffer.GetFeatureOnOffOption(FeatureOnOffOptions.FormatOnPaste) Then + If Not args.SubjectBuffer.GetFeatureOnOffOption(FormattingOptions2.FormatOnPaste) Then transaction.Complete() Return End If diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitConnectionListener.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitConnectionListener.vb index 09de868a68017..4c70da37830a2 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitConnectionListener.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitConnectionListener.vb @@ -21,24 +21,24 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Private ReadOnly _commitBufferManagerFactory As CommitBufferManagerFactory Private ReadOnly _textBufferAssociatedViewService As ITextBufferAssociatedViewService Private ReadOnly _textUndoHistoryRegistry As ITextUndoHistoryRegistry - Private ReadOnly _waitIndicator As IWaitIndicator + Private ReadOnly _uiThreadOperationExecutor As IUIThreadOperationExecutor Public Sub New(commitBufferManagerFactory As CommitBufferManagerFactory, textBufferAssociatedViewService As ITextBufferAssociatedViewService, textUndoHistoryRegistry As ITextUndoHistoryRegistry, - waitIndicator As IWaitIndicator) + uiThreadOperationExecutor As IUIThreadOperationExecutor) _commitBufferManagerFactory = commitBufferManagerFactory _textBufferAssociatedViewService = textBufferAssociatedViewService _textUndoHistoryRegistry = textUndoHistoryRegistry - _waitIndicator = waitIndicator + _uiThreadOperationExecutor = uiThreadOperationExecutor End Sub Public Sub SubjectBuffersConnected(view As ITextView, reason As ConnectionReason, subjectBuffers As IReadOnlyCollection(Of ITextBuffer)) Implements ITextViewConnectionListener.SubjectBuffersConnected ' Make sure we have a view manager view.Properties.GetOrCreateSingletonProperty( - Function() New CommitViewManager(view, _commitBufferManagerFactory, _textBufferAssociatedViewService, _textUndoHistoryRegistry, _waitIndicator)) + Function() New CommitViewManager(view, _commitBufferManagerFactory, _textBufferAssociatedViewService, _textUndoHistoryRegistry, _uiThreadOperationExecutor)) ' Connect to each of these buffers, and increment their ref count For Each buffer In subjectBuffers diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb index b6fceb98ab7d1..ed7467677a2af 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb @@ -4,26 +4,23 @@ Imports System.Collections.Immutable Imports System.ComponentModel.Composition -Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.CodeCleanup Imports Microsoft.CodeAnalysis.CodeCleanup.Providers -Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Indentation Imports Microsoft.CodeAnalysis.Internal.Log Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Text -Imports Microsoft.VisualStudio.Text.Editor Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Friend Class CommitFormatter Implements ICommitFormatter - Private ReadOnly _indentationManagerService As IIndentationManagerService - Private Shared ReadOnly s_codeCleanupPredicate As Func(Of ICodeCleanupProvider, Boolean) = Function(p) Return p.Name <> PredefinedCodeCleanupProviderNames.Simplification AndAlso @@ -31,9 +28,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End Function - - Public Sub New(indentationManagerService As IIndentationManagerService) - _indentationManagerService = indentationManagerService + + Public Sub New() End Sub Public Sub CommitRegion(spanToFormat As SnapshotSpan, @@ -60,7 +56,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Return End If - Dim documentOptions = document.GetDocumentOptionsWithInferredIndentationAsync(isExplicitFormat, _indentationManagerService, cancellationToken).WaitAndGetResult(cancellationToken) + Dim inferredIndentationService = document.Project.Solution.Workspace.Services.GetRequiredService(Of IInferredIndentationService)() + Dim documentOptions = inferredIndentationService.GetDocumentOptionsWithInferredIndentationAsync(document, isExplicitFormat, cancellationToken).WaitAndGetResult(cancellationToken) If Not (isExplicitFormat OrElse documentOptions.GetOption(FeatureOnOffOptions.PrettyListing)) Then Return End If diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitViewManager.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitViewManager.vb index 95b2d6884e4f5..abb5a9410878f 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitViewManager.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitViewManager.vb @@ -3,10 +3,10 @@ ' See the LICENSE file in the project root for more information. Imports System.Threading -Imports Microsoft.CodeAnalysis.Editor.Host Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Operations +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ''' @@ -17,18 +17,18 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Private ReadOnly _commitBufferManagerFactory As CommitBufferManagerFactory Private ReadOnly _textBufferAssociatedViewService As ITextBufferAssociatedViewService Private ReadOnly _textUndoHistoryRegistry As ITextUndoHistoryRegistry - Private ReadOnly _waitIndicator As IWaitIndicator + Private ReadOnly _uiThreadOperationExecutor As IUIThreadOperationExecutor Public Sub New(view As ITextView, commitBufferManagerFactory As CommitBufferManagerFactory, textBufferAssociatedViewService As ITextBufferAssociatedViewService, textUndoHistoryRegistry As ITextUndoHistoryRegistry, - waitIndicator As IWaitIndicator) + uiThreadOperationExecutor As IUIThreadOperationExecutor) _view = view _commitBufferManagerFactory = commitBufferManagerFactory _textBufferAssociatedViewService = textBufferAssociatedViewService _textUndoHistoryRegistry = textUndoHistoryRegistry - _waitIndicator = waitIndicator + _uiThreadOperationExecutor = uiThreadOperationExecutor AddHandler _view.Caret.PositionChanged, AddressOf OnCaretPositionChanged AddHandler _view.LostAggregateFocus, AddressOf OnLostAggregateFocus @@ -52,22 +52,23 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Dim oldBuffer = oldSnapshotPoint.Value.Snapshot.TextBuffer Dim newBuffer = If(newSnapshotPoint.HasValue, newSnapshotPoint.Value.Snapshot.TextBuffer, Nothing) - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( VBEditorResources.Line_commit, VBEditorResources.Committing_line, - allowCancel:=True, + allowCancellation:=True, + showProgress:=False, action:= - Sub(waitContext) + Sub(context) ' If our buffers changed, then we commit the old one If oldBuffer IsNot newBuffer Then - CommitBufferForCaretMovement(oldBuffer, e, waitContext.CancellationToken) + CommitBufferForCaretMovement(oldBuffer, e, context.UserCancellationToken) Return End If ' We're in the same snapshot. Are we on the same line? Dim commitBufferManager = _commitBufferManagerFactory.CreateForBuffer(newBuffer) - If CommitBufferManager.IsMovementBetweenStatements(oldSnapshotPoint.Value, newSnapshotPoint.Value, waitContext.CancellationToken) Then - CommitBufferForCaretMovement(oldBuffer, e, waitContext.CancellationToken) + If CommitBufferManager.IsMovementBetweenStatements(oldSnapshotPoint.Value, newSnapshotPoint.Value, context.UserCancellationToken) Then + CommitBufferForCaretMovement(oldBuffer, e, context.UserCancellationToken) End If End Sub) End Sub @@ -112,14 +113,15 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End Sub Private Sub OnLostAggregateFocus(sender As Object, e As EventArgs) - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( "Commit", VBEditorResources.Committing_line, - allowCancel:=True, + allowCancellation:=True, + showProgress:=False, action:= - Sub(waitContext) + Sub(context) For Each buffer In _view.BufferGraph.GetTextBuffers(Function(b) b.ContentType.IsOfType(ContentTypeNames.VisualBasicContentType)) - _commitBufferManagerFactory.CreateForBuffer(buffer).CommitDirty(isExplicitFormat:=False, cancellationToken:=waitContext.CancellationToken) + _commitBufferManagerFactory.CreateForBuffer(buffer).CommitDirty(isExplicitFormat:=False, cancellationToken:=context.UserCancellationToken) Next End Sub) End Sub diff --git a/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj index 94cea75a54fde..9a3cb1ca4f787 100644 --- a/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj @@ -43,7 +43,7 @@ - + diff --git a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb index 306723ee04c69..6a5f91f5f1e72 100644 --- a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb +++ b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb @@ -3,15 +3,15 @@ ' See the LICENSE file in the project root for more information. Imports System.Composition +Imports System.Runtime.CompilerServices Imports System.Threading -Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities Imports Microsoft.CodeAnalysis.Host.Mef -Imports Microsoft.CodeAnalysis.NavigationBar Imports Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Operations @@ -27,59 +27,54 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar - Public Sub New(editorOperationsFactoryService As IEditorOperationsFactoryService, textUndoHistoryRegistry As ITextUndoHistoryRegistry) + Public Sub New( + threadingContext As IThreadingContext, + editorOperationsFactoryService As IEditorOperationsFactoryService, + textUndoHistoryRegistry As ITextUndoHistoryRegistry) + MyBase.New(threadingContext) _editorOperationsFactoryService = editorOperationsFactoryService _textUndoHistoryRegistry = textUndoHistoryRegistry End Sub - Public Overrides Function ShowItemGrayedIfNear(item As NavigationBarItem) As Boolean - ' We won't show gray things that don't actually exist - Return TypeOf DirectCast(item, WrappedNavigationBarItem).UnderlyingItem Is SymbolItem - End Function - - Protected Overrides Function GetSymbolNavigationPoint(document As Document, symbol As ISymbol, cancellationToken As CancellationToken) As VirtualTreePoint? - Dim location As Location = GetSourceNavigationLocation(document, symbol, cancellationToken) - If location Is Nothing Then - Return Nothing - End If - - ' If the symbol is a method symbol, we'll figure out the right location which may be in - ' virtual space - If symbol.Kind = SymbolKind.Method Then - Dim methodBlock = location.FindToken(cancellationToken).GetAncestor(Of MethodBlockBaseSyntax)() - - If methodBlock IsNot Nothing Then - Return NavigationPointHelpers.GetNavigationPoint(location.SourceTree.GetText(cancellationToken), 4, methodBlock) - End If - End If + Friend Overrides Async Function GetNavigationLocationAsync( + document As Document, + item As NavigationBarItem, + symbolItem As SymbolItem, + textSnapshot As ITextSnapshot, + cancellationToken As CancellationToken) As Task(Of (documentId As DocumentId, position As Integer, virtualSpace As Integer)) - Return New VirtualTreePoint(location.SourceTree, location.SourceTree.GetText(cancellationToken), location.SourceSpan.Start) - End Function + Dim navigationLocation = Await MyBase.GetNavigationLocationAsync(document, item, symbolItem, textSnapshot, cancellationToken).ConfigureAwait(False) - Private Shared Function GetSourceNavigationLocation(document As Document, symbol As ISymbol, cancellationToken As CancellationToken) As Location - Dim sourceLocations = symbol.Locations.Where(Function(l) l.IsInSource) + Dim destinationDocument = document.Project.Solution.GetDocument(navigationLocation.documentId) - ' First figure out the location that we want to grab considering partial types - Dim syntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken) - Dim location = sourceLocations.FirstOrDefault(Function(l) l.SourceTree.Equals(syntaxTree)) + Dim root = Await destinationDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) - If location Is Nothing Then - location = sourceLocations.FirstOrDefault + ' If the symbol is a method symbol, we'll figure out the right location which may be in virtual space + Dim methodBlock = root.FindToken(navigationLocation.position).GetAncestor(Of MethodBlockBaseSyntax)() + If methodBlock IsNot Nothing Then + Dim text = Await destinationDocument.GetTextAsync(cancellationToken).ConfigureAwait(False) + Dim navPoint = NavigationPointHelpers.GetNavigationPoint(text, indentSize:=4, methodBlock) + Return (navigationLocation.documentId, navPoint.Position, navPoint.VirtualSpaces) End If - Return location + Return navigationLocation End Function - Protected Overrides Sub NavigateToItem(document As Document, item As WrappedNavigationBarItem, textView As ITextView, cancellationToken As CancellationToken) + Protected Overrides Async Function TryNavigateToItemAsync( + document As Document, item As WrappedNavigationBarItem, textView As ITextView, textSnapshot As ITextSnapshot, cancellationToken As CancellationToken) As Task(Of Boolean) Dim underlying = item.UnderlyingItem Dim generateCodeItem = TryCast(underlying, AbstractGenerateCodeItem) Dim symbolItem = TryCast(underlying, SymbolItem) If generateCodeItem IsNot Nothing Then - GenerateCodeForItem(document, generateCodeItem, textView, cancellationToken) + Await GenerateCodeForItemAsync(document, generateCodeItem, textView, cancellationToken).ConfigureAwait(False) + Return True ElseIf symbolItem IsNot Nothing Then - NavigateToSymbolItem(document, symbolItem, cancellationToken) + Await NavigateToSymbolItemAsync(document, item, symbolItem, textSnapshot, cancellationToken).ConfigureAwait(False) + Return True End If - End Sub + + Return False + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb index 043999dfd4adf..8b1ddd6f02b20 100644 --- a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb +++ b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb @@ -20,24 +20,32 @@ Imports Microsoft.VisualStudio.Text.Editor Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Partial Friend Class VisualBasicEditorNavigationBarItemService - Private Sub GenerateCodeForItem(document As Document, generateCodeItem As AbstractGenerateCodeItem, textView As ITextView, cancellationToken As CancellationToken) + Private Async Function GenerateCodeForItemAsync(document As Document, generateCodeItem As AbstractGenerateCodeItem, textView As ITextView, cancellationToken As CancellationToken) As Task ' We'll compute everything up front before we go mutate state - Dim text = document.GetTextSynchronously(cancellationToken) - Dim newDocument = GetGeneratedDocumentAsync(document, generateCodeItem, cancellationToken).WaitAndGetResult(cancellationToken) - Dim generatedTree = newDocument.GetSyntaxRootSynchronously(cancellationToken) + Dim text = Await document.GetTextAsync(cancellationToken).ConfigureAwait(False) + Dim newDocument = Await GetGeneratedDocumentAsync(document, generateCodeItem, cancellationToken).ConfigureAwait(False) + Dim generatedTree = Await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim generatedNode = generatedTree.GetAnnotatedNodes(GeneratedSymbolAnnotation).Single().FirstAncestorOrSelf(Of MethodBlockBaseSyntax) - Dim documentOptions = document.GetOptionsAsync(cancellationToken).WaitAndGetResult(cancellationToken) + Dim documentOptions = Await document.GetOptionsAsync(cancellationToken).ConfigureAwait(False) Dim indentSize = documentOptions.GetOption(FormattingOptions.IndentationSize) + Dim navigationPoint = NavigationPointHelpers.GetNavigationPoint(generatedTree.GetText(text.Encoding), indentSize, generatedNode) + ' switch back to ui thread to actually perform the application and navigation + Await Me.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken) + Using transaction = New CaretPreservingEditTransaction(VBEditorResources.Generate_Member, textView, _textUndoHistoryRegistry, _editorOperationsFactoryService) newDocument.Project.Solution.Workspace.ApplyDocumentChanges(newDocument, cancellationToken) - NavigateToVirtualTreePoint(newDocument.Project.Solution, navigationPoint, cancellationToken) + Dim solution = newDocument.Project.Solution + NavigateToPosition( + solution.Workspace, solution.GetRequiredDocument(navigationPoint.Tree).Id, + navigationPoint.Position, navigationPoint.VirtualSpaces, cancellationToken) transaction.Complete() End Using - End Sub + End Function Public Shared Async Function GetGeneratedDocumentAsync(document As Document, generateCodeItem As RoslynNavigationBarItem, cancellationToken As CancellationToken) As Task(Of Document) Dim syntaxTree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False) @@ -49,7 +57,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Return document End If - newDocument = Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, Nothing, cancellationToken).WaitAndGetResult(cancellationToken) + newDocument = Await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, Nothing, cancellationToken).ConfigureAwait(False) Dim formatterRules = Formatter.GetDefaultFormattingRules(newDocument) If ShouldApplyLineAdjustmentFormattingRule(generateCodeItem) Then @@ -57,11 +65,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar End If Dim documentOptions = Await newDocument.GetOptionsAsync(cancellationToken).ConfigureAwait(False) - Return Formatter.FormatAsync(newDocument, - Formatter.Annotation, - options:=documentOptions, - cancellationToken:=cancellationToken, - rules:=formatterRules).WaitAndGetResult(cancellationToken) + Return Await Formatter.FormatAsync( + newDocument, + Formatter.Annotation, + options:=documentOptions, + cancellationToken:=cancellationToken, + rules:=formatterRules).ConfigureAwait(False) End Function Private Shared Function ShouldApplyLineAdjustmentFormattingRule(generateCodeItem As RoslynNavigationBarItem) As Boolean diff --git a/src/EditorFeatures/VisualBasic/TextStructureNavigation/TextStructureNavigatorProvider.vb b/src/EditorFeatures/VisualBasic/TextStructureNavigation/TextStructureNavigatorProvider.vb index 2137ff4f69154..9549cc060db46 100644 --- a/src/EditorFeatures/VisualBasic/TextStructureNavigation/TextStructureNavigatorProvider.vb +++ b/src/EditorFeatures/VisualBasic/TextStructureNavigation/TextStructureNavigatorProvider.vb @@ -22,8 +22,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.TextStructureNavigation Public Sub New( selectorService As ITextStructureNavigatorSelectorService, contentTypeService As IContentTypeRegistryService, - waitIndicator As IWaitIndicator) - MyBase.New(selectorService, contentTypeService, waitIndicator) + uiThreadOperationExecutor As IUIThreadOperationExecutor) + MyBase.New(selectorService, contentTypeService, uiThreadOperationExecutor) End Sub Protected Overrides Function ShouldSelectEntireTriviaFromStart(trivia As SyntaxTrivia) As Boolean diff --git a/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb b/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb index 884a0d78f7ad6..9cebc9f7f20b8 100644 --- a/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb +++ b/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.Editor.VisualBasic.AutomaticEndConstructCorrectio Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.VisualStudio.Text +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.AutomaticEndConstructCorrection <[UseExportProvider]> @@ -358,7 +359,7 @@ End Class.Value Dim caretPosition = initialTextSnapshot.CreateTrackingPoint(document.CursorPosition.Value, PointTrackingMode.Positive, TrackingFidelityMode.Backward) - Dim corrector = New AutomaticEndConstructCorrector(buffer, New TestWaitIndicator()) + Dim corrector = New AutomaticEndConstructCorrector(buffer, workspace.GetService(Of IUIThreadOperationExecutor)) corrector.Connect() @@ -408,7 +409,7 @@ End Class.Value Dim spanToReplace = selectedSpans.First() Dim spanToVerify = selectedSpans.Skip(1).Single() - Verify(document, keyword, expected, spanToReplace, spanToVerify) + Verify(document, keyword, expected, spanToReplace, spanToVerify, workspace) End Using End Sub @@ -421,13 +422,14 @@ End Class.Value Dim spanToReplace = selectedSpans.Skip(1).Single() Dim spanToVerify = selectedSpans.First() - Verify(document, keyword, expected, spanToReplace, spanToVerify) + Verify(document, keyword, expected, spanToReplace, spanToVerify, workspace) End Using End Sub - Private Shared Sub Verify(document As TestHostDocument, keyword As String, expected As String, spanToReplace As TextSpan, spanToVerify As TextSpan) + Private Shared Sub Verify(document As TestHostDocument, keyword As String, expected As String, spanToReplace As TextSpan, spanToVerify As TextSpan, workspace As TestWorkspace) Dim buffer = document.GetTextBuffer() - Dim corrector = New AutomaticEndConstructCorrector(buffer, New TestWaitIndicator()) + Dim uiThreadOperationExecutor = workspace.GetService(Of IUIThreadOperationExecutor) + Dim corrector = New AutomaticEndConstructCorrector(buffer, uiThreadOperationExecutor) corrector.Connect() buffer.Replace(spanToReplace.ToSpan(), keyword) diff --git a/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb b/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb index 53eceeb6438fc..c7fa03533f9ea 100644 --- a/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb @@ -28,7 +28,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.BraceMatching Dim producer = New BraceHighlightingViewTaggerProvider( workspace.ExportProvider.GetExportedValue(Of IThreadingContext), workspace.GetService(Of IBraceMatchingService), - workspace.GetService(Of IForegroundNotificationService), AsynchronousOperationListenerProvider.NullProvider) Dim doc = buffer.CurrentSnapshot.GetRelatedDocumentsWithChanges().FirstOrDefault() diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb new file mode 100644 index 0000000000000..c55b3c42b7b87 --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceParameter/IntroduceParameterTests.vb @@ -0,0 +1,936 @@ +' 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.Collections.Immutable +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings.IntroduceParameter + Public Class IntroduceParameterTests + Inherits AbstractVisualBasicCodeActionTest + + Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider + Return New VisualBasicIntroduceParameterCodeRefactoringProvider() + End Function + + Protected Overrides Function MassageActions(actions As ImmutableArray(Of CodeAction)) As ImmutableArray(Of CodeAction) + Return GetNestedActions(actions) + End Function + + + Public Async Function TestExpressionWithNoMethodCallsCase() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithLocal() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim l As Integer = 5 + Dim num As Integer = [|l * x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestBasicComplexExpressionCase() As Task + Dim source = +"Class Program + Sub M(x As String, y As Integer, z As Integer) + Dim num As Integer = [|x.Length * y * z|] + End Sub + + Sub M1(y As String) + M(y, 5, 2) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As String, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(y As String) + M(y, 5, 2, y.Length * 5 * 2) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithSingleMethodCall() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x, z * y * x) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithSingleMethodCallMultipleDeclarators() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num = [|x * y * z|], y = 0 + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestHighlightIncompleteExpressionCaseWithSingleMethodCall() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = 5 * [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestExpressionCaseWithMultipleMethodCall() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + M(a + b, 5, x) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x, z * y * x) + M(a + b, 5, x, (a + b) * 5 * x) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionAllOccurrences() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num2 As Integer = x * y * z + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + Dim num2 As Integer = num + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x, z * y * x) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=3) + End Function + + + Public Async Function TestExpressionWithNoMethodCallsTrampoline() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, y As Integer, z As Integer) As Integer + Return x * y * z + End Function + + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallTrampoline() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, y As Integer, z As Integer) As Integer + Return x * y * z + End Function + + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x, GetNum(z, y, x)) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallTrampolineAllOccurrences() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + Dim num2 As Integer = x * y * z + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, y As Integer, z As Integer) As Integer + Return x * y * z + End Function + + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + Dim num2 As Integer = num + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x, GetNum(z, y, x)) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=4) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallAndAccessorsTrampoline() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + Me.M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, y As Integer, z As Integer) As Integer + Return x * y * z + End Function + + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + Me.M(z, y, x, GetNum(z, y, x)) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallAndAccessorsConditionalTrampoline() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + Me?.M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, y As Integer, z As Integer) As Integer + Return x * y * z + End Function + + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + Me?.M(z, y, x, Me?.GetNum(z, y, x)) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallMultipleAccessorsTrampoline() As Task + Dim source = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a.Prop.ComputeAge(5, 5) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Function ComputeAge(x As Integer, y As Integer) As Integer + Dim age = [|x + y|] + Return age + End Function +End Class" + Dim expected = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a.Prop.ComputeAge(5, 5, a.Prop.GetAge(5, 5)) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Public Function GetAge(x As Integer, y As Integer) As Integer + Return x + y + End Function + + Function ComputeAge(x As Integer, y As Integer, age As Integer) As Integer + Return age + End Function +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallMultipleAccessorsConditionalTrampoline() As Task + Dim source = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a?.Prop?.ComputeAge(5, 5) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Function ComputeAge(x As Integer, y As Integer) As Integer + Dim age = [|x + y|] + Return age + End Function +End Class" + Dim expected = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a?.Prop?.ComputeAge(5, 5, a?.Prop?.GetAge(5, 5)) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Public Function GetAge(x As Integer, y As Integer) As Integer + Return x + y + End Function + + Function ComputeAge(x As Integer, y As Integer, age As Integer) As Integer + Return age + End Function +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallAccessorsMixedConditionalTrampoline() As Task + Dim source = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a.Prop?.ComputeAge(5, 5) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Function ComputeAge(x As Integer, y As Integer) As Integer + Dim age = [|x + y|] + Return age + End Function +End Class" + Dim expected = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a.Prop?.ComputeAge(5, 5, a.Prop?.GetAge(5, 5)) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Public Function GetAge(x As Integer, y As Integer) As Integer + Return x + y + End Function + + Function ComputeAge(x As Integer, y As Integer, age As Integer) As Integer + Return age + End Function +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithSingleMethodCallAccessorsMixedConditionalTrampoline2() As Task + Dim source = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a?.Prop.ComputeAge(5, 5) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Function ComputeAge(x As Integer, y As Integer) As Integer + Dim age = [|x + y|] + Return age + End Function +End Class" + Dim expected = +"Class TestClass + Sub Main(args As String()) + Dim a = New A() + a?.Prop.ComputeAge(5, 5, a?.Prop.GetAge(5, 5)) + End Sub +End Class + +Class A + Public Prop As B +End Class + +Class B + Public Function GetAge(x As Integer, y As Integer) As Integer + Return x + y + End Function + + Function ComputeAge(x As Integer, y As Integer, age As Integer) As Integer + Return age + End Function +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionWithNoMethodCallOverload() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + Dim expected = +"Class Program + Public Sub M(x As Integer, y As Integer, z As Integer) + M(x, y, z, x * y * z) + End Sub + + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + End Sub + + Sub M1(x As Integer, y As Integer, z As Integer) + M(z, y, x) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=2) + End Function + + + Public Async Function TestExpressionCaseWithRecursiveCall() As Task + Dim source = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer) + Dim num As Integer = [|x * y * z|] + M(x, x, z) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, y As Integer, z As Integer, num As Integer) + M(x, x, z, x * x * z) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithNestedRecursiveCall() As Task + Dim source = +"Class Program + Function M(x As Integer, y As Integer, z As Integer) As Integer + Dim num As Integer = [|x * y * z|] + return M(x, x, M(x, y, z)) + End Function +End Class" + Dim expected = +"Class Program + Function M(x As Integer, y As Integer, z As Integer, num As Integer) As Integer + return M(x, x, M(x, y, z, x * y * z), x * x * M(x, y, z, x * y * z)) + End Function +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithParamsArg() As Task + Dim source = +"Class Program + Function M(ParamArray args() As Integer) As Integer + Dim num As Integer = [|args(0) + args(1)|] + Return num + End Function +End Class" + + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestExpressionCaseWithOptionalParameters() As Task + Dim source = +"Class Program + Sub M(x As Integer, Optional y As Integer = 5) + Dim num As Integer = [|x * y|] + End Sub + + Sub M1() + M(7, 2) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, num As Integer, Optional y As Integer = 5) + End Sub + + Sub M1() + M(7, 7 * 2, 2) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithOptionalParametersUsed() As Task + Dim source = +"Class Program + Sub M(x As Integer, Optional y As Integer = 5) + Dim num As Integer = [|x * y|] + End Sub + + Sub M1() + M(7) + End Sub +End Class" + Dim expected = +"Class Program + Sub M(x As Integer, num As Integer, Optional y As Integer = 5) + End Sub + + Sub M1() + M(7, 7 * 5) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionCaseWithOptionalParametersUsedOverload() As Task + Dim source = +"Class Program + Function M(x As Integer, Optional y As Integer = 5) As Integer + Dim num As Integer = [|x * y|] + Return num + End Function + + Sub M1() + Dim x = M(7) + End Sub +End Class" + Dim expected = +"Class Program + Public Function M(x As Integer, Optional y As Integer = 5) As Integer + Return M(x, x * y, y) + End Function + + Function M(x As Integer, num As Integer, Optional y As Integer = 5) As Integer + Return num + End Function + + Sub M1() + Dim x = M(7) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=2) + End Function + + + Public Async Function TestExpressionCaseWithOptionalParametersUsedTrampoline() As Task + Dim source = +"Class Program + Function M(x As Integer, Optional y As Integer = 5) As Integer + Dim num As Integer = [|x * y|] + Return num + End Function + + Sub M1() + Dim x = M(7) + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, Optional y As Integer = 5) As Integer + Return x * y + End Function + + Function M(x As Integer, num As Integer, Optional y As Integer = 5) As Integer + Return num + End Function + + Sub M1() + Dim x = M(7, GetNum(7)) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionCaseWithOptionalParametersUnusedTrampoline() As Task + Dim source = +"Class Program + Function M(x As Integer, Optional y As Integer = 5) As Integer + Dim num As Integer = [|x * y|] + Return num + End Function + + Sub M1() + Dim x = M(7, 2) + End Sub +End Class" + Dim expected = +"Class Program + Public Function GetNum(x As Integer, Optional y As Integer = 5) As Integer + Return x * y + End Function + + Function M(x As Integer, num As Integer, Optional y As Integer = 5) As Integer + Return num + End Function + + Sub M1() + Dim x = M(7, GetNum(7, 2), 2) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestExpressionCaseWithCancellationToken() As Task + Dim source = +"Imports System.Threading +Class Program + Sub M(x As Integer, cancellationToken As CancellationToken) + Dim num As Integer = [|x * x|] + End Sub + + Sub M1(cancellationToken As CancellationToken) + M(7, cancellationToken) + End Sub +End Class" + Dim expected = +"Imports System.Threading +Class Program + Sub M(x As Integer, num As Integer, cancellationToken As CancellationToken) + End Sub + + Sub M1(cancellationToken As CancellationToken) + M(7, 7 * 7, cancellationToken) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestExpressionInConstructor() As Task + Dim source = +"Class Program + Public Sub New(x As Integer, y As Integer) + Dim prod = [|x * y|] + End Sub + + Sub M1() + Dim test As New Program(5, 2) + End Sub +End Class" + Dim expected = +"Class Program + Public Sub New(x As Integer, y As Integer, prod As Integer) + End Sub + + Sub M1() + Dim test As New Program(5, 2, 5 * 2) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + + + Public Async Function TestFieldInitializer() As Task + Dim source = +"Class Program + Public val As Integer = [|5 * 2|] + Public Sub New(x As Integer, y As Integer) + Dim prod = x * y + End Sub + + Sub M1() + Dim test As New Program(5, 2) + End Sub +End Class" + + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestPropertyGetter() As Task + Dim source = +"Class TestClass + Dim seconds As Double + Property Hours() As Double + Get + Return [|seconds / 3600|] + End Get + Set(ByVal Value As Double) + seconds = Value * 3600 + End Set + End Property +End Class" + + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestPropertySetter() As Task + Dim source = +"Class TestClass + Dim seconds As Double + Property Hours() As Double + Get + Return seconds / 3600 + End Get + Set(ByVal Value As Double) + seconds = [|Value * 3600|] + End Set + End Property +End Class" + + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestDestructor() As Task + Dim source = +"Class Program + Protected Overrides Sub Finalize() + Dim prod = [|1 * 5|] + End Sub +End Class" + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestExpressionInParameter() As Task + Dim source = +"Class Program + Public Sub M(Optional y as Integer = [|5 * 5|]) + End Sub +End Class" + + Await TestMissingInRegularAndScriptAsync(source) + End Function + + + Public Async Function TestMeKeywordInExpression() As Task + Dim source = +"Class Program + Dim f As Integer + + Public Sub M(x As Integer) + Dim y = [|Me.f + x|] + End Sub +End Class" + Dim expected = +"Class Program + Dim f As Integer + + Public Function GetY(x As Integer) As Integer + Return Me.f + x + End Function + + Public Sub M(x As Integer, y As Integer) + End Sub +End Class" + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestNamedParameterNecessary() As Task + Dim source = +"Class Program + Function M(x As Integer, Optional y As Integer = 5, Optional z As Integer = 3) As Integer + Dim num As Integer = [|y * z|] + Return num + End Function + + Sub M1() + M(z:=0, y:=2) + End Sub +End Class" + Dim expected = +"Class Program + Function M(x As Integer, num As Integer, Optional y As Integer = 5, Optional z As Integer = 3) As Integer + Return num + End Function + + Sub M1() + M(z:=0, num:=2 * 0, y:=2) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=0) + End Function + + + Public Async Function TestInvocationInWithBlock() As Task + Dim source = +"Class Program + Sub M1() + Dim a = New A() + With a + a.Mult(4, 7) + End With + End Sub +End Class + +Class A + Sub Mult(x As Integer, y As Integer) + Dim m = [|x * y|] + End Sub +End Class" + Dim expected = +"Class Program + Sub M1() + Dim a = New A() + With a + a.Mult(4, 7, a.GetM(4, 7)) + End With + End Sub +End Class + +Class A + Public Function GetM(x As Integer, y As Integer) As Integer + Return x * y + End Function + + Sub Mult(x As Integer, y As Integer, m As Integer) + End Sub +End Class" + + Await TestInRegularAndScriptAsync(source, expected, index:=1) + End Function + End Class +End Namespace + diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb index a27fd5218980c..697bd8ec5bc68 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/IntroduceVariable/IntroduceVariableTests.vb @@ -3229,5 +3229,61 @@ Class C End Class" Await TestMissingAsync(source) End Function + + + + Public Async Function DoNotIntroduceConstantForConstant_Local() As Task + Dim source = " +Class C + Sub Test + Const i As Integer = [|10|] + End Sub +End Class +" + Await TestMissingAsync(source) + End Function + + + + Public Async Function DoNotIntroduceConstantForConstant_Member() As Task + Dim source = " +Class C + Const i As Integer = [|10|] +End Class +" + Await TestMissingAsync(source) + End Function + + + + Public Async Function DoNotIntroduceConstantForConstant_Parentheses() As Task + Dim source = " +Class C + Const i As Integer = ([|10|]) +End Class +" + Await TestMissingAsync(source) + End Function + + + + Public Async Function DoNotIntroduceConstantForConstant_NotForSubExpression() As Task + Dim source = " +Class C + Sub Test + Const i As Integer = [|10|] + 10 + End Sub +End Class +" + Dim expected = " +Class C + Sub Test + Const {|Rename:V|} As Integer = 10 + Const i As Integer = V + 10 + End Sub +End Class +" + Await TestInRegularAndScriptAsync(source, expected, index:=2) + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb index 366e9512c004a..b8cb3a00d3757 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb @@ -377,8 +377,7 @@ End Class If useDebuggerOptions Then options = options. WithChangedOption(CompletionControllerOptions.FilterOutOfScopeLocals, False). - WithChangedOption(CompletionControllerOptions.ShowXmlDocCommentCompletion, False). - WithChangedOption(CompletionServiceOptions.DisallowAddingImports, True) + WithChangedOption(CompletionControllerOptions.ShowXmlDocCommentCompletion, False) End If Dim document1 = workspaceFixture.UpdateDocument(code, SourceCodeKind.Regular) diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests.vb index c817b17977485..20c918526aeac 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests.vb @@ -2480,5 +2480,67 @@ Namespace A End Class End Namespace", testHost, placeSystemFirst:=True) End Function + + + + Public Async Function TestImportIncompleteSub() As Task + Await TestAsync( +"Imports System + +Class A + Dim a As Action = Sub() + Try + Catch ex As [|TestException|] + End Sub +End Class +Namespace T + Class TestException + Inherits Exception + End Class +End Namespace", +"Imports System +Imports T + +Class A + Dim a As Action = Sub() + Try + Catch ex As TestException + End Sub +End Class +Namespace T + Class TestException + Inherits Exception + End Class +End Namespace", TestHost.InProcess) + End Function + + + + Public Async Function TestImportIncompleteSub2() As Task + Await TestAsync( +"Imports System +Imports System.Linq + +Namespace X + Class Test + End Class +End Namespace +Class C + Sub New() + Dim s As Action = Sub() + Dim a = New [|Test|]()", +"Imports System +Imports System.Linq +Imports X + +Namespace X + Class Test + End Class +End Namespace +Class C + Sub New() + Dim s As Action = Sub() + Dim a = New Test()", TestHost.InProcess) + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTestsWithAddImportDiagnosticProvider.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTestsWithAddImportDiagnosticProvider.vb index c329f0143f6a0..b4371d20b1f40 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTestsWithAddImportDiagnosticProvider.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTestsWithAddImportDiagnosticProvider.vb @@ -105,62 +105,6 @@ Class MultiDictionary(Of K, V) End Class") End Function - - - Public Async Function TestImportIncompleteSub() As Task - Await TestAsync( -"Class A - Dim a As Action = Sub() - Try - Catch ex As [|TestException|] - End Sub -End Class -Namespace T - Class TestException - Inherits Exception - End Class -End Namespace", - "Imports T - -Class A - Dim a As Action = Sub() - Try - Catch ex As TestException - End Sub -End Class -Namespace T - Class TestException - Inherits Exception - End Class -End Namespace", TestHost.InProcess) - End Function - - - - Public Async Function TestImportIncompleteSub2() As Task - Await TestAsync( -"Imports System.Linq -Namespace X - Class Test - End Class -End Namespace -Class C - Sub New() - Dim s As Action = Sub() - Dim a = New [|Test|]()", - "Imports System.Linq -Imports X - -Namespace X - Class Test - End Class -End Namespace -Class C - Sub New() - Dim s As Action = Sub() - Dim a = New Test()", TestHost.InProcess) - End Function - Public Async Function TestMissingDiagnosticForNameOf() As Task diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/FullyQualify/FullyQualifyTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/FullyQualify/FullyQualifyTests.vb index 54ba7c795cd79..4655d34635d4f 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/FullyQualify/FullyQualifyTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/FullyQualify/FullyQualifyTests.vb @@ -53,6 +53,7 @@ Namespace SomeNamespace End Namespace") End Function + Public Async Function TestOrdering() As Task Dim code = " namespace System.Windows.Controls @@ -71,7 +72,7 @@ namespace System.Windows.Forms.VisualStyles.VisualStyleElement end namespace Public Class TextBoxEx - Inherits TextBox + Inherits [|TextBox|] End Class" diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb index 7cbe87b9e32c1..bcb75e8d2494a 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb @@ -2,28 +2,20 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics -Imports Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier(Of + Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, + Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase.OverloadBaseCodeFixProvider) Namespace NS Public Class OverloadBaseTests - Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest - - Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (Nothing, New OverloadBaseCodeFixProvider()) - End Function - Public Async Function TestAddOverloadsToProperty() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Property Current As Application End Class Class App : Inherits Application - [|Shared Property Current As App|] + Shared Property {|BC40003:Current|} As App End Class", "Class Application Shared Property Current As Application @@ -35,16 +27,16 @@ End Class") Public Async Function TestAddOverloadsToFunction() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Function Test() As Integer Return 1 End Function End Class Class App : Inherits Application - [|Shared Function Test() As Integer + Shared Function {|BC40003:Test|}() As Integer Return 2 - End Function|] + End Function End Class", "Class Application Shared Function Test() As Integer @@ -60,14 +52,14 @@ End Class") Public Async Function TestAddOverloadsToSub() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Sub Test() End Sub End Class Class App : Inherits Application - [|Shared Sub Test() - End Sub|] + Shared Sub {|BC40003:Test|}() + End Sub End Class", "Class Application Shared Sub Test() @@ -79,15 +71,47 @@ Class App : Inherits Application End Class") End Function + + + Public Async Function TestAddOverloadsToSub_HandlingTrivia() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Class Base + Sub M() + + End Sub +End Class + +Class Derived + Inherits Base + ' Trivia + Sub {|BC40003:M|}() + End Sub ' Trivia2 +End Class +", " +Class Base + Sub M() + + End Sub +End Class + +Class Derived + Inherits Base + ' Trivia + Overloads Sub M() + End Sub ' Trivia2 +End Class +") + End Function + Public Async Function TestAddShadowsToProperty() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Sub Current() End Sub End Class Class App : Inherits Application - [|Shared Property Current As App|] + Shared Property {|BC40004:Current|} As App End Class", "Class Application Shared Sub Current() @@ -100,14 +124,14 @@ End Class") Public Async Function TestAddShadowsToFunction() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Property Test As Integer End Class Class App : Inherits Application - [|Shared Function Test() As Integer + Shared Function {|BC40004:Test|}() As Integer Return 2 - End Function|] + End Function End Class", "Class Application Shared Property Test As Integer @@ -121,13 +145,13 @@ End Class") Public Async Function TestAddShadowsToSub() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Property Test As Integer End Class Class App : Inherits Application - [|Shared Sub Test() - End Sub|] + Shared Sub {|BC40004:Test|}() + End Sub End Class", "Class Application Shared Property Test As Integer diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index ab29de1e29ad4..d110065e51de3 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -1382,7 +1382,7 @@ End Class Public Sub InstancePropertyInitializer_Delete() Dim src1 = " Class C - Property P As Integer = 1 + Property P As Integer = 1 Sub Main Dim c = New C() @@ -1461,6 +1461,34 @@ End Class Diagnostic(RudeEditKind.ActiveStatementUpdate, "d = 3")) End Sub + + Public Sub Initializer_SemanticError() + Dim src1 = " +Class C + Dim a, b = 2 + + Sub Main + Dim a, b = 2 + End Sub +End Class +" + + Dim src2 = " +Class C + Dim a, b = 20 + + Sub Main + Dim a, b = 20 + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + ' Since the code is semantically incorrect it's acceptable to misreport active statements. + edits.VerifyRudeDiagnostics(active) + End Sub + Public Sub FieldInitializer_AsNewToInit() Dim src1 = " @@ -1541,56 +1569,36 @@ End Class Public Sub Initializer_AsNewMulti_Update1() Dim src1 = " Class C - Dim a, b As New D(1) + Dim a, b As New D(1) + Dim c, d As New D(1) + Dim e, f As New D(1) Sub Main - Dim c, d As New D(1) - End Sub -End Class -" - - Dim src2 = " -Class C - Dim a, b As New D(2) - - Sub Main - Dim c, d As New D(2) - End Sub -End Class -" - - Dim edits = GetTopEdits(src1, src2) - Dim active = GetActiveStatements(src1, src2) - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ActiveStatementUpdate, "c")) - End Sub - - - Public Sub Initializer_AsNewMulti_Update2() - Dim src1 = " -Class C - Dim a, b As New D(1) - - Sub Main - Dim c, d As New D(1) - End Sub + Dim x, y As New D(1) + End Sub End Class " Dim src2 = " Class C - Dim a, b As New D(2) + Dim a, b As New D(2) + Dim c, d As New D(2) + Dim e, f As New D(2) Sub Main - Dim c, d As New D(2) - End Sub + Dim x, y As New D(2) + End Sub End Class " Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ActiveStatementUpdate, "d")) + Diagnostic(RudeEditKind.ActiveStatementUpdate, "a"), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "d"), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "e"), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "f"), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "x")) End Sub @@ -1716,6 +1724,37 @@ End Class edits.VerifyRudeDiagnostics(active) End Sub + + Public Sub Initializer_Array_SemanticError() + Dim src1 = " +Class C + Dim a(1), b(2) = 3 + + Sub Main + Dim c(1), d(2) = 3 + End Sub +End Class +" + + Dim src2 = " +Class C + Dim a(10), b(20) = 30 + + Sub Main + Dim c(10), d(20) = 30 + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + ' since the code is semantically incorrect we only care about not crashing + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.ActiveStatementUpdate, "c(10)"), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "d(20)"), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "b(20)")) + End Sub + Public Sub Initializer_Array_Update1() Dim src1 = " @@ -1776,7 +1815,7 @@ End Class Public Sub Initializer_Array_Update3() Dim src1 = " Class C - Private a(1,0) + Private a(1,1) Private e(1,0) Private f(1,0) @@ -1788,7 +1827,7 @@ End Class Dim src2 = " Class C - Private a(1,2) + Private a(1,0) Private e(1,2) Private f(1,2) @@ -1898,7 +1937,7 @@ End Class Dim src2 = " Class C - Private Const a As Integer = 1 + Private Const a As Integer = 1 End Class " @@ -1943,7 +1982,7 @@ End Class Dim src2 = " Class C - Private Const a As Integer = 1, b As Integer = 2 + Private Const a As Integer = 1, b As Integer = 2 End Class " @@ -2347,7 +2386,7 @@ End Class Public Sub PropertyInitializer_Delete_StaticInstanceMix() Dim src1 = " Class C - Shared Property a As Integer = 1 + Shared Property a As Integer = 1 Property b As Integer = 1 End Class " @@ -3261,8 +3300,8 @@ Class C Sub Main() Try Goo() - Catch - End Try + Catch + End Try End Sub Sub Goo() @@ -3327,8 +3366,8 @@ Class C Sub Main() Try Goo() - Catch - End Try + Catch + End Try End Sub Sub Goo() @@ -3537,9 +3576,9 @@ Class C Try Console.WriteLine(1) - Finally + Finally Console.WriteLine(2) - End Try + End Try End Sub End Class " @@ -3667,8 +3706,8 @@ End Class Class C Sub Main() Try - Catch - Goo() + Catch + Goo() End Try End Sub @@ -3706,8 +3745,8 @@ Class C Sub Goo() Try - Catch - Console.WriteLine(1) + Catch + Console.WriteLine(1) End Try End Sub End Class @@ -3735,8 +3774,8 @@ End Class Class C Sub Main() Try - Catch - Goo() + Catch + Goo() End Try End Sub @@ -3750,8 +3789,8 @@ End Class Class C Sub Main() Try - Catch e As IOException - Goo() + Catch e As IOException + Goo() End Try End Sub @@ -3776,8 +3815,8 @@ Class C Sub Goo() Try - Catch - Console.WriteLine(1) + Catch + Console.WriteLine(1) End Try End Sub End Class @@ -3791,9 +3830,9 @@ Class C Sub Goo() Try - Catch e As IOException - Console.WriteLine(1) - End try + Catch e As IOException + Console.WriteLine(1) + End Try End Sub End Class " @@ -3813,8 +3852,8 @@ Class C Sub Goo() Try - Catch e As IOException When Goo(1) - Console.WriteLine(1) + Catch e As IOException When Goo(1) + Console.WriteLine(1) End Try End Sub End Class @@ -3828,8 +3867,8 @@ Class C Sub Goo() Try - Catch e As IOException When Goo(2) - Console.WriteLine(1) + Catch e As IOException When Goo(2) + Console.WriteLine(1) End Try End Sub End Class @@ -3851,8 +3890,8 @@ Class C Sub Goo() Try - Catch e As IOException When Goo(1) - Console.WriteLine(1) + Catch e As IOException When Goo(1) + Console.WriteLine(1) End Try End Sub End Class @@ -3866,9 +3905,9 @@ Class C Sub Goo() Try - Catch e As IOException When Goo(2) + Catch e As IOException When Goo(2) Console.WriteLine(1) - End Try + End Try End Sub End Class " @@ -3888,8 +3927,8 @@ Class C Sub Goo() Try - Catch e As IOException When Goo(1) - Console.WriteLine(1) + Catch e As IOException When Goo(1) + Console.WriteLine(1) End Try End Sub End Class @@ -3903,9 +3942,9 @@ Class C Sub Goo() Try - Catch e As Exception When Goo(1) + Catch e As Exception When Goo(1) Console.WriteLine(1) - End Try + End Try End Sub End Class " @@ -3988,10 +4027,10 @@ End Class Dim src1 = " Class C Sub Main() - Try + Try Finally Goo() - End Try + End Try End Sub Sub Goo() @@ -4026,10 +4065,10 @@ Class C End Sub Sub Goo() - Try + Try Finally Console.WriteLine(1) - End Try + End Try End Sub End Class " @@ -4058,7 +4097,7 @@ End Class Class C Sub Main() Try - Catch e As IOException + Catch e As IOException Try Try Try @@ -4068,7 +4107,7 @@ Class C Catch Exception End Try Finally - End Try + End Try End Try End Sub @@ -4081,7 +4120,7 @@ End Class Class C Sub Main() Try - Catch e As Exception + Catch e As Exception Try Try Finally @@ -4091,7 +4130,7 @@ Class C End Try End Try Catch e As Exception - End Try + End Try End Try End Sub @@ -4202,8 +4241,8 @@ Class C Dim f = Function(x) Try Return 1 + Goo(x) - Catch - End Try + Catch + End Try End Function Console.Write(f(2)) @@ -5579,79 +5618,6 @@ End Class End Sub #End Region -#Region "Unmodified Documents" - - - Public Sub UnmodifiedDocument1() - Dim src1 = " -Module C - Sub Main(args As String()) - Try - Catch As IOException - Goo() - Goo() - End Try - End Sub - - Sub Goo() - Console.WriteLine(1) - End Sub -End Module" - Dim src2 = " -Module C - Sub Main(args As String()) - Try - Catch e As IOException - Goo() - Goo() - End Try - End Sub - - Sub Goo() - Console.WriteLine(1) - End Sub -End Module" - - Dim active = GetActiveStatements(src1, src2) - EditAndContinueValidation.VerifyUnchangedDocument(src2, active) - End Sub - - - Public Sub UnmodifiedDocument_BadSpans1() - Dim src1 = " -Module C - Const a As Integer = 1 - - Sub Main(args As String()) - Goo() - End Sub - - Sub Goo() - Console.WriteLine(1) - End Sub -End Module - - -" - Dim src2 = " -Module C - Const a As Integer = 1 - - Sub Main(args As String()) - Goo() - End Sub - - Sub Goo() - Console.WriteLine(1) - End Sub -End Module" - - Dim active = GetActiveStatements(src1, src2) - EditAndContinueValidation.VerifyUnchangedDocument(src2, active) - End Sub - -#End Region - Public Sub PartiallyExecutedActiveStatement() Dim src1 As String = " @@ -5677,13 +5643,14 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - Dim active = GetActiveStatements(src1, src2) - - active.OldStatements(0) = active.OldStatements(0).WithFlags(ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsLeafFrame) - active.OldStatements(1) = active.OldStatements(1).WithFlags(ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsNonLeafFrame) - active.OldStatements(2) = active.OldStatements(2).WithFlags(ActiveStatementFlags.IsLeafFrame) - active.OldStatements(3) = active.OldStatements(3).WithFlags(ActiveStatementFlags.IsNonLeafFrame) - active.OldStatements(4) = active.OldStatements(4).WithFlags(ActiveStatementFlags.IsNonLeafFrame Or ActiveStatementFlags.IsLeafFrame) + Dim active = GetActiveStatements(src1, src2, + { + ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsLeafFrame, + ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsNonLeafFrame, + ActiveStatementFlags.IsLeafFrame, + ActiveStatementFlags.IsNonLeafFrame, + ActiveStatementFlags.IsNonLeafFrame Or ActiveStatementFlags.IsLeafFrame + }) edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementUpdate, "Console.WriteLine(10)"), @@ -5708,9 +5675,7 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - Dim active = GetActiveStatements(src1, src2) - - active.OldStatements(0) = active.OldStatements(0).WithFlags(ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsLeafFrame) + Dim active = GetActiveStatements(src1, src2, {ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsLeafFrame}) edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementDelete, "Sub F()", FeaturesResources.code)) @@ -5732,9 +5697,7 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - Dim active = GetActiveStatements(src1, src2) - - active.OldStatements(0) = active.OldStatements(0).WithFlags(ActiveStatementFlags.IsNonLeafFrame Or ActiveStatementFlags.IsLeafFrame) + Dim active = GetActiveStatements(src1, src2, {ActiveStatementFlags.IsNonLeafFrame Or ActiveStatementFlags.IsLeafFrame}) edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.DeleteActiveStatement, "Sub F()", FeaturesResources.code)) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb index a056621841930..fd8e2d9f86ae4 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb @@ -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. +Imports System.Collections.Immutable Imports System.Runtime.CompilerServices Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue @@ -12,19 +13,6 @@ Imports Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Friend Module EditAndContinueValidation - - Friend Sub VerifyUnchangedDocument( - source As String, - description As ActiveStatementsDescription) - - Dim validator = New VisualBasicEditAndContinueTestHelpers() - validator.VerifyUnchangedDocument( - ActiveStatementsDescription.ClearTags(source), - description.OldStatements, - description.NewSpans, - description.NewRegions) - End Sub - Friend Sub VerifyRudeDiagnostics(editScript As EditScript(Of SyntaxNode), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) @@ -43,6 +31,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests expectedLineEdits As IEnumerable(Of SourceLineUpdate), expectedNodeUpdates As IEnumerable(Of String), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + Assert.NotEmpty(expectedLineEdits) + + VerifyLineEdits( + editScript, + {New SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, expectedLineEdits.ToImmutableArray())}, + expectedNodeUpdates, + expectedDiagnostics) + End Sub + + + Friend Sub VerifyLineEdits(editScript As EditScript(Of SyntaxNode), + expectedLineEdits As IEnumerable(Of SequencePointUpdates), + expectedNodeUpdates As IEnumerable(Of String), + ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) Dim validator = New VisualBasicEditAndContinueTestHelpers() validator.VerifyLineEdits(editScript, expectedLineEdits, expectedNodeUpdates, expectedDiagnostics) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index 7b1c648a2f606..c2c191621330f 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -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. +Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests @@ -11,6 +12,7 @@ Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests @@ -41,7 +43,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests kind, symbolProvider, If(partialType Is Nothing, Nothing, Function(c As Compilation) CType(c.GetMember(partialType), ITypeSymbol)), - syntaxMap:=Nothing, + syntaxMap, hasSyntaxMap:=syntaxMap IsNot Nothing) End Function @@ -68,9 +70,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Return New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, diagnostics) End Function - Private Shared Function ParseSource(source As String) As SyntaxTree - Dim validator = New VisualBasicEditAndContinueTestHelpers() - Return validator.ParseText(ActiveStatementsDescription.ClearTags(source)) + Private Shared Function ParseSource(markedSource As String) As SyntaxTree + Return SyntaxFactory.ParseSyntaxTree( + ActiveStatementsDescription.ClearTags(markedSource), + VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest), + path:="test.vb") End Function Friend Shared Function GetTopEdits(src1 As String, src2 As String) As EditScript(Of SyntaxNode) @@ -154,12 +158,33 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests End Select End Function - Friend Shared Function GetActiveStatements(oldSource As String, newSource As String) As ActiveStatementsDescription - Return New ActiveStatementsDescription(oldSource, newSource) + Friend Shared Function GetActiveStatements(oldSource As String, newSource As String, Optional flags As ActiveStatementFlags() = Nothing, Optional path As String = "0") As ActiveStatementsDescription + Return New ActiveStatementsDescription(oldSource, newSource, Function(source) SyntaxFactory.ParseSyntaxTree(source, path:=path), flags) End Function Friend Shared Function GetSyntaxMap(oldSource As String, newSource As String) As SyntaxMapDescription Return New SyntaxMapDescription(oldSource, newSource) End Function + + Friend Shared Function GetActiveStatementDebugInfos( + markedSources As String(), + Optional filePaths As String() = Nothing, + Optional methodRowIds As Integer() = Nothing, + Optional modules As Guid() = Nothing, + Optional methodVersions As Integer() = Nothing, + Optional ilOffsets As Integer() = Nothing, + Optional flags As ActiveStatementFlags() = Nothing) As ImmutableArray(Of ManagedActiveStatementDebugInfo) + + Return ActiveStatementsDescription.GetActiveStatementDebugInfos( + Function(source, path) SyntaxFactory.ParseSyntaxTree(source, path:=path), + markedSources, + filePaths, + extension:=".vb", + methodRowIds, + modules, + methodVersions, + ilOffsets, + flags) + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb index 24933351e9c04..648d8e1319522 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb @@ -35,14 +35,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue Public Overrides ReadOnly Property TopSyntaxComparer As TreeComparer(Of SyntaxNode) Get - Return CodeAnalysis.VisualBasic.EditAndContinue.SyntaxComparer.TopLevel + Return SyntaxComparer.TopLevel End Get End Property - Public Overrides Function ParseText(source As String) As SyntaxTree - Return SyntaxFactory.ParseSyntaxTree(source, VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest)) - End Function - Public Overrides Function FindNode(root As SyntaxNode, span As TextSpan) As SyntaxNode Dim result = root.FindToken(span.Start).Parent While result.Span <> span diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb index b97b5bc74b75d..a3b70d11715e1 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb @@ -2,9 +2,12 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Xml.Linq Imports Microsoft.CodeAnalysis.EditAndContinue +Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Emit +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests @@ -12,27 +15,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Public Class LineEditTests Inherits EditingTestBase - Private Shared Function ToCode(element As XElement) As String - Return element.Value.Replace(vbLf, vbCrLf) - End Function - - Private Shared Function ToCode(element As XCData) As String - Return element.Value.Replace(vbLf, vbCrLf) - End Function - #Region "Methods" Public Sub Method_Update1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Bar() Console.ReadLine(1) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub Bar() @@ -40,15 +35,15 @@ Class C Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), Array.Empty(Of String)) End Sub Public Sub Method_Reorder1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Goo() Console.ReadLine(1) @@ -58,9 +53,9 @@ Class C Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub Bar() Console.ReadLine(2) @@ -70,15 +65,20 @@ Class C Console.ReadLine(1) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(2, 6), New SourceLineUpdate(6, 2)}, {}) + edits.VerifyLineEdits( + { + New SourceLineUpdate(2, 6), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(5), + New SourceLineUpdate(6, 2) + }, {}) End Sub Public Sub Method_Reorder2() - Dim src1 = ToCode( + Dim src1 = " Class Program Shared Sub Main() Goo() @@ -93,9 +93,9 @@ Class Program Return 2 End Function End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class Program Shared Function Goo() As Integer Return 1 @@ -110,23 +110,29 @@ Class Program Return 2 End Function End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(2, 6), New SourceLineUpdate(7, 2)}, {}) + edits.VerifyLineEdits( + { + New SourceLineUpdate(2, 6), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(6), + New SourceLineUpdate(7, 2), + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(10) + }, {}) End Sub Public Sub Method_LineChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C @@ -134,7 +140,7 @@ Class C Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 4)}, {}) @@ -142,15 +148,15 @@ End Class Public Sub Method_LineChangeWithLambda1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Bar() F(Function() 1) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C @@ -158,7 +164,7 @@ Class C F(Function() 1) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 4)}, {}) @@ -166,75 +172,75 @@ End Class Public Sub Method_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub _ Bar() Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub _"}) End Sub Public Sub Method_Recompile2() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub Bar()"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar()"}) End Sub - Public Sub Method_Recompile3() - Dim src1 = ToCode( + Public Sub Method_PartialBodyLineUpdate1() + Dim src1 = " Class C Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub Bar()"}) + edits.VerifyLineEdits({New SourceLineUpdate(4, 5)}, {}) End Sub - Public Sub Method_Recompile4() - Dim src1 = ToCode( + Public Sub Method_PartialBodyLineUpdate2() + Dim src1 = " Class C Shared Sub Bar() @@ -242,9 +248,9 @@ Class C Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub Bar() Console.ReadLine(1) @@ -252,10 +258,10 @@ Class C Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub Bar()"}) + edits.VerifyLineEdits({New SourceLineUpdate(4, 3)}, {}) End Sub @@ -280,7 +286,7 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub Bar()"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar()"}) Dim active = GetActiveStatements(src1, src2) Dim syntaxMap = GetSyntaxMap(src1, src2) @@ -291,115 +297,114 @@ End Class Public Sub Method_Recompile6() - Dim src1 = ToCode() +" - Dim src2 = ToCode() +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub Bar() : End Sub"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar() : End Sub"}) End Sub - Public Sub Method_RudeRecompile1() - Dim src1 = ToCode( + Public Sub Method_PartialBodyLineUpdate3() + Dim src1 = " Class C(Of T) Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C(Of T) Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, - {"Shared Sub Bar()"}, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, vbCrLf & " ", FeaturesResources.method)) + edits.VerifyLineEdits({New SourceLineUpdate(4, 3)}, + Array.Empty(Of String)) End Sub Public Sub Method_RudeRecompile2() - Dim src1 = ToCode( + Dim src1 = " Class C(Of T) Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C(Of T) Shared Sub Bar() Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar()"}, Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, vbCrLf & " ", FeaturesResources.method)) End Sub Public Sub Method_RudeRecompile3() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Sub Bar(Of T)() Console.ReadLine(2) End Sub End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Sub Bar(Of T)() Console.ReadLine(2) End Sub End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar(Of T)()"}, Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, vbCrLf & " ", FeaturesResources.method)) End Sub Public Sub Method_RudeRecompile4() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Async Function Bar() As Task(Of Integer) Console.WriteLine(2) End Function End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared Async Function Bar() As Task(Of Integer) Console.WriteLine( 2) End Function End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Async Function Bar() As Task(Of Integer)"}) End Sub @@ -410,43 +415,43 @@ End Class Public Sub Constructor_Recompile1() Dim src1 = -"Class C" & vbCrLf & - "Shared Sub New()" & vbCrLf & - "Console.ReadLine(2)" & vbCrLf & - "End Sub" & vbCrLf & -"End Class" +"Class C + Shared Sub New() + Console.ReadLine(2) + End Sub +End Class" Dim src2 = -"Class C" & vbCrLf & - "Shared Sub _" & vbLf & - "New()" & vbCrLf & - "Console.ReadLine(2)" & vbCrLf & - "End Sub" & vbCrLf & -"End Class" +"Class C + Shared Sub _ + New() + Console.ReadLine(2) + End Sub +End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub _" & vbLf & "New()"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub _"}) End Sub Public Sub Constructor_Recompile2() Dim src1 = -"Class C" & vbCrLf & - "Shared Sub New()" & vbCrLf & - "MyBase.New()" & vbCrLf & - "End Sub" & vbCrLf & -"End Class" +"Class C + Shared Sub New() + MyBase.New() + End Sub +End Class" Dim src2 = -"Class C" & vbCrLf & - "Shared Sub _" & vbLf & - "New()" & vbCrLf & - "MyBase.New()" & vbCrLf & - "End Sub" & vbCrLf & -"End Class" +"Class C + Shared Sub _ + New() + MyBase.New() + End Sub +End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Shared Sub _" & vbLf & "New()"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub _"}) End Sub #End Region @@ -455,114 +460,112 @@ End Class Public Sub Field_Init_Reorder1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared Goo As Integer = 1 Shared Bar As Integer = 2 End Class -) - Dim src2 = ToCode( +" + Dim src2 = " Class C Shared Bar As Integer = 2 Shared Goo As Integer = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3), New SourceLineUpdate(3, 2)}, {}) End Sub Public Sub Field_AsNew_Reorder1() - Dim src1 = ToCode( + Dim src1 = " Class C Shared a As New C() Shared c As New C() End Class -) - Dim src2 = ToCode( +" + Dim src2 = " Class C Shared c As New C() Shared a As New C() End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3), New SourceLineUpdate(3, 2)}, {}) End Sub Public Sub Field_AsNew_Reorder2() - Dim src1 = ToCode( + Dim src1 = " Class C Shared a, b As New C() Shared c, d As New C() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Shared c, d As New C() Shared a, b As New C() End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3), - New SourceLineUpdate(2, 3), - New SourceLineUpdate(3, 2), New SourceLineUpdate(3, 2)}, {}) End Sub Public Sub Field_Init_LineChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 4)}, {}) End Sub Public Sub Field_Init_LineChange2() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim _ Goo = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) End Sub Public Sub Field_AsNew_LineChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim _ Goo As New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) @@ -570,18 +573,18 @@ End Class Public Sub Field_AsNew_LineChange2() - Dim src1 = ToCode( + Dim src1 = " Class C Private Shared Goo As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Private _ Shared Goo As New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) @@ -589,18 +592,18 @@ End Class Public Sub Field_AsNew_LineChange_WithLambda() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo, Bar As New D(Function() 1) End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo, _ Bar As New D(Function() 1) End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {"Goo"}) @@ -608,186 +611,186 @@ End Class Public Sub Field_ArrayInit_LineChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo(1) End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo(1) End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 4)}, {}) End Sub Public Sub Field_ArrayInit_LineChange2() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo(1) End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim _ Goo(1) End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) End Sub Public Sub Field_Init_Recompile1a() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo = _ 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo = _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo = _"}) End Sub Public Sub Field_Init_Recompile1b() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo _ = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo _ "}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo _ "}) End Sub Public Sub Field_Init_Recompile1c() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo ? = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo _ ? = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo _"}) End Sub Public Sub Field_Init_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo = 1"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo = 1"}) End Sub Public Sub Field_Init_Recompile2() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo As Integer = 1 + 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo As Integer = 1 + 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo As Integer = 1 + 1"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo As Integer = 1 + 1"}) End Sub Public Sub Field_SingleAsNew_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo As _ New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo As _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo As _"}) End Sub Public Sub Field_SingleAsNew_Recompile2() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo _ As New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo _"}) End Sub Public Sub Field_MultiAsNew_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo, Bar As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo, _ Bar As New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) @@ -797,94 +800,94 @@ End Class Public Sub Field_MultiAsNew_Recompile2() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo, Bar As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo, Bar As New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) ' we treat "Goo + New D()" as a whole for simplicity - edits.VerifyLineEdits({}, {"Goo", "Bar"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo", "Bar"}) End Sub Public Sub Field_MultiAsNew_Recompile3() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo, Bar As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo, Bar As New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo", "Bar"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo", "Bar"}) End Sub Public Sub Field_MultiAsNew_Recompile4() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo, Bar As New D() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo, Bar As _ New D() End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo", "Bar"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo", "Bar"}) End Sub Public Sub Field_ArrayInit_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Dim Goo(1) End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Dim Goo(1) End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Goo(1)"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo(1)"}) End Sub Public Sub Field_RudeRecompile1() - Dim src1 = ToCode( + Dim src1 = " Class C(Of T) Dim Goo As Integer = 1 + 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C(Of T) Dim Goo As Integer = 1 + 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo As Integer = 1 + 1"}, Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, " ", FeaturesResources.field)) End Sub @@ -893,75 +896,75 @@ End Class #Region "Auto-Properties" Public Sub Property_NoChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 Implements I.P End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As Integer = 1 _ Implements I.P End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {}) End Sub Public Sub PropertyTypeChar_NoChange1() - Dim src1 = ToCode( + Dim src1 = " Class C - Property Goo$ = "" Implements I.P + Property Goo$ = """" Implements I.P End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C - Property Goo$ = "" _ + Property Goo$ = """" _ Implements I.P End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {}) End Sub Public Sub PropertyAsNew_NoChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As New C() Implements I.P End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As New C() _ Implements I.P End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {}) End Sub Public Sub Property_LineChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As Integer = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) @@ -969,18 +972,18 @@ End Class Public Sub Property_LineChange2() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property _ Goo As Integer = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) @@ -988,18 +991,18 @@ End Class Public Sub PropertyTypeChar_LineChange2() - Dim src1 = ToCode( + Dim src1 = " Class C - Property Goo$ = "" + Property Goo$ = """" End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property _ - Goo$ = "" + Goo$ = """" End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) @@ -1007,18 +1010,18 @@ End Class Public Sub PropertyAsNew_LineChange1() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As New C() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property _ Goo As New C() End Class -) +" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {}) @@ -1026,118 +1029,309 @@ End Class Public Sub Property_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo _ As Integer = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Property Goo _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo _"}) End Sub Public Sub Property_Recompile2() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As _ Integer = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Property Goo As _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As _"}) End Sub Public Sub Property_Recompile3() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As Integer _ = 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Property Goo As Integer _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As Integer _"}) End Sub Public Sub Property_Recompile4() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As Integer = 1 End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As Integer = _ 1 End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Property Goo As Integer = _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As Integer = _"}) End Sub Public Sub PropertyAsNew_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C Property Goo As New C() End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo As _ New C() End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Property Goo As _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As _"}) End Sub Public Sub PropertyTypeChar_Recompile1() - Dim src1 = ToCode( + Dim src1 = " Class C - Property Goo$ = "" + Property Goo$ = """" End Class -) +" - Dim src2 = ToCode( + Dim src2 = " Class C Property Goo$ = _ - "" + """" End Class -) +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({}, {"Property Goo$ = _"}) + edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo$ = _"}) End Sub #End Region +#Region "Line Mappings" + + ' + ' Validates that changes in #line directives produce semantic updates of the containing method. + ' + + Public Sub LineMapping_ChangeLineNumber_OutsideOfMethod() + Dim src1 = " +#ExternalSource(""a"", 1) +Class C + Dim x As Integer = 1 + Shared Dim y As Integer = 1 + Sub F1() : End Sub + Sub F2() : End Sub +End Class +Class D + Sub New() : End Sub +#End ExternalSource +#ExternalSource(""a"", 4) + Sub F3() : End Sub +#End ExternalSource +#ExternalSource(""a"", 5) + Sub F4() : End Sub +#End ExternalSource +End Class +" + + Dim src2 = " +#ExternalSource(""a"", 11) +Class C + Dim x As Integer = 1 + Shared Dim y As Integer = 1 + Sub F1() : End Sub + Sub F2() : End Sub +End Class +Class D + Sub New() : End Sub +#End ExternalSource +#ExternalSource(""a"", 4) + Sub F3() : End Sub + Sub F4() : End Sub +#End ExternalSource +End Class +" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyLineEdits( + { + New SequencePointUpdates("a", ImmutableArray.Create( + New SourceLineUpdate(1, 11), ' x, y, F1, F2 + AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(5),' lines between F2 And D ctor + New SourceLineUpdate(7, 17)))' D ctor + }, + { + "Sub F3() : End Sub", ' overlaps with "Sub F1" + "Sub F4() : End Sub" ' overlaps with "Sub F2" + }) + End Sub + + + Public Sub LineMapping_LineDirectivesAndWhitespace() + Dim src1 = " +Class C +#ExternalSource(""a"", 5) +#End ExternalSource +#ExternalSource(""a"", 6) + + + + Sub F() : End Sub ' line 9 +End Class +#End ExternalSource +" + Dim src2 = " +Class C +#ExternalSource(""a"", 9) + Sub F() : End Sub +End Class +#End ExternalSource +" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics() + End Sub + + + Public Sub LineMapping_MultipleFiles() + Dim src1 = " +Class C + Sub F() +#ExternalSource(""a"", 1) + A() +#End ExternalSource +#ExternalSource(""b"", 1) + B() +#End ExternalSource + End Sub +End Class" + Dim src2 = " +Class C + Sub F() +#ExternalSource(""a"", 2) + A() +#End ExternalSource +#ExternalSource(""b"", 2) + B() +#End ExternalSource + End Sub +End Class" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyLineEdits( + { + New SequencePointUpdates("a", ImmutableArray.Create(New SourceLineUpdate(0, 1))), + New SequencePointUpdates("b", ImmutableArray.Create(New SourceLineUpdate(0, 1))) + }, + Array.Empty(Of String)) + End Sub + + + Public Sub LineMapping_FileChange_Recompile() + Dim src1 = " +Class C + Sub F() + A() +#ExternalSource(""a"", 1) + B() +#End ExternalSource +#ExternalSource(""a"", 3) + C() + End Sub + + + Dim x As Integer = 1 +#End ExternalSource +End Class" + Dim src2 = " +Class C + Sub F() + A() +#ExternalSource(""b"", 1) + B() +#End ExternalSource +#ExternalSource(""a"", 2) + C() + End Sub + + Dim x As Integer = 1 +#End ExternalSource +End Class" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyLineEdits( + { + New SequencePointUpdates("a", ImmutableArray.Create(New SourceLineUpdate(6, 4))) + }, + {"Sub F()"}) + + edits.VerifySemantics(ActiveStatementsDescription.Empty, + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F")) + }) + End Sub + + + Public Sub LineMapping_FileChange_RudeEdit() + Dim src1 = " +#ExternalSource(""a"", 1) +Class C + Sub Bar(Of T)() + End Sub +End Class +#End ExternalSource +" + Dim src2 = " +#ExternalSource(""b"", 1) +Class C + Sub Bar(Of T)() + End Sub +End Class +#End ExternalSource +" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates)(), + {"Sub Bar(Of T)()"}, + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub Bar(Of T)()", FeaturesResources.method)) + End Sub + +#End Region End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 5ec44d611ab73..5f10e00e950e8 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -919,10 +919,10 @@ End Class" 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, "F3 As Integer", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F4 As New Object", 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)) @@ -4954,7 +4954,7 @@ End Class Imports System Partial Class C - Dim a = 1 + Dim a = 1 Sub New(arg As UInteger) Console.WriteLine(2) @@ -4979,7 +4979,7 @@ End Class Imports System Partial Class C - Dim a = 2 ' updated field initializer + Dim a = 2 ' updated field initializer Sub New(arg As UInteger) Console.WriteLine(2) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index d940b1d771828..54061eb80965c 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -434,9 +434,7 @@ End Class Dim source1 = " Class C Sub Main() - - ' comment - System.Console.WriteLine(1) + System.Console.WriteLine(1) End Sub End Class " @@ -468,9 +466,21 @@ End Class Dim oldStatementSpan = oldText.Lines.GetLinePositionSpan(oldStatementTextSpan) Dim oldStatementSyntax = oldSyntaxRoot.FindNode(oldStatementTextSpan) + Dim baseActiveStatements = New ActiveStatementsMap( + ImmutableDictionary.CreateRange( + { + KeyValuePairUtil.Create(newDocument.FilePath, ImmutableArray.Create( + New ActiveStatement( + ordinal:=0, + ActiveStatementFlags.IsLeafFrame, + New SourceFileSpan(newDocument.FilePath, oldStatementSpan), + instructionId:=Nothing))) + }), + ActiveStatementsMap.Empty.InstructionMap) + Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim baseActiveStatements = ImmutableArray.Create(ActiveStatementsDescription.CreateActiveStatement(ActiveStatementFlags.IsLeafFrame, oldStatementSpan, DocumentId.CreateNewId(ProjectId.CreateNewId()))) - Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None) Assert.True(result.HasChanges) Dim syntaxMap = result.SemanticEdits(0).SyntaxMap @@ -498,9 +508,9 @@ End Class Using workspace = TestWorkspace.CreateVisualBasic(source, composition:=s_composition) Dim oldProject = workspace.CurrentSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() - Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() + Dim baseActiveStatements = ActiveStatementsMap.Empty Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None) Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) @@ -533,8 +543,8 @@ End Class Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim baseActiveStatements = ActiveStatementsMap.Empty + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None) Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) @@ -556,9 +566,9 @@ End Class Using workspace = TestWorkspace.CreateVisualBasic(source, composition:=s_composition) Dim oldProject = workspace.CurrentSolution.Projects.Single() Dim oldDocument = oldProject.Documents.Single() - Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() + Dim baseActiveStatements = ActiveStatementsMap.Empty Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None) Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) @@ -593,8 +603,8 @@ End Class Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim baseActiveStatements = ActiveStatementsMap.Empty + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None) ' no declaration errors (error in method body is only reported when emitting) Assert.False(result.HasChangesAndErrors) @@ -626,8 +636,8 @@ End Class Dim newSolution = workspace.CurrentSolution.WithDocumentText(documentId, SourceText.From(source2)) Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim baseActiveStatements = ActiveStatementsMap.Empty + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None) Assert.True(result.HasChanges) @@ -638,29 +648,6 @@ End Class End Using End Function - - Public Sub FindMemberDeclaration1() - Dim source = -Class C - Inherits D - - Public Sub New() - MyBase.New() - Goo() - End Sub - - Public Sub Goo - End Sub -End Class -.Value - - Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim root = SyntaxFactory.ParseCompilationUnit(source) - - Assert.Null(analyzer.FindMemberDeclaration(root, Integer.MaxValue)) - Assert.Null(analyzer.FindMemberDeclaration(root, Integer.MinValue)) - End Sub - Public Async Function AnalyzeDocumentAsync_AddingNewFile() As Task Dim source1 = " @@ -694,10 +681,10 @@ End Class Dim changedDocuments = changes.GetChangedDocuments().Concat(changes.GetAddedDocuments()) Dim result = New List(Of DocumentAnalysisResults)() - Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() + Dim baseActiveStatements = ActiveStatementsMap.Empty 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)) + result.Add(Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray(Of LinePositionSpan).Empty, EditAndContinueTestHelpers.Net5RuntimeCapabilities, CancellationToken.None)) Next Assert.True(result.IsSingle()) diff --git a/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb b/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb index a89f63bd239c7..ed67ca1646282 100644 --- a/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb +++ b/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb @@ -4,10 +4,9 @@ Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics -Imports Microsoft.CodeAnalysis.VisualBasic.Diagnostics Imports Microsoft.CodeAnalysis.VisualBasic.GenerateConstructor -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.GenerateConstructor Public Class GenerateConstructorTests @@ -1578,23 +1577,72 @@ Class A End Class") End Function - Public Class GenerateConstructorTestsWithFindMissingIdentifiersAnalyzer - Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest + + + Public Async Function TestGenerateConstructorInIncompleteLambda() As Task + Await TestInRegularAndScriptAsync( +"Imports System +Imports System.Linq +Class C + Sub New() + Dim s As Action = Sub() + Dim a = New C([|0|])", +"Imports System +Imports System.Linq +Class C + Private v As Integer + + Sub New() + Dim s As Action = Sub() + Dim a = New C(0)Public Sub New(v As Integer) + Me.v = v + End Sub +End Class +") + End Function + + + + Public Async Function TestGenerateConstructorInIncompleteLambda2() As Task + Await TestInRegularAndScriptAsync( +"Imports System +Imports System.Linq +Class C + Private v As Integer + Public Sub New(v As Integer) + Me.v = v + End Sub + Sub New() + Dim s As Action = Sub() + Dim a = New [|C|](0, 0)", +"Imports System +Imports System.Linq +Class C + Private v As Integer + Private v1 As Integer - Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (New VisualBasicUnboundIdentifiersDiagnosticAnalyzer(), - New GenerateConstructorCodeFixProvider()) - End Function + Public Sub New(v As Integer) + Me.v = v + End Sub + Sub New() + Dim s As Action = Sub() + Dim a = New C(0, 0)Public Sub New(v As Integer, v1 As Integer) + Me.New(v) + Me.v1 = v1 + End Sub +End Class +") + End Function - - - Public Async Function TestGenerateConstructorInIncompleteLambda() As Task - Await TestInRegularAndScriptAsync( + + + Public Async Function TestGenerateConstructorInIncompleteLambda_WithoutImport() As Task + Await TestInRegularAndScriptAsync( "Imports System.Linq Class C Sub New() Dim s As Action = Sub() - Dim a = New [|C|](0)", + Dim a = New C([|0|])", "Imports System.Linq Class C Private v As Integer @@ -1606,12 +1654,12 @@ Class C End Sub End Class ") - End Function + End Function - - - Public Async Function TestGenerateConstructorInIncompleteLambda2() As Task - Await TestInRegularAndScriptAsync( + + + Public Async Function TestGenerateConstructorInIncompleteLambda2_WithoutImport() As Task + Await TestInRegularAndScriptAsync( "Imports System.Linq Class C Private v As Integer @@ -1637,8 +1685,7 @@ Class C End Sub End Class ") - End Function - End Class + End Function Public Async Function TestGenerateConstructorNotOfferedForDuplicate() As Task diff --git a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb index 2fd0863ca5eb1..5ee239b0bcd43 100644 --- a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb +++ b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit +Imports Microsoft.CodeAnalysis.Indentation Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.VisualStudio.Text @@ -142,7 +143,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.LineCommit Assert.Equal(trackingSpan.GetSpan(spanToFormat.Snapshot), spanToFormat.Span) End If - Dim realCommitFormatter As New CommitFormatter(_testWorkspace.GetService(Of IIndentationManagerService)) + Dim realCommitFormatter = Assert.IsType(Of CommitFormatter)(_testWorkspace.GetService(Of ICommitFormatter)()) realCommitFormatter.CommitRegion(spanToFormat, isExplicitFormat, useSemantics, dirtyRegion, baseSnapshot, baseTree, cancellationToken) End Sub End Class diff --git a/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb b/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb index 9ae8ccef1e15d..1a43a627be113 100644 --- a/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb +++ b/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb @@ -15,6 +15,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.MakeTypeAbstract Return (Nothing, New VisualBasicMakeTypeAbstractCodeFixProvider()) End Function + Public Async Function TestMethod_CodeFix() As Task Await TestInRegularAndScript1Async(" @@ -22,7 +23,7 @@ Public Class [|Foo|] Public MustOverride Sub M() End Class", " -Public MustOverride Class Foo +Public MustInherit Class Foo Public MustOverride Sub M() End Class") End Function diff --git a/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb b/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb index 6a69ac2716df1..5a85a01c060e6 100644 --- a/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb +++ b/src/EditorFeatures/VisualBasicTest/NavigateTo/NavigateToTests.vb @@ -28,16 +28,16 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.NavigateTo - Public Async Function TestNoItemsForEmptyFile(testHost As TestHost) As Task - Await TestAsync(testHost, "", Async Function(w) - Assert.Empty(Await _aggregator.GetItemsAsync("Hello")) - End Function) + Public Async Function TestNoItemsForEmptyFile(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "", Async Function(w) + Assert.Empty(Await _aggregator.GetItemsAsync("Hello")) + End Function) End Function - Public Async Function TestFindClass(testHost As TestHost) As Task - Await TestAsync(testHost, + Public Async Function TestFindClass(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("Goo")).Single() @@ -47,8 +47,8 @@ End Class", Async Function(w) - Public Async Function TestFindVerbatimClass(testHost As TestHost) As Task - Await TestAsync(testHost, + Public Async Function TestFindVerbatimClass(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class [Class] End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("class")).Single() @@ -61,8 +61,8 @@ End Class", Async Function(w) - Public Async Function TestFindNestedClass(testHost As TestHost) As Task - Await TestAsync(testHost, + Public Async Function TestFindNestedClass(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Alpha Class Beta Class Gamma @@ -76,8 +76,8 @@ End Class", Async Function(w) - Public Async Function TestFindMemberInANestedClass(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Alpha + Public Async Function TestFindMemberInANestedClass(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Alpha Class Beta Class Gamma Sub DoSomething() @@ -93,8 +93,8 @@ End Class", Async Function(w) - Public Async Function TestFindGenericConstrainedClass(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo(Of M As IComparable) + Public Async Function TestFindGenericConstrainedClass(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo(Of M As IComparable) End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("Goo")).Single() VerifyNavigateToResultItem(item, "Goo", "[|Goo|](Of M)", PatternMatchKind.Exact, NavigateToItemKind.Class, Glyph.ClassInternal) @@ -103,8 +103,8 @@ End Class", Async Function(w) - Public Async Function TestFindGenericConstrainedMethod(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo(Of M As IComparable) + Public Async Function TestFindGenericConstrainedMethod(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo(Of M As IComparable) Public Sub Bar(Of T As IComparable)() End Sub End Class", Async Function(w) @@ -115,8 +115,8 @@ End Class", Async Function(w) - Public Async Function TestFindPartialClass(testHost As TestHost) As Task - Await TestAsync(testHost, "Partial Public Class Goo + Public Async Function TestFindPartialClass(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Partial Public Class Goo Private a As Integer End Class Partial Class Goo @@ -137,8 +137,8 @@ End Class", Async Function(w) - Public Async Function TestFindClassInNamespace(testHost As TestHost) As Task - Await TestAsync(testHost, "Namespace Bar + Public Async Function TestFindClassInNamespace(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Namespace Bar Class Goo End Class End Namespace", Async Function(w) @@ -149,8 +149,8 @@ End Namespace", Async Function(w) - Public Async Function TestFindStruct(testHost As TestHost) As Task - Await TestAsync(testHost, "Structure Bar + Public Async Function TestFindStruct(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Structure Bar End Structure", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("B")).Single() VerifyNavigateToResultItem(item, "Bar", "[|B|]ar", PatternMatchKind.Prefix, NavigateToItemKind.Structure, Glyph.StructureInternal) @@ -159,8 +159,8 @@ End Structure", Async Function(w) - Public Async Function TestFindEnum(testHost As TestHost) As Task - Await TestAsync(testHost, "Enum Colors + Public Async Function TestFindEnum(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Enum Colors Red Green Blue @@ -172,8 +172,8 @@ End Enum", Async Function(w) - Public Async Function TestFindEnumMember(testHost As TestHost) As Task - Await TestAsync(testHost, "Enum Colors + Public Async Function TestFindEnumMember(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Enum Colors Red Green Blue @@ -185,8 +185,8 @@ End Enum", Async Function(w) - Public Async Function TestFindField1(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindField1(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Bar As Integer End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("Ba")).Single() @@ -196,8 +196,8 @@ End Class", Async Function(w) - Public Async Function TestFindField2(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindField2(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Bar As Integer End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("ba")).Single() @@ -207,8 +207,8 @@ End Class", Async Function(w) - Public Async Function TestFindField3(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindField3(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Bar As Integer End Class", Async Function(w) Assert.Empty(Await _aggregator.GetItemsAsync("ar")) @@ -217,8 +217,8 @@ End Class", Async Function(w) - Public Async Function TestFindVerbatimField(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindVerbatimField(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private [string] As String End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("string")).Single() @@ -231,8 +231,8 @@ End Class", Async Function(w) - Public Async Function TestFindConstField(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindConstField(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Const bar As String = ""bar"" End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("bar")).Single() @@ -242,8 +242,8 @@ End Class", Async Function(w) - Public Async Function TestFindIndexer(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindIndexer(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private arr As Integer() Default Public Property Item(ByVal i As Integer) As Integer Get @@ -262,8 +262,8 @@ End Class", Async Function(w) - Public Async Function TestFindEvent(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindEvent(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Public Event Bar as EventHandler End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("Bar")).Single() @@ -273,8 +273,8 @@ End Class", Async Function(w) - Public Async Function TestFindNormalProperty(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindNormalProperty(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Property Name As String Get Return String.Empty @@ -289,8 +289,8 @@ End Class", Async Function(w) - Public Async Function TestFindAutoImplementedProperty(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindAutoImplementedProperty(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Property Name As String End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("Name")).Single() @@ -300,8 +300,8 @@ End Class", Async Function(w) - Public Async Function TestFindMethod(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindMethod(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Sub DoSomething() End Sub End Class", Async Function(w) @@ -312,8 +312,8 @@ End Class", Async Function(w) - Public Async Function TestFindVerbatimMethod(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindVerbatimMethod(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Sub [Sub]() End Sub End Class", Async Function(w) @@ -327,8 +327,8 @@ End Class", Async Function(w) - Public Async Function TestFindParameterizedMethod(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindParameterizedMethod(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private Sub DoSomething(ByVal i As Integer, s As String) End Sub End Class", Async Function(w) @@ -339,8 +339,8 @@ End Class", Async Function(w) - Public Async Function TestFindConstructor(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindConstructor(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Sub New() End Sub End Class", Async Function(w) @@ -351,8 +351,8 @@ End Class", Async Function(w) - Public Async Function TestFindStaticConstructor(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindStaticConstructor(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Shared Sub New() End Sub End Class", Async Function(w) @@ -363,8 +363,8 @@ End Class", Async Function(w) - Public Async Function TestFindDestructor(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindDestructor(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Implements IDisposable Public Sub Dispose() Implements IDisposable.Dispose End Sub @@ -382,8 +382,8 @@ End Class", Async Function(w) - Public Async Function TestFindPartialMethods(testHost As TestHost) As Task - Await TestAsync(testHost, "Partial Class Goo + Public Async Function TestFindPartialMethods(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Partial Class Goo Partial Private Sub Bar() End Sub End Class @@ -407,8 +407,8 @@ End Class", Async Function(w) - Public Async Function TestFindPartialMethodDefinitionOnly(testHost As TestHost) As Task - Await TestAsync(testHost, "Partial Class Goo + Public Async Function TestFindPartialMethodDefinitionOnly(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Partial Class Goo Partial Private Sub Bar() End Sub End Class", Async Function(w) @@ -419,8 +419,8 @@ End Class", Async Function(w) - Public Async Function TestFindPartialMethodImplementationOnly(testHost As TestHost) As Task - Await TestAsync(testHost, "Partial Class Goo + Public Async Function TestFindPartialMethodImplementationOnly(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Partial Class Goo Private Sub Bar() End Sub End Class", Async Function(w) @@ -431,8 +431,8 @@ End Class", Async Function(w) - Public Async Function TestFindOverriddenMethods(testHost As TestHost) As Task - Await TestAsync(testHost, "Class BaseGoo + Public Async Function TestFindOverriddenMethods(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class BaseGoo Public Overridable Sub Bar() End Sub End Class @@ -456,8 +456,8 @@ End Class", Async Function(w) - Public Async Function TestDottedPattern1(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern1(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz sub Quux() @@ -478,8 +478,8 @@ end namespace", Async Function(w) - Public Async Function TestDottedPattern2(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern2(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz sub Quux() @@ -499,8 +499,8 @@ end namespace", Async Function(w) - Public Async Function TestDottedPattern3(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern3(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz sub Quux() @@ -521,8 +521,8 @@ end namespace", Async Function(w) - Public Async Function TestDottedPattern4(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern4(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz sub Quux() @@ -543,8 +543,8 @@ end namespace", Async Function(w) - Public Async Function TestDottedPattern5(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern5(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz sub Quux() @@ -565,8 +565,8 @@ end namespace", Async Function(w) - Public Async Function TestDottedPattern6(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern6(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz sub Quux() @@ -585,8 +585,8 @@ end namespace", Async Function(w) - Public Async Function TestDottedPattern7(testHost As TestHost) As Task - Await TestAsync(testHost, "namespace Goo + Public Async Function TestDottedPattern7(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "namespace Goo namespace Bar class Baz(of X, Y, Z) sub Quux() @@ -607,8 +607,8 @@ end namespace", Async Function(w) - Public Async Function TestFindInterface(testHost As TestHost) As Task - Await TestAsync(testHost, "Public Interface IGoo + Public Async Function TestFindInterface(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Public Interface IGoo End Interface", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("IG")).Single() VerifyNavigateToResultItem(item, "IGoo", "[|IG|]oo", PatternMatchKind.Prefix, NavigateToItemKind.Interface, Glyph.InterfacePublic) @@ -617,8 +617,8 @@ End Interface", Async Function(w) - Public Async Function TestFindDelegateInNamespace(testHost As TestHost) As Task - Await TestAsync(testHost, "Namespace Goo + Public Async Function TestFindDelegateInNamespace(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Namespace Goo Delegate Sub DoStuff() End Namespace", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("DoStuff")).Single() @@ -628,8 +628,8 @@ End Namespace", Async Function(w) - Public Async Function TestFindLambdaExpression(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindLambdaExpression(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Dim sqr As Func(Of Integer, Integer) = Function(x) x*x End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("sqr")).Single() @@ -639,8 +639,8 @@ End Class", Async Function(w) - Public Async Function TestFindModule(testHost As TestHost) As Task - Await TestAsync(testHost, "Module ModuleTest + Public Async Function TestFindModule(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Module ModuleTest End Module", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("MT")).Single() VerifyNavigateToResultItem(item, "ModuleTest", "[|M|]odule[|T|]est", PatternMatchKind.CamelCaseExact, NavigateToItemKind.Module, Glyph.ModuleInternal) @@ -649,8 +649,8 @@ End Module", Async Function(w) - Public Async Function TestFindLineContinuationMethod(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindLineContinuationMethod(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Public Sub Bar(x as Integer, y as Integer) End Sub", Async Function(w) @@ -661,8 +661,8 @@ End Sub", Async Function(w) - Public Async Function TestFindArray(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindArray(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo Private itemArray as object() End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("itemArray")).Single @@ -672,8 +672,8 @@ End Class", Async Function(w) - Public Async Function TestFindClassAndMethodWithSameName(testHost As TestHost) As Task - Await TestAsync(testHost, "Class Goo + Public Async Function TestFindClassAndMethodWithSameName(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class Goo End Class Class Test Private Sub Goo() @@ -694,8 +694,8 @@ End Class", Async Function(w) - Public Async Function TestFindMethodNestedInGenericTypes(testHost As TestHost) As Task - Await TestAsync(testHost, "Class A(Of T) + Public Async Function TestFindMethodNestedInGenericTypes(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Class A(Of T) Class B Structure C(Of U) Sub M() @@ -711,8 +711,8 @@ End Class", Async Function(w) - Public Async Function TestFindClassInNamespaceWithGlobalPrefix(testHost As TestHost) As Task - Await TestAsync(testHost, "Namespace Global.MyNS + Public Async Function TestFindClassInNamespaceWithGlobalPrefix(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Namespace Global.MyNS Public Class C End Class End Namespace", Async Function(w) @@ -724,8 +724,8 @@ End Namespace", Async Function(w) - Public Async Function TestFindClassInGlobalNamespace(testHost As TestHost) As Task - Await TestAsync(testHost, "Namespace Global + Public Async Function TestFindClassInGlobalNamespace(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Namespace Global Public Class C(Of T) End Class End Namespace", Async Function(w) @@ -737,8 +737,8 @@ End Namespace", Async Function(w) - Public Async Function TestConstructorNotParentedByTypeBlock(testHost As TestHost) As Task - Await TestAsync(testHost, "Module Program + Public Async Function TestConstructorNotParentedByTypeBlock(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, "Module Program End Module Public Sub New() End Sub", Async Function(w) @@ -750,9 +750,9 @@ End Sub", Async Function(w) - Public Async Function TestStartStopSanity(testHost As TestHost) As Task + Public Async Function TestStartStopSanity(testHost As TestHost, composition As Composition) As Task ' Verify that multiple calls to start/stop don't blow up - Await TestAsync(testHost, "Public Class Goo + Await TestAsync(testHost, composition, "Public Class Goo End Class", Async Function(w) ' Do one query Assert.Single(Await _aggregator.GetItemsAsync("Goo")) @@ -769,8 +769,8 @@ End Class", Async Function(w) - Public Async Function TestDescriptionItems(testHost As TestHost) As Task - Await TestAsync(testHost, " + Public Async Function TestDescriptionItems(testHost As TestHost, composition As Composition) As Task + Await TestAsync(testHost, composition, " Public Class Goo End Class", Async Function(w) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("G")).Single() @@ -792,7 +792,7 @@ End Class", Async Function(w) - Public Async Function TestDescriptionItemsFilePath(testHost As TestHost) As Task + Public Async Function TestDescriptionItemsFilePath(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -801,7 +801,7 @@ Public Class Goo End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) Dim item As NavigateToItem = (Await _aggregator.GetItemsAsync("G")).Single() Dim itemDisplay As INavigateToItemDisplay = item.DisplayFactory.CreateItemDisplay(item) @@ -822,7 +822,7 @@ End Class - Public Async Function DoNotIncludeTrivialPartialContainer(testHost As TestHost) As Task + Public Async Function DoNotIncludeTrivialPartialContainer(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -839,7 +839,7 @@ Public Partial Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) @@ -855,7 +855,7 @@ End Class - Public Async Function DoNotIncludeTrivialPartialContainerWithMultipleNestedTypes(testHost As TestHost) As Task + Public Async Function DoNotIncludeTrivialPartialContainerWithMultipleNestedTypes(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -874,7 +874,7 @@ Public Partial Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) @@ -890,7 +890,7 @@ End Class - Public Async Function DoNotIncludeWhenAllAreTrivialPartialContainer(testHost As TestHost) As Task + Public Async Function DoNotIncludeWhenAllAreTrivialPartialContainer(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -907,7 +907,7 @@ Public Partial Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) @@ -920,7 +920,7 @@ End Class - Public Async Function DoIncludeNonTrivialPartialContainer(testHost As TestHost) As Task + Public Async Function DoIncludeNonTrivialPartialContainer(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -937,7 +937,7 @@ Public Partial Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) @@ -954,7 +954,7 @@ End Class - Public Async Function DoIncludeNonTrivialPartialContainerWithNestedType(testHost As TestHost) As Task + Public Async Function DoIncludeNonTrivialPartialContainerWithNestedType(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -973,7 +973,7 @@ Public Partial Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) @@ -990,7 +990,7 @@ End Class - Public Async Function DoIncludePartialWithNoContents(testHost As TestHost) As Task + Public Async Function DoIncludePartialWithNoContents(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -999,7 +999,7 @@ Public Partial Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) @@ -1015,7 +1015,7 @@ End Class - Public Async Function DoIncludeNonPartialOnlyContainingNestedTypes(testHost As TestHost) As Task + Public Async Function DoIncludeNonPartialOnlyContainingNestedTypes(testHost As TestHost, composition As Composition) As Task Using workspace = CreateWorkspace( @@ -1026,7 +1026,7 @@ Public Class Outer End Class - , testHost, createTrackingService:=Nothing) + , testHost, DefaultComposition) _provider = New NavigateToItemProvider(workspace, AsynchronousOperationListenerProvider.NullListener, workspace.GetService(Of IThreadingContext)()) _aggregator = New NavigateToTestAggregator(_provider) diff --git a/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb b/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb index 6c5d91b17b6da..0102a14a1426a 100644 --- a/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb @@ -1515,6 +1515,22 @@ End Class MainDescription("Goo.B = 1")) End Function + + + Public Async Function EnumNonDefaultUnderlyingType() As Task + Dim code = + +Enum Goo$$ As Byte + A + B + C +End Enum +.NormalizedValue() + + Await TestAsync(code, + MainDescription("Enum Goo As Byte")) + End Function + Public Async Function TestTextOnlyDocComment() As Task Await TestAsync( + Public Async Function TestNoParameterReassignment() As Task + Await TestAsync( +"Class C + Sub M(p As Integer) + End Sub +End Class") + End Function + + + Public Async Function TestParameterReassignment() As Task + Await TestAsync( +"Class C + Sub M([|p|] As Integer) + [|p|] = 1 + End Sub +End Class") + End Function + + + Public Async Function TestParameterReassignmentWhenReadAfter() As Task + Await TestAsync( +" +Imports System +Class C + Sub M([|p|] As Integer) + [|p|] = 1 + Console.WriteLine([|p|]) + End Sub +End Class") + End Function + + + Public Async Function TestParameterReassignmentWhenReadBefore() As Task + Await TestAsync( +" +Imports System +Class C + Sub M([|p|] As Integer) + Console.WriteLine([|p|]) + [|p|] = 1 + End Sub +End Class") + End Function + + + Public Async Function TestParameterReassignmentWhenReadWithDefaultValue() As Task + Await TestAsync( +" +Imports System +Class C + Sub M([|p|] As Integer = 1) + Console.WriteLine([|p|]) + [|p|] = 1 + End Sub +End Class") + End Function + + + Public Async Function TestIndexerWithWriteInGetter() As Task + Await TestAsync( +" +Imports System +Class C + Default Property Item([|p|] As Integer) As Integer + Get + [|p|] += 1 + Return [|p|] + End Get + End Property +End Class") + End Function + + + Public Async Function TestIndexerWithWriteInSetter() As Task + Await TestAsync( +" +Imports System +Class C + Default Property Item([|p|] As Integer) As Integer + Set(ByVal value As Integer) + [|p|] += 1 + End Set + End Property +End Class") + End Function + + + Public Async Function TestPropertyWithAssignmentToValue() As Task + Await TestAsync( +" +Imports System +Class C + Property Goo As Integer + Set(ByVal [|value|] As Integer) + [|value|] = [|value|] + 1 + End Set + End Property +End Class") + End Function + + + Public Async Function TestEventAddWithAssignmentToValue() As Task + Await TestAsync( +" +Imports System +Class C + Custom Event Goo As Action + AddHandler([|value|] As Action) + [|value|] = Nothing + End AddHandler + RemoveHandler(value As Action) + End RemoveHandler + RaiseEvent + End RaiseEvent + End Event +End Class") + End Function + + + Public Async Function TestEventRemoveWithAssignmentToValue() As Task + Await TestAsync( +" +Imports System +Class C + Custom Event Goo As Action + AddHandler(value As Action) + End AddHandler + RemoveHandler([|value|] As Action) + [|value|] = Nothing + End RemoveHandler + RaiseEvent + End RaiseEvent + End Event +End Class") + End Function + + + Public Async Function TestEventRaiseWithAssignmentToValue() As Task + Await TestAsync( +" +Imports System +Class C + Custom Event Goo As EventHandler + AddHandler(value As EventHandler) + End AddHandler + RemoveHandler(value As EventHandler) + End RemoveHandler + RaiseEvent([|sender|] As Object, e As EventArgs) + [|sender|] = Nothing + End RaiseEvent + End Event +End Class") + End Function + + + Public Async Function TestLambdaParameterWithoutReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim a As Action(Of Integer) = Sub(x) Console.WriteLine(x) + End Sub +End Class") + End Function + + + Public Async Function TestLambdaParameterWithReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim a As Action(Of Integer) = + Sub([|x|]) + [|x|] += 1 + Console.WriteLine([|x|]) + End Sub + End Sub +End Class") + End Function + + + Public Async Function TestLambdaParameterWithReassignment2() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim a As Action(Of Integer) = + Sub([|x|]) [|x|] += 1 + End Sub +End Class") + End Function + + + Public Async Function TestLambdaParameterWithReassignment3() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim a As Action(Of Integer) = + Sub([|x|] As Integer) + [|x|] += 1 + Console.WriteLine([|x|]) + End Sub + End Sub +End Class") + End Function + + + Public Async Function TestLocalWithoutInitializerWithoutReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M(b As Boolean) + Dim p As Integer + If b Then p = 1 Else p = 2 + + Console.WriteLine(p) + End Sub +End Class") + End Function + + + Public Async Function TestLocalWithoutInitializerWithReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M(b As Boolean) + Dim [|p|] As Integer + If b Then [|p|] = 1 Else [|p|] = 2 + + [|p|] = 0 + Console.WriteLine([|p|]) + End Sub +End Class") + End Function + + + Public Async Function TestOutParameterCausingReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim [|p|] As Integer = 0 + M2([|p|]) + Console.WriteLine([|p|]) + End Sub + + Sub M2( ByRef [|p|] As Integer) + [|p|] = 0 + End Sub +End Class") + End Function + + + Public Async Function TestOutParameterWithoutReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim p As Integer + M2(p) + Console.WriteLine(p) + End Sub + + Sub M2( ByRef [|p|] As Integer) + [|p|] = 0 + End Sub +End Class") + End Function + + + Public Async Function AssignmentThroughOutParameterIsNotAssignmentOfTheVariableItself() As Task + Await TestAsync( +" +Imports System +Class C + Sub M( ByRef [|p|] As Integer) + [|p|] = 0 + [|p|] = 1 + Console.WriteLine([|p|]) + End Sub +End Class") + End Function + + + Public Async Function AssignmentThroughRefParameter() As Task + Await TestAsync( +" +Imports System +Class C + Sub M(ByRef [|p|] As Integer) + [|p|] = 0 + [|p|] = 1 + Console.WriteLine([|p|]) + End Sub +End Class") + End Function + + + Public Async Function TestRefParameterCausingPossibleReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim [|p|] As Integer = 0 + M2([|p|]) + Console.WriteLine([|p|]) + End Sub + + Sub M2(ByRef p As Integer) + End Sub +End Class") + End Function + + + Public Async Function TestRefParameterWithoutReassignment() As Task + Await TestAsync( +" +Imports System +Class C + Sub M() + Dim p As Integer + M2(p) + Console.WriteLine(p) + End Sub + + Sub M2(ByRef p As Integer) + End Sub +End Class") + End Function + + + Public Async Function TestRefExtensionMethodCausingPossibleReassignment() As Task + Await TestAsync( +" +Imports System +Module C + Sub M() + Dim [|p|] As Integer = 0 + [|p|].M2() + Console.WriteLine([|p|]) + End Sub + + + Sub M2(ByRef p As Integer) + End Sub +End Module") + End Function + + + Public Async Function TestMutatingStructMethod() As Task + Await TestAsync( +" +Imports System +Structure S + f As Integer + + Sub M(p As S) + p.MutatingMethod() + Console.WriteLine(p) + End Sub + + Sub MutatingMethod() + Me = Nothing + End Sub +End Structure") + End Function + + + Public Async Function TestDuplicateMethod() As Task + Await TestAsync( +"Class C + Sub M([|p|] As Integer) + [|p|] = 1 + End Sub + + Sub M([|p|] As Integer) + [|p|] = 1 + End Sub +End Class") + End Function + + + Public Async Function TestDuplicateParameter() As Task + Await TestAsync( +"Class C + Sub M([|p|] As Integer, p As Integer) + [|p|] = 1 + End Sub +End Class") + End Function + End Class +End Namespace diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs index 83aa94caa844e..d3dab807d90fe 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs @@ -732,7 +732,6 @@ private static Binder CreateBinderChain( Debug.Assert((object)@namespace == compilation.GlobalNamespace); } - Imports? imports = null; if (hasImports) { if (currentStringGroup < 0) @@ -742,11 +741,13 @@ private static Binder CreateBinderChain( } var importsBinder = new InContainerBinder(@namespace, binder); - imports = BuildImports(compilation, importRecordGroups[currentStringGroup], importsBinder); + Imports imports = BuildImports(compilation, importRecordGroups[currentStringGroup], importsBinder); currentStringGroup--; + + binder = WithExternAndUsingAliasesBinder.Create(imports.ExternAliases, imports.UsingAliases, WithUsingNamespacesAndTypesBinder.Create(imports.Usings, binder)); } - binder = new InContainerBinder(@namespace, binder, imports); + binder = new InContainerBinder(@namespace, binder); } stack.Free(); @@ -944,9 +945,12 @@ private static Imports BuildImports(CSharpCompilation compilation, ImmutableArra continue; } - var externAliasSyntax = SyntaxFactory.ExternAliasDirective(aliasNameSyntax.Identifier); - var aliasSymbol = new AliasSymbol(binder, externAliasSyntax); // Binder is only used to access compilation. - externsBuilder.Add(new AliasAndExternAliasDirective(aliasSymbol, externAliasDirective: null)); // We have one, but we pass null for consistency. + NamespaceSymbol target; + compilation.GetExternAliasTarget(aliasNameSyntax.Identifier.ValueText, out target); + Debug.Assert(target.IsGlobalNamespace); + + var aliasSymbol = AliasSymbol.CreateCustomDebugInfoAlias(target, aliasNameSyntax.Identifier, binder.ContainingMemberOrLambda, isExtern: true); + externsBuilder.Add(new AliasAndExternAliasDirective(aliasSymbol, externAliasDirective: null, skipInLookup: false)); } var externs = externsBuilder.ToImmutableAndFree(); @@ -958,8 +962,7 @@ private static Imports BuildImports(CSharpCompilation compilation, ImmutableArra // the actual binder chain. binder = new InContainerBinder( binder.Container, - binder, - Imports.FromCustomDebugInfo(binder.Compilation, ImmutableDictionary.Empty, ImmutableArray.Empty, externs)); + WithExternAliasesBinder.Create(externs, binder)); } var usingAliases = ImmutableDictionary.CreateBuilder(); @@ -1070,7 +1073,7 @@ private static Imports BuildImports(CSharpCompilation compilation, ImmutableArra } } - return Imports.FromCustomDebugInfo(binder.Compilation, usingAliases.ToImmutableDictionary(), usingsBuilder.ToImmutableAndFree(), externs); + return Imports.Create(usingAliases.ToImmutableDictionary(), usingsBuilder.ToImmutableAndFree(), externs); } private static NamespaceSymbol? BindNamespace(string namespaceName, NamespaceSymbol globalNamespace) @@ -1110,7 +1113,7 @@ private static bool TryAddImport( return false; } - var aliasSymbol = AliasSymbol.CreateCustomDebugInfoAlias(targetSymbol, aliasSyntax.Identifier, binder); + var aliasSymbol = AliasSymbol.CreateCustomDebugInfoAlias(targetSymbol, aliasSyntax.Identifier, binder.ContainingMemberOrLambda, isExtern: false); usingAliases.Add(alias, new AliasAndUsingDirective(aliasSymbol, usingDirective: null)); } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs index 5867309217851..b540143fe6de0 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs @@ -229,7 +229,6 @@ internal CompilationContext CreateCompilationContext() () => stream, getPortablePdbStreamOpt: null, nativePdbWriterOpt: null, - pdbOptionsBlobReader: null, pdbPathOpt: null, metadataOnly: false, isDeterministic: false, @@ -292,7 +291,6 @@ internal CompilationContext CreateCompilationContext() () => stream, getPortablePdbStreamOpt: null, nativePdbWriterOpt: null, - pdbOptionsBlobReader: null, pdbPathOpt: null, metadataOnly: false, isDeterministic: false, @@ -377,7 +375,6 @@ internal CompilationContext CreateCompilationContext() () => stream, getPortablePdbStreamOpt: null, nativePdbWriterOpt: null, - pdbOptionsBlobReader: null, pdbPathOpt: null, metadataOnly: false, isDeterministic: false, @@ -426,7 +423,6 @@ internal override ReadOnlyCollection CompileGetLocals( () => stream, getPortablePdbStreamOpt: null, nativePdbWriterOpt: null, - pdbOptionsBlobReader: null, pdbPathOpt: null, metadataOnly: false, isDeterministic: false, diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs index 066f61287360c..d89910712becb 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs @@ -349,6 +349,7 @@ internal override bool IsInterface internal sealed override NamedTypeSymbol NativeIntegerUnderlyingType => null; internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; internal override bool HasPossibleWellKnownCloneMethod() => false; [Conditional("DEBUG")] diff --git a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj index e34d41ffff1cc..fa4d315968f61 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj +++ b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj @@ -33,9 +33,9 @@ + - diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj b/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj index 775f4d87a28f3..cc964c299a8a5 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj @@ -74,9 +74,9 @@ + - diff --git a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs index a347e1ef0c368..4cb86e9d0299f 100644 --- a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs +++ b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs @@ -818,7 +818,6 @@ internal static void EmitCorLibWithAssemblyReferences( () => peStream, () => pdbStream, nativePdbWriterOpt: null, - pdbOptionsBlobReader: null, pdbPathOpt: null, metadataOnly: true, isDeterministic: false, diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb index 353fe79aeb27c..e4896807e92d4 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb @@ -376,7 +376,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Function() stream, getPortablePdbStreamOpt:=Nothing, nativePdbWriterOpt:=Nothing, - pdbOptionsBlobReader:=Nothing, pdbPathOpt:=Nothing, metadataOnly:=False, isDeterministic:=False, @@ -426,7 +425,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Function() stream, getPortablePdbStreamOpt:=Nothing, nativePdbWriterOpt:=Nothing, - pdbOptionsBlobReader:=Nothing, pdbPathOpt:=Nothing, metadataOnly:=False, isDeterministic:=False, @@ -477,7 +475,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Function() stream, getPortablePdbStreamOpt:=Nothing, nativePdbWriterOpt:=Nothing, - pdbOptionsBlobReader:=Nothing, pdbPathOpt:=Nothing, metadataOnly:=False, isDeterministic:=False, diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj index 88d58005035d8..bb240cea6c5e2 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj @@ -32,9 +32,9 @@ + - diff --git a/src/Features/CSharp/Portable/AddParameter/CSharpAddParameterCodeFixProvider.cs b/src/Features/CSharp/Portable/AddParameter/CSharpAddParameterCodeFixProvider.cs index b2f8c02a372b9..c5d154c15ff77 100644 --- a/src/Features/CSharp/Portable/AddParameter/CSharpAddParameterCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/AddParameter/CSharpAddParameterCodeFixProvider.cs @@ -35,8 +35,7 @@ internal class CSharpAddParameterCodeFixProvider : AbstractAddParameterCodeFixPr private const string CS1739 = nameof(CS1739); // error CS1739: The best overload for 'M' does not have a parameter named 'x' private static readonly ImmutableArray AddParameterFixableDiagnosticIds = ImmutableArray.Create( - CS1501, CS1503, CS1660, CS1729, CS1739, - IDEDiagnosticIds.UnboundConstructorId); + CS1501, CS1503, CS1660, CS1729, CS1739); [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index c5b6b2c9b08d2..209f102173c6c 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -618,4 +618,10 @@ Change to cast + + record + + + record struct + \ No newline at end of file diff --git a/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs index 5cffa6cd2e1c7..138f868763ba6 100644 --- a/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs @@ -46,17 +46,13 @@ node is SimpleNameSyntax || node is ExpressionSyntax; } - protected override SyntaxNode GetTargetNode(SyntaxNode node) + protected override SyntaxNode? GetTargetNode(SyntaxNode node) { if (node is InvocationExpressionSyntax invocation) - { return invocation.Expression.GetRightmostName(); - } if (node is MemberBindingExpressionSyntax memberBindingExpression) - { return memberBindingExpression.Name; - } return node; } diff --git a/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs index f8b2c9a791d67..c0a307f077a85 100644 --- a/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs @@ -76,7 +76,7 @@ node is SimpleNameSyntax || node is ExpressionSyntax; } - protected override SyntaxNode GetTargetNode(SyntaxNode node) + protected override SyntaxNode? GetTargetNode(SyntaxNode node) { switch (node) { diff --git a/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs index c9206be502d5d..0ad17420edac7 100644 --- a/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs @@ -57,7 +57,7 @@ protected override bool IsCandidate(SyntaxNode node, SyntaxToken token, Diagnost return false; } - protected override SyntaxNode GetTargetNode(SyntaxNode node) + protected override SyntaxNode? GetTargetNode(SyntaxNode node) => ((ExpressionSyntax)node).GetRightmostName(); protected override Task> GetCodeActionsAsync( diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs index 3ff468e03ddd1..f78d2375ec78d 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs @@ -419,7 +419,7 @@ private static bool IsPrimaryConstructorParameter(SyntaxToken token, SemanticMod cancellationToken); if (result.Type != null && - token.GetAncestor().Parent.IsParentKind(SyntaxKind.RecordDeclaration)) + token.GetAncestor().Parent.IsParentKind(SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration)) { return true; } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs index 929af8d6e7111..b15a95ab04146 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.NameGenerator.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using Humanizer; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -21,10 +22,10 @@ internal class NameGenerator internal static ImmutableArray GetBaseNames(ITypeSymbol type, bool pluralize) { var baseName = TryRemoveInterfacePrefix(type); - var parts = StringBreaker.GetWordParts(baseName); + using var parts = TemporaryArray.Empty; + StringBreaker.AddWordParts(baseName, ref parts.AsRef()); var result = GetInterleavedPatterns(parts, baseName, pluralize); - parts.Free(); return result; } @@ -38,15 +39,16 @@ internal static ImmutableArray GetBaseNames(IAliasSymbol alias) name = name.Substring(1); } - var breaks = StringBreaker.GetWordParts(name); + using var breaks = TemporaryArray.Empty; + StringBreaker.AddWordParts(name, ref breaks.AsRef()); var result = GetInterleavedPatterns(breaks, name, pluralize: false); - breaks.Free(); return result; } - private static ImmutableArray GetInterleavedPatterns(ArrayBuilder breaks, string baseName, bool pluralize) + private static ImmutableArray GetInterleavedPatterns( + in TemporaryArray breaks, string baseName, bool pluralize) { - var result = ArrayBuilder.GetInstance(); + using var result = TemporaryArray.Empty; var breakCount = breaks.Count; result.Add(GetWords(0, breakCount, breaks, baseName, pluralize)); @@ -59,22 +61,22 @@ private static ImmutableArray GetInterleavedPatterns(ArrayBuilder breaks, string baseName, bool pluralize) + private static Words GetLongestBackwardSubsequence(int length, in TemporaryArray breaks, string baseName, bool pluralize) { var breakCount = breaks.Count; var start = breakCount - length; return GetWords(start, breakCount, breaks, baseName, pluralize); } - private static Words GetLongestForwardSubsequence(int length, ArrayBuilder breaks, string baseName, bool pluralize) + private static Words GetLongestForwardSubsequence(int length, in TemporaryArray breaks, string baseName, bool pluralize) => GetWords(0, length, breaks, baseName, pluralize); - private static Words GetWords(int start, int end, ArrayBuilder breaks, string baseName, bool pluralize) + private static Words GetWords(int start, int end, in TemporaryArray breaks, string baseName, bool pluralize) { - var result = ArrayBuilder.GetInstance(); + using var result = TemporaryArray.Empty; // Add all the words but the last one for (; start < end; start++) { @@ -91,7 +93,7 @@ private static Words GetWords(int start, int end, ArrayBuilder breaks, } } - return result.ToImmutableAndFree(); + return result.ToImmutableAndClear(); } private static string TryRemoveInterfacePrefix(ITypeSymbol type) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs index 754cb0310009c..fd435e1bf5f3f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs @@ -142,7 +142,7 @@ private static bool IsPreviousTokenValid(SyntaxToken tokenBeforeType) } private static bool IsClassOrStructOrInterfaceOrRecord(SyntaxNode node) - => node.Kind() == SyntaxKind.ClassDeclaration || node.Kind() == SyntaxKind.StructDeclaration || - node.Kind() == SyntaxKind.InterfaceDeclaration || node.Kind() == SyntaxKind.RecordDeclaration; + => node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or + SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs index be33b63f9ee38..2c58e8d0f34b7 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs @@ -149,12 +149,10 @@ private void AddUnnamedSymbols( } } - internal override Task GetChangeAsync( + public override Task GetChangeAsync( Document document, CompletionItem item, - TextSpan completionListSpan, char? commitKey, - bool disallowAddingImports, CancellationToken cancellationToken) { var kind = item.Properties[KindName]; diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ClassKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ClassKeywordRecommender.cs index 9d0a69bbb57c8..7934a96916601 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ClassKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ClassKeywordRecommender.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Utilities; @@ -41,6 +42,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructRecordTypeDeclarations, canBePartial: true, cancellationToken: cancellationToken) || + context.LeftToken.GetPreviousTokenIfTouchingWord(position).IsKind(SyntaxKind.RecordKeyword) || syntaxTree.IsTypeParameterConstraintStartContext(position, context.LeftToken); } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 26afaf5f39551..e098eb5de0c35 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -18,6 +18,7 @@ internal class FieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommend SyntaxKind.StructDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, + SyntaxKind.RecordStructDeclaration, SyntaxKind.EnumDeclaration, }; diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs index ee5f4b3630410..73f6ee3cbab26 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs @@ -36,6 +36,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context return context.IsStatementContext || context.IsGlobalStatementContext || + UsingKeywordRecommender.IsUsingDirectiveContext(context, forGlobalKeyword: true, cancellationToken) || context.IsAnyExpressionContext || context.IsObjectCreationTypeContext || context.IsIsOrAsTypeContext || diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs index e0b0f69feb818..25636c745941b 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs @@ -82,7 +82,9 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context // a namespace can't come before usings/externs // a child namespace can't come before usings/externs - if (leftToken.GetNextToken(includeSkipped: true).IsUsingOrExternKeyword()) + var nextToken = leftToken.GetNextToken(includeSkipped: true); + if (nextToken.IsUsingOrExternKeyword() || + (nextToken.Kind() == SyntaxKind.GlobalKeyword && nextToken.GetAncestor()?.GlobalKeyword == nextToken)) { return false; } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs index 18a5aae547cf4..4f52472c83899 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs @@ -52,7 +52,11 @@ private static bool IsValidContextForType(CSharpSyntaxContext context, Cancellat } private static bool IsStructAccessorContext(CSharpSyntaxContext context) - => context.ContainingTypeDeclaration.IsKind(SyntaxKind.StructDeclaration) && + { + var type = context.ContainingTypeDeclaration; + return type is not null && + type.Kind() is SyntaxKind.StructDeclaration or SyntaxKind.RecordStructDeclaration && context.TargetToken.IsAnyAccessorDeclarationContext(context.Position, SyntaxKind.ReadOnlyKeyword); + } } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs index a93598f5a528f..540f5870c92b2 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RecordKeywordRecommender.cs @@ -24,7 +24,6 @@ internal class RecordKeywordRecommender : AbstractSyntacticSingleKeywordRecommen SyntaxKind.SealedKeyword, SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, - SyntaxKind.DataKeyword }; public RecordKeywordRecommender() diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StructKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StructKeywordRecommender.cs index d7c8f1e1a33d0..1dc721bff834b 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StructKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StructKeywordRecommender.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Utilities; @@ -39,6 +40,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructRecordTypeDeclarations, canBePartial: true, cancellationToken: cancellationToken) || + context.LeftToken.GetPreviousTokenIfTouchingWord(position).IsKind(SyntaxKind.RecordKeyword) || syntaxTree.IsTypeParameterConstraintStartContext(position, context.LeftToken); } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs index e149a84d9d004..b9adb86cabfcd 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs @@ -26,11 +26,11 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context return context.IsStatementContext || context.IsGlobalStatementContext || - IsUsingDirectiveContext(context, cancellationToken) || + IsUsingDirectiveContext(context, forGlobalKeyword: false, cancellationToken) || context.IsAwaitStatementContext(position, cancellationToken); } - private static bool IsUsingDirectiveContext(CSharpSyntaxContext context, CancellationToken cancellationToken) + internal static bool IsUsingDirectiveContext(CSharpSyntaxContext context, bool forGlobalKeyword, CancellationToken cancellationToken) { // cases: // root: | @@ -95,15 +95,7 @@ private static bool IsUsingDirectiveContext(CSharpSyntaxContext context, Cancell { // root namespace - // a using can't come before externs - var nextToken = originalToken.GetNextToken(includeSkipped: true); - if (nextToken.Kind() == SyntaxKind.ExternKeyword || - ((CompilationUnitSyntax)context.SyntaxTree.GetRoot(cancellationToken)).Externs.Count > 0) - { - return false; - } - - return true; + return IsValidContextAtTheRoot(context, originalToken, cancellationToken); } if (token.Kind() == SyntaxKind.OpenBraceToken && @@ -126,14 +118,62 @@ private static bool IsUsingDirectiveContext(CSharpSyntaxContext context, Cancell // | if (token.Kind() == SyntaxKind.SemicolonToken) { - if (token.Parent.IsKind(SyntaxKind.ExternAliasDirective) || - token.Parent.IsKind(SyntaxKind.UsingDirective)) + if (token.Parent.IsKind(SyntaxKind.ExternAliasDirective, SyntaxKind.UsingDirective)) { return true; } } + // extern alias a; + // global | + + // global using Goo; + // global | + + // global | + if (!forGlobalKeyword) + { + var previousToken = token.GetPreviousToken(includeSkipped: true); + if (previousToken.Kind() == SyntaxKind.None) + { + // root namespace + if (token.Kind() == SyntaxKind.GlobalKeyword) + { + return true; + } + else if (token.Kind() == SyntaxKind.IdentifierToken && SyntaxFacts.GetContextualKeywordKind((string)token.Value!) == SyntaxKind.GlobalKeyword) + { + return IsValidContextAtTheRoot(context, originalToken, cancellationToken); + } + } + else if (previousToken.Kind() == SyntaxKind.SemicolonToken && + previousToken.Parent.IsKind(SyntaxKind.ExternAliasDirective, SyntaxKind.UsingDirective)) + { + if (token.Kind() == SyntaxKind.GlobalKeyword) + { + return true; + } + else if (token.Kind() == SyntaxKind.IdentifierToken && SyntaxFacts.GetContextualKeywordKind((string)token.Value!) == SyntaxKind.GlobalKeyword) + { + return true; + } + } + } + return false; + + static bool IsValidContextAtTheRoot(CSharpSyntaxContext context, SyntaxToken originalToken, CancellationToken cancellationToken) + { + // a using can't come before externs + var nextToken = originalToken.GetNextToken(includeSkipped: true); + if (nextToken.Kind() == SyntaxKind.ExternKeyword || + ((CompilationUnitSyntax)context.SyntaxTree.GetRoot(cancellationToken)).Externs.Count > 0) + { + return false; + } + + return true; + } } } } diff --git a/src/Features/CSharp/Portable/ConvertTupleToStruct/CSharpConvertTupleToStructCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertTupleToStruct/CSharpConvertTupleToStructCodeRefactoringProvider.cs index 7a4ca8c2057de..70ee8a6d87d4d 100644 --- a/src/Features/CSharp/Portable/ConvertTupleToStruct/CSharpConvertTupleToStructCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertTupleToStruct/CSharpConvertTupleToStructCodeRefactoringProvider.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Composition; -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.ConvertTupleToStruct; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -27,7 +27,7 @@ internal class CSharpConvertTupleToStructCodeRefactoringProvider : NamespaceDeclarationSyntax> { [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpConvertTupleToStructCodeRefactoringProvider() { } diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs index 3011dacc7a5ad..d625cd68869d3 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs @@ -39,6 +39,7 @@ protected override bool IsIgnoredCodeBlock(SyntaxNode codeBlock) SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKind.EnumDeclaration); diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpUnboundIdentifiersDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpUnboundIdentifiersDiagnosticAnalyzer.cs index 15d697ac4f2df..b54b1a11212eb 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpUnboundIdentifiersDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpUnboundIdentifiersDiagnosticAnalyzer.cs @@ -13,62 +13,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Diagnostics { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal sealed class CSharpUnboundIdentifiersDiagnosticAnalyzer : UnboundIdentifiersDiagnosticAnalyzerBase + internal sealed class CSharpUnboundIdentifiersDiagnosticAnalyzer : UnboundIdentifiersDiagnosticAnalyzerBase { private readonly LocalizableString _nameNotInContextMessageFormat = new LocalizableResourceString(nameof(CSharpFeaturesResources.The_name_0_does_not_exist_in_the_current_context), CSharpFeaturesResources.ResourceManager, typeof(CSharpFeaturesResources)); - private readonly LocalizableString _constructorOverloadResolutionFailureMessageFormat = - new LocalizableResourceString(nameof(CSharpFeaturesResources._0_does_not_contain_a_constructor_that_takes_that_many_arguments), CSharpFeaturesResources.ResourceManager, typeof(CSharpFeaturesResources)); - - private static readonly ImmutableArray s_kindsOfInterest = ImmutableArray.Create(SyntaxKind.IncompleteMember, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression); + private static readonly ImmutableArray s_kindsOfInterest = ImmutableArray.Create(SyntaxKind.IncompleteMember); protected override ImmutableArray SyntaxKindsOfInterest => s_kindsOfInterest; - protected override DiagnosticDescriptor DiagnosticDescriptor => GetDiagnosticDescriptor(IDEDiagnosticIds.UnboundIdentifierId, _nameNotInContextMessageFormat); - - protected override DiagnosticDescriptor DiagnosticDescriptor2 => GetDiagnosticDescriptor(IDEDiagnosticIds.UnboundConstructorId, _constructorOverloadResolutionFailureMessageFormat); - - protected override bool ConstructorDoesNotExist(SyntaxNode node, SymbolInfo info, SemanticModel model) - { - var argList = (node.Parent as ObjectCreationExpressionSyntax)?.ArgumentList?.Arguments; - if (!argList.HasValue) - { - return false; - } - - var args = argList.Value; - - var constructors = (info.Symbol?.OriginalDefinition as INamedTypeSymbol)?.Constructors; - if (!constructors.HasValue) - { - return false; - } - - var count = constructors.Value - .WhereAsArray(constructor => constructor.Parameters.Length == args.Count) - .WhereAsArray(constructor => - { - for (var i = 0; i < constructor.Parameters.Length; i++) - { - var typeInfo = model.GetTypeInfo(args[i].Expression); - if (!constructor.Parameters[i].Type.Equals(typeInfo.ConvertedType)) - { - return false; - } - } - - return true; - }).Length; - - if (count == 0) - { - return true; - } - - return false; - } + protected override DiagnosticDescriptor DiagnosticDescriptor + => GetDiagnosticDescriptor(IDEDiagnosticIds.UnboundIdentifierId, _nameNotInContextMessageFormat); - protected override bool IsNameOf(SyntaxNode node) => node.Parent is InvocationExpressionSyntax invocation && invocation.IsNameOfInvocation(); + protected override bool IsNameOf(SyntaxNode node) + => node.Parent is InvocationExpressionSyntax invocation && invocation.IsNameOfInvocation(); } } diff --git a/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs b/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs index 079e46c889145..a908916e98648 100644 --- a/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs +++ b/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs @@ -39,8 +39,10 @@ protected override bool SupportsDocumentationComments(MemberDeclarationSyntax me switch (member.Kind()) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.EnumMemberDeclaration: diff --git a/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs b/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs index 894ea061dd0f2..c1b084b4c2b67 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs @@ -152,7 +152,7 @@ private static int GetEndPosition(SyntaxNodeOrToken nodeOrToken) return (methodDeclaration.Body != null) ? CreateSpanForBlock(methodDeclaration.Body, position) : methodDeclaration.ExpressionBody?.Expression.Span; case SyntaxKind.ConstructorDeclaration: - return CreateSpanForConstructorDeclaration((ConstructorDeclarationSyntax)node); + return CreateSpanForConstructorDeclaration((ConstructorDeclarationSyntax)node, position); case SyntaxKind.VariableDeclarator: // handled by the parent node @@ -325,21 +325,27 @@ node is SwitchExpressionSyntax switchExpression && } } - private static TextSpan CreateSpanForConstructorDeclaration(ConstructorDeclarationSyntax constructorSyntax) + private static TextSpan CreateSpanForConstructorDeclaration(ConstructorDeclarationSyntax constructorSyntax, int position) { - if (constructorSyntax.Initializer != null) + if (constructorSyntax.ExpressionBody != null && + position > constructorSyntax.ExpressionBody.ArrowToken.Span.Start) { - return CreateSpanForConstructorInitializer(constructorSyntax.Initializer); + return constructorSyntax.ExpressionBody.Expression.Span; } - if (constructorSyntax.ExpressionBody != null) + if (constructorSyntax.Initializer != null) { - return constructorSyntax.ExpressionBody.Expression.Span; + return CreateSpanForConstructorInitializer(constructorSyntax.Initializer); } // static ctor doesn't have a default initializer: if (constructorSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)) { + if (constructorSyntax.ExpressionBody != null) + { + return constructorSyntax.ExpressionBody.Expression.Span; + } + return CreateSpan(constructorSyntax.Body.OpenBraceToken); } @@ -618,18 +624,47 @@ private static SyntaxToken LastNotMissing(SyntaxToken token1, SyntaxToken token2 position = variableDeclaration.SpanStart; } - var declarator = FindClosestDeclaratorWithInitializer(variableDeclaration.Variables, position); - if (declarator == null) + var variableDeclarator = FindClosestDeclaratorWithInitializer(variableDeclaration.Variables, position); + if (variableDeclarator == null) { return default(TextSpan); } - if (declarator == variableDeclaration.Variables[0]) + if (variableDeclarator == variableDeclaration.Variables[0]) + { + return CreateSpan(modifiersOpt, variableDeclaration, variableDeclarator); + } + + return CreateSpan(variableDeclarator); + } + + private static TextSpan CreateSpanForVariableDeclarator( + VariableDeclaratorSyntax variableDeclarator, + SyntaxTokenList modifiersOpt, + SyntaxToken semicolonOpt) + { + if (variableDeclarator.Initializer == null) + { + return default; + } + + if (modifiersOpt.Any(SyntaxKind.ConstKeyword)) + { + return default; + } + + var variableDeclaration = (VariableDeclarationSyntax)variableDeclarator.Parent; + if (variableDeclaration.Variables.Count == 1) { - return CreateSpan(modifiersOpt, variableDeclaration.Type, variableDeclaration.Variables[0]); + return CreateSpan(modifiersOpt, variableDeclaration, semicolonOpt); } - return CreateSpan(declarator); + if (variableDeclarator == variableDeclaration.Variables[0]) + { + return CreateSpan(modifiersOpt, variableDeclaration, variableDeclarator); + } + + return CreateSpan(variableDeclarator); } private static VariableDeclaratorSyntax FindClosestDeclaratorWithInitializer(SeparatedSyntaxList declarators, int position) @@ -745,5 +780,34 @@ private static bool IsBreakableExpression(ExpressionSyntax expression) return null; } + + /// + /// Returns a span that contains all possible breakpoint spans of top-level + /// and no breakpoint spans that do not belong to the . + /// + /// Returns default if the declaration does not have any breakpoint spans. + /// + internal static TextSpan GetEnvelope(SyntaxNode declaration) + { + if (declaration is VariableDeclaratorSyntax { Parent: { Parent: BaseFieldDeclarationSyntax fieldDeclaration } } variableDeclarator) + { + return CreateSpanForVariableDeclarator(variableDeclarator, fieldDeclaration.Modifiers, fieldDeclaration.SemicolonToken); + } + + if (declaration is ConstructorDeclarationSyntax constructorDeclaration) + { + var firstSpan = CreateSpanForConstructorDeclaration(constructorDeclaration, constructorDeclaration.Identifier.SpanStart); + var lastSpan = ((SyntaxNode)constructorDeclaration.ExpressionBody ?? constructorDeclaration.Body).Span; + return TextSpan.FromBounds(firstSpan.Start, lastSpan.End); + } + + var body = SyntaxUtilities.TryGetMethodDeclarationBody(declaration); + if (body == null) + { + return default; + } + + return body.Span; + } } } diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index e0e62cceef9c2..dd102f7e42bfa 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -78,13 +78,14 @@ private enum SwitchExpressionPart /// for field initializers. /// for property initializers and expression bodies. /// for indexer expression bodies. + /// for getter of an expression-bodied property/indexer. /// - internal override SyntaxNode? FindMemberDeclaration(SyntaxNode? root, SyntaxNode node) + internal override bool TryFindMemberDeclaration(SyntaxNode? root, SyntaxNode node, out OneOrMany declarations) { - while (node != root) + var current = node; + while (current != null && current != root) { - RoslynDebug.Assert(node is object); - switch (node.Kind()) + switch (current.Kind()) { case SyntaxKind.MethodDeclaration: case SyntaxKind.ConversionOperatorDeclaration: @@ -96,38 +97,53 @@ private enum SwitchExpressionPart case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.DestructorDeclaration: - return node; + declarations = new(current); + return true; case SyntaxKind.PropertyDeclaration: // int P { get; } = [|initializer|]; - RoslynDebug.Assert(((PropertyDeclarationSyntax)node).Initializer != null); - return node; + RoslynDebug.Assert(((PropertyDeclarationSyntax)current).Initializer != null); + declarations = new(current); + return true; case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: // Active statements encompassing modifiers or type correspond to the first initialized field. // [|public static int F = 1|], G = 2; - return ((BaseFieldDeclarationSyntax)node).Declaration.Variables.First(); + declarations = new(((BaseFieldDeclarationSyntax)current).Declaration.Variables.First()); + return true; case SyntaxKind.VariableDeclarator: // public static int F = 1, [|G = 2|]; - RoslynDebug.Assert(node.Parent.IsKind(SyntaxKind.VariableDeclaration)); + RoslynDebug.Assert(current.Parent.IsKind(SyntaxKind.VariableDeclaration)); - switch (node.Parent.Parent!.Kind()) + switch (current.Parent.Parent!.Kind()) { case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: - return node; + declarations = new(current); + return true; + } + + current = current.Parent; + break; + + case SyntaxKind.ArrowExpressionClause: + // represents getter symbol declaration node of a property/indexer with expression body + if (current.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) + { + declarations = new(current); + return true; } - node = node.Parent; break; } - node = node.Parent!; + current = current.Parent; } - return null; + declarations = default; + return false; } /// @@ -148,6 +164,9 @@ private enum SwitchExpressionPart return SyntaxUtilities.TryGetMethodDeclarationBody(node); } + internal override bool IsDeclarationWithSharedBody(SyntaxNode declaration) + => false; + protected override ImmutableArray GetCapturedVariables(SemanticModel model, SyntaxNode memberBody) { Debug.Assert(memberBody.IsKind(SyntaxKind.Block) || memberBody is ExpressionSyntax); @@ -188,7 +207,7 @@ where node.IsKind(SyntaxKind.IdentifierName) /// tokens of the initializer concatenated with tokens of the constructor body. /// /// If is a variable declarator of a field with an initializer, - /// tokens of the field initializer. + /// subset of the tokens of the field declaration depending on which variable declarator it is. /// /// Null reference otherwise. /// @@ -225,6 +244,16 @@ where node.IsKind(SyntaxKind.IdentifierName) return declarator.DescendantTokens(); } + if (node is PropertyDeclarationSyntax { ExpressionBody: var propertyExpressionBody and not null }) + { + return propertyExpressionBody.Expression.DescendantTokens(); + } + + if (node is IndexerDeclarationSyntax { ExpressionBody: var indexerExpressionBody and not null }) + { + return indexerExpressionBody.Expression.DescendantTokens(); + } + var bodyTokens = SyntaxUtilities.TryGetMethodDeclarationBody(node)?.DescendantTokens(); if (node.IsKind(SyntaxKind.ConstructorDeclaration, out ConstructorDeclarationSyntax? ctor)) @@ -238,6 +267,9 @@ where node.IsKind(SyntaxKind.IdentifierName) return bodyTokens; } + internal override (TextSpan envelope, TextSpan hole) GetActiveSpanEnvelope(SyntaxNode declaration) + => (BreakpointSpans.GetEnvelope(declaration), default); + protected override SyntaxNode GetEncompassingAncestorImpl(SyntaxNode bodyOrMatchRoot) { // Constructor may contain active nodes outside of its body (constructor initializer), @@ -477,50 +509,9 @@ private static bool AreEquivalentIgnoringLambdaBodies(SyntaxNode left, SyntaxNod return LambdaUtilities.AreEquivalentIgnoringLambdaBodies(left, right); } - internal override SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRoot, SyntaxNode leftNode) + internal override SyntaxNode FindDeclarationBodyPartner(SyntaxNode leftRoot, SyntaxNode rightRoot, SyntaxNode leftNode) => SyntaxUtilities.FindPartner(leftRoot, rightRoot, leftNode); - internal override SyntaxNode? FindPartnerInMemberInitializer(SemanticModel leftModel, INamedTypeSymbol leftType, SyntaxNode leftNode, INamedTypeSymbol rightType, CancellationToken cancellationToken) - { - var leftEqualsClause = leftNode.FirstAncestorOrSelf( - node => node.Parent.IsKind(SyntaxKind.PropertyDeclaration) || node.Parent!.Parent!.Parent.IsKind(SyntaxKind.FieldDeclaration)); - - if (leftEqualsClause == null) - { - return null; - } - - SyntaxNode? rightEqualsClause; - if (leftEqualsClause.Parent.IsKind(SyntaxKind.PropertyDeclaration, out PropertyDeclarationSyntax? leftDeclaration)) - { - var leftSymbol = leftModel.GetDeclaredSymbol(leftDeclaration, cancellationToken); - Contract.ThrowIfNull(leftSymbol); - - var rightProperty = rightType.GetMembers(leftSymbol.Name).Single(); - var rightDeclaration = (PropertyDeclarationSyntax)rightProperty.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); - - rightEqualsClause = rightDeclaration.Initializer; - } - else - { - var leftDeclarator = (VariableDeclaratorSyntax)leftEqualsClause.Parent!; - var leftSymbol = leftModel.GetDeclaredSymbol(leftDeclarator, cancellationToken); - Contract.ThrowIfNull(leftSymbol); - - var rightField = rightType.GetMembers(leftSymbol.Name).Single(); - var rightDeclarator = (VariableDeclaratorSyntax)rightField.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); - - rightEqualsClause = rightDeclarator.Initializer; - } - - if (rightEqualsClause == null) - { - return null; - } - - return FindPartner(leftEqualsClause, rightEqualsClause, leftNode); - } - internal override bool IsClosureScope(SyntaxNode node) => LambdaUtilities.IsClosureScope(node); @@ -648,6 +639,77 @@ private static IEnumerable GetChildNodes(SyntaxNode root, SyntaxNode } } + internal override void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol) + { + // Compiler generated methods of records have a declaring syntax reference to the record declaration itself + // but their explicitly implemented counterparts reference the actual member. Compiler generated properties + // of records reference the parameter that names them. + // + // Since there is no useful "old" syntax node for these members, we can't compute declaration or body edits + // using the standard tree comparison code. + // + // Based on this, we can detect a new explicit implementation of a record member by checking if the + // declaration kind has changed. If it hasn't changed, then our standard code will handle it. + if (oldNode.RawKind == newNode.RawKind) + { + base.ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode, oldSymbol, newSymbol); + return; + } + + // When explicitly implementing a property that is represented by a positional parameter + // what looks like an edit could actually be a rude delete, or something else + if (oldNode is ParameterSyntax && + newNode is PropertyDeclarationSyntax property) + { + if (property.AccessorList!.Accessors.Count == 1) + { + // Explicitly implementing a property with only one accessor is a delete of the init accessor, so a rude edit. + // Not implementing the get accessor would be a compile error + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ImplementRecordParameterAsReadOnly, + GetDiagnosticSpan(newNode, EditKind.Delete), + oldNode, + new[] { + property.Identifier.ToString() + })); + } + else if (property.AccessorList.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration))) + { + // The compiler implements the properties with an init accessor so explicitly implementing + // it with a set accessor is a rude accessor change edit + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ImplementRecordParameterWithSet, + GetDiagnosticSpan(newNode, EditKind.Delete), + oldNode, + new[] { + property.Identifier.ToString() + })); + } + } + else if (oldNode is RecordDeclarationSyntax && + newNode is MethodDeclarationSyntax && + !oldSymbol.GetParameters().Select(p => p.Name).SequenceEqual(newSymbol.GetParameters().Select(p => p.Name))) + { + // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 + // Explicitly implemented methods must have parameter names that match the compiler generated versions + // exactly otherwise symbol matching won't work for them. + // We don't need to worry about parameter types, because if they were different then we wouldn't get here + // as this wouldn't be the explicit implementation of a known method. + // We don't need to worry about access modifiers because the symbol matching still works, and most of the + // time changing access modifiers for these known methods is a compile error anyway. + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, + GetDiagnosticSpan(newNode, EditKind.Update), + oldNode, + new[] { + oldSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat) + })); + } + } + protected override void ReportLocalFunctionsDeclarationRudeEdits(ArrayBuilder diagnostics, Match bodyMatch) { var bodyEditsForLambda = bodyMatch.GetTreeEdits(); @@ -704,6 +766,12 @@ protected override bool TryMatchActiveStatement( #region Syntax and Semantic Utils + protected override string LineDirectiveKeyword + => "line"; + + protected override ushort LineDirectiveSyntaxKind + => (ushort)SyntaxKind.LineDirectiveTrivia; + protected override IEnumerable GetSyntaxSequenceEdits(ImmutableArray oldNodes, ImmutableArray newNodes) => SyntaxComparer.GetSequenceEdits(oldNodes, newNodes); @@ -1028,6 +1096,9 @@ private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldN internal override bool IsInterfaceDeclaration(SyntaxNode node) => node.IsKind(SyntaxKind.InterfaceDeclaration); + internal override bool IsRecordDeclaration(SyntaxNode node) + => node.IsKind(SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); + internal override SyntaxNode? TryGetContainingTypeDeclaration(SyntaxNode node) => node.Parent!.FirstAncestorOrSelf(); @@ -1041,6 +1112,42 @@ internal override bool HasBackingField(SyntaxNode propertyOrIndexerDeclaration) internal override bool IsDeclarationWithInitializer(SyntaxNode declaration) => declaration is VariableDeclaratorSyntax { Initializer: not null } || declaration is PropertyDeclarationSyntax { Initializer: not null }; + internal override bool IsRecordPrimaryConstructorParameter(SyntaxNode declaration) + => declaration is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } }; + + private static bool IsPropertyDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType) + { + if (newContainingType.IsRecord && + declaration is PropertyDeclarationSyntax { Identifier: { ValueText: var name } }) + { + // We need to use symbol information to find the primary constructor, because it could be in another file if the type is partial + foreach (var reference in newContainingType.DeclaringSyntaxReferences) + { + // Since users can define as many constructors as they like, going back to syntax to find the parameter list + // in the record declaration is the simplest way to check if there is a matching parameter + if (reference.GetSyntax() is RecordDeclarationSyntax record && + record.ParameterList is not null && + record.ParameterList.Parameters.Any(p => p.Identifier.ValueText.Equals(name))) + { + return true; + } + } + } + return false; + } + + internal override bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType, out bool isFirstAccessor) + { + isFirstAccessor = false; + if (declaration is AccessorDeclarationSyntax { Parent: AccessorListSyntax { Parent: PropertyDeclarationSyntax property } list } && + IsPropertyDeclarationMatchingPrimaryConstructorParameter(property, newContainingType)) + { + isFirstAccessor = list.Accessors[0] == declaration; + return true; + } + return false; + } + internal override bool IsConstructorWithMemberInitializers(SyntaxNode constructorDeclaration) => constructorDeclaration is ConstructorDeclarationSyntax ctor && (ctor.Initializer == null || ctor.Initializer.IsKind(SyntaxKind.BaseConstructorInitializer)); @@ -1054,40 +1161,90 @@ internal override bool IsPartial(INamedTypeSymbol type) protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference reference, CancellationToken cancellationToken) => reference.GetSyntax(cancellationToken); - protected override ISymbol? GetSymbolForEdit( - SemanticModel model, - SyntaxNode node, + protected override OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol)> GetSymbolsForEdit( EditKind editKind, + SyntaxNode? oldNode, + SyntaxNode? newNode, + SemanticModel? oldModel, + SemanticModel newModel, IReadOnlyDictionary editMap, - out bool isAmbiguous, CancellationToken cancellationToken) { - isAmbiguous = false; + var oldSymbol = (oldNode != null) ? GetSymbolForEdit(oldNode, editKind, oldModel!, cancellationToken) : null; + var newSymbol = (newNode != null) ? GetSymbolForEdit(newNode, editKind, newModel, cancellationToken) : null; - if (node.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter, SyntaxKind.UsingDirective, SyntaxKind.NamespaceDeclaration)) + switch (editKind) { - return null; + case EditKind.Update: + Contract.ThrowIfNull(oldNode); + Contract.ThrowIfNull(newNode); + + // Either old or new property/indexer has an expression body (or both do). + // int this[...] => expr; + // int this[...] { get => expr; } + // int P => expr; + // int P { get => expr; } = init + if (oldNode is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null } || + newNode is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null }) + { + Debug.Assert(oldSymbol is IPropertySymbol); + Debug.Assert(newSymbol is IPropertySymbol); + + var oldGetterSymbol = ((IPropertySymbol)oldSymbol).GetMethod; + var newGetterSymbol = ((IPropertySymbol)newSymbol).GetMethod; + + return OneOrMany.Create(ImmutableArray.Create((oldSymbol, newSymbol), (oldGetterSymbol, newGetterSymbol))); + } + + break; + + case EditKind.Delete: + case EditKind.Insert: + var node = oldNode ?? newNode; + + // If the entire block-bodied property/indexer is deleted/inserted (accessors and the list they are contained in), + // ignore this edit. We will have a semantic edit for the property/indexer itself. + if (node.IsKind(SyntaxKind.GetAccessorDeclaration)) + { + Debug.Assert(node.Parent.IsKind(SyntaxKind.AccessorList)); + + if (HasEdit(editMap, node.Parent, editKind) && !HasEdit(editMap, node.Parent.Parent, editKind)) + { + return OneOrMany<(ISymbol?, ISymbol?)>.Empty; + } + } + + // Inserting/deleting an expression-bodied property/indexer affects two symbols: + // the property/indexer itself and the getter. + // int this[...] => expr; + // int P => expr; + if (node is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null }) + { + var oldGetterSymbol = ((IPropertySymbol?)oldSymbol)?.GetMethod; + var newGetterSymbol = ((IPropertySymbol?)newSymbol)?.GetMethod; + return OneOrMany.Create(ImmutableArray.Create((oldSymbol, newSymbol), (oldGetterSymbol, newGetterSymbol))); + } + + break; } - if (editKind == EditKind.Update) - { - if (node.IsKind(SyntaxKind.EnumDeclaration)) - { - // Enum declaration update that removes/adds a trailing comma. - return null; - } + return (editKind == EditKind.Delete ? oldSymbol : newSymbol) is null ? + OneOrMany<(ISymbol?, ISymbol?)>.Empty : new OneOrMany<(ISymbol?, ISymbol?)>((oldSymbol, newSymbol)); + } - 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. - // In any case, the update is to the property getter symbol. - var propertyOrIndexer = model.GetRequiredDeclaredSymbol(node, cancellationToken); - return ((IPropertySymbol)propertyOrIndexer).GetMethod; - } + private static ISymbol? GetSymbolForEdit( + SyntaxNode node, + EditKind editKind, + SemanticModel model, + CancellationToken cancellationToken) + { + if (node.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter, SyntaxKind.UsingDirective, SyntaxKind.NamespaceDeclaration)) + { + return null; } - if (IsGetterToExpressionBodyTransformation(editKind, node, editMap)) + // Enum declaration update that removes/adds a trailing comma. + if (editKind == EditKind.Update && node.IsKind(SyntaxKind.EnumDeclaration)) { return null; } @@ -1105,40 +1262,6 @@ protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference referen return symbol; } - 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 (oldDeclaration.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) - { - oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldDeclaration); - newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newDeclaration); - - if (oldBody != null && newBody != null) - { - return; - } - } - - base.GetUpdatedDeclarationBodies(oldDeclaration, newDeclaration, out oldBody, out newBody); - } - - private static bool IsGetterToExpressionBodyTransformation(EditKind editKind, SyntaxNode node, IReadOnlyDictionary editMap) - { - 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, SyntaxKind.IndexerDeclaration)); - return editMap.TryGetValue(node.Parent, out var parentEdit) && parentEdit == editKind && - editMap.TryGetValue(node.Parent!.Parent, out parentEdit) && parentEdit == EditKind.Update; - } - - return false; - } - internal override bool ContainsLambda(SyntaxNode declaration) => declaration.DescendantNodes().Any(LambdaUtilities.IsLambda); @@ -1315,6 +1438,8 @@ private static bool GroupBySignatureComparer(ImmutableArray ol case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: var typeDeclaration = (TypeDeclarationSyntax)node; return GetDiagnosticSpan(typeDeclaration.Modifiers, typeDeclaration.Keyword, typeDeclaration.TypeParameterList ?? (SyntaxNodeOrToken)typeDeclaration.Identifier); @@ -1663,6 +1788,12 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i case SyntaxKind.InterfaceDeclaration: return FeaturesResources.interface_; + case SyntaxKind.RecordDeclaration: + return CSharpFeaturesResources.record_; + + case SyntaxKind.RecordStructDeclaration: + return CSharpFeaturesResources.record_struct; + case SyntaxKind.EnumDeclaration: return FeaturesResources.enum_; @@ -1735,6 +1866,14 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i case SyntaxKind.RemoveAccessorDeclaration: return FeaturesResources.event_accessor; + case SyntaxKind.ArrowExpressionClause: + return node.Parent!.Kind() switch + { + SyntaxKind.PropertyDeclaration => CSharpFeaturesResources.property_getter, + SyntaxKind.IndexerDeclaration => CSharpFeaturesResources.indexer_getter, + _ => null + }; + case SyntaxKind.TypeParameterConstraintClause: return FeaturesResources.type_constraint; @@ -2021,6 +2160,8 @@ private void ClassifyReorder(SyntaxNode newNode) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.VariableDeclaration: @@ -2093,6 +2234,8 @@ private void ClassifyInsert(SyntaxNode node) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: @@ -2114,14 +2257,26 @@ private void ClassifyInsert(SyntaxNode node) case SyntaxKind.AccessorList: case SyntaxKind.VariableDeclarator: case SyntaxKind.VariableDeclaration: - // 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.ArrowExpressionClause: + if (node.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) + { + return; + } + + break; + case SyntaxKind.Parameter when !_classifyStatementSyntax: // Parameter inserts are allowed for local functions - ReportError(RudeEditKind.Insert); + if (node.Parent?.Parent is RecordDeclarationSyntax) + { + ReportError(RudeEditKind.AddRecordPositionalParameter); + } + else + { + ReportError(RudeEditKind.Insert); + } return; case SyntaxKind.EnumMemberDeclaration: @@ -2175,6 +2330,8 @@ private void ClassifyDelete(SyntaxNode oldNode) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.IndexerDeclaration: @@ -2200,6 +2357,15 @@ private void ClassifyDelete(SyntaxNode oldNode) // We do not report error here since it will be reported in semantic analysis. return; + case SyntaxKind.ArrowExpressionClause: + // We do not report error here since it will be reported in semantic analysis. + if (oldNode.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) + { + return; + } + + break; + case SyntaxKind.AttributeList: case SyntaxKind.Attribute: // To allow removal of attributes we would need to check if the removed attribute @@ -2216,7 +2382,14 @@ private void ClassifyDelete(SyntaxNode oldNode) case SyntaxKind.Parameter when !_classifyStatementSyntax: case SyntaxKind.ParameterList when !_classifyStatementSyntax: - ReportError(RudeEditKind.Delete); + if (oldNode.Parent?.Parent is RecordDeclarationSyntax) + { + ReportError(RudeEditKind.DeleteRecordPositionalParameter); + } + else + { + ReportError(RudeEditKind.Delete); + } return; } @@ -2255,6 +2428,8 @@ private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: ClassifyUpdate((TypeDeclarationSyntax)oldNode, (TypeDeclarationSyntax)newNode); return; @@ -2882,11 +3057,6 @@ public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) case SyntaxKind.ImplicitStackAllocArrayCreationExpression: ReportError(RudeEditKind.StackAllocUpdate, node, _newNode); return; - - case SyntaxKind.SwitchExpression: - // TODO: remove (https://github.com/dotnet/roslyn/issues/43099) - ReportError(RudeEditKind.SwitchExpressionUpdate, node, _newNode); - break; } } } @@ -3055,6 +3225,8 @@ protected override List GetExceptionHandlingAncestors(SyntaxNode nod // stop at type declaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return result; } diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 2517806df62f5..11a4bfd82d71f 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -563,10 +563,11 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, out bool isLeaf) case SyntaxKind.NamespaceDeclaration: return Label.NamespaceDeclaration; - // Need to add support for records (tracked by https://github.com/dotnet/roslyn/issues/44877) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return Label.TypeDeclaration; case SyntaxKind.MethodDeclaration: @@ -1342,10 +1343,11 @@ private static double CombineOptional( case SyntaxKind.NamespaceDeclaration: return ((NamespaceDeclarationSyntax)node).Name; - // Need to add support for records (tracked by https://github.com/dotnet/roslyn/issues/44877) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)node).Identifier; case SyntaxKind.EnumDeclaration: diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs index 2fccc1ff4fb88..333e146172c19 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs @@ -58,16 +58,16 @@ static SyntaxNode BlockOrExpression(BlockSyntax blockBodyOpt, ArrowExpressionCla case SyntaxKind.PropertyDeclaration: var propertyDeclaration = (PropertyDeclarationSyntax)node; - if (propertyDeclaration.Initializer != null) - { - result = propertyDeclaration.Initializer.Value; - break; - } - - return propertyDeclaration.ExpressionBody?.Expression; + result = propertyDeclaration.Initializer?.Value; + break; - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)node).ExpressionBody?.Expression; + case SyntaxKind.ArrowExpressionClause: + // We associate the body of expression-bodied property/indexer with the ArrowExpressionClause + // since that's the syntax node associated with the getter symbol. + // The property/indexer itself is considered to not have a body unless the property has an initializer. + result = node.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration) ? + ((ArrowExpressionClauseSyntax)node).Expression : null; + break; default: return null; diff --git a/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.PasteFormattingRule.cs b/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.PasteFormattingRule.cs similarity index 75% rename from src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.PasteFormattingRule.cs rename to src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.PasteFormattingRule.cs index 177a7390be519..eb1763381af8a 100644 --- a/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.PasteFormattingRule.cs +++ b/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.PasteFormattingRule.cs @@ -2,18 +2,16 @@ // 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 Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; -namespace Microsoft.CodeAnalysis.Editor.CSharp.Formatting +namespace Microsoft.CodeAnalysis.CSharp.Formatting { - internal partial class CSharpEditorFormattingService : IEditorFormattingService + internal partial class CSharpFormattingInteractionService : IFormattingInteractionService { internal class PasteFormattingRule : AbstractFormattingRule { - public override AdjustNewLinesOperation GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation) + public override AdjustNewLinesOperation? GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation) { if (currentToken.Parent != null) { diff --git a/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs b/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs similarity index 82% rename from src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs rename to src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs index 2755046386889..d038b32461384 100644 --- a/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs +++ b/src/Features/CSharp/Portable/Formatting/CSharpFormattingInteractionService.cs @@ -10,14 +10,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.BraceCompletion; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Indentation; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Shared.Options; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Host.Mef; @@ -25,24 +21,23 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.CSharp.Formatting +namespace Microsoft.CodeAnalysis.CSharp.Formatting { - [ExportLanguageService(typeof(IEditorFormattingService), LanguageNames.CSharp), Shared] - internal partial class CSharpEditorFormattingService : IEditorFormattingService + [ExportLanguageService(typeof(IFormattingInteractionService), LanguageNames.CSharp), Shared] + internal partial class CSharpFormattingInteractionService : IFormattingInteractionService { // All the characters that might potentially trigger formatting when typed private readonly char[] _supportedChars = ";{}#nte:)".ToCharArray(); - private readonly IIndentationManagerService _indentationManagerService; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpEditorFormattingService(IIndentationManagerService indentationManagerService) - => _indentationManagerService = indentationManagerService; + public CSharpFormattingInteractionService() + { + } public bool SupportsFormatDocument => true; public bool SupportsFormatOnPaste => true; @@ -74,7 +69,7 @@ public bool SupportsFormattingOnTypedCharacter(Document document, char ch) } // If format-on-typing is not on, then we don't support formatting on any other characters. - var autoFormattingOnTyping = options.GetOption(FeatureOnOffOptions.AutoFormattingOnTyping, LanguageNames.CSharp); + var autoFormattingOnTyping = options.GetOption(FormattingOptions2.AutoFormattingOnTyping, LanguageNames.CSharp); if (!autoFormattingOnTyping) { return false; @@ -85,7 +80,7 @@ public bool SupportsFormattingOnTypedCharacter(Document document, char ch) return false; } - if (ch == ';' && !options.GetOption(FeatureOnOffOptions.AutoFormattingOnSemicolon, LanguageNames.CSharp)) + if (ch == ';' && !options.GetOption(FormattingOptions2.AutoFormattingOnSemicolon, LanguageNames.CSharp)) { return false; } @@ -99,19 +94,31 @@ public bool SupportsFormattingOnTypedCharacter(Document document, char ch) return _supportedChars.Contains(ch); } - public async Task> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken) + public async Task> GetFormattingChangesAsync( + Document document, + TextSpan? textSpan, + DocumentOptionSet? documentOptions, + CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var span = textSpan ?? new TextSpan(0, root.FullSpan.Length); var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, span); - - var options = await document.GetDocumentOptionsWithInferredIndentationAsync(explicitFormat: true, indentationManagerService: _indentationManagerService, cancellationToken: cancellationToken).ConfigureAwait(false); + var options = documentOptions; + if (options == null) + { + var inferredIndentationService = document.Project.Solution.Workspace.Services.GetRequiredService(); + options = await inferredIndentationService.GetDocumentOptionsWithInferredIndentationAsync(document, explicitFormat: true, cancellationToken: cancellationToken).ConfigureAwait(false); + } return Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), - document.Project.Solution.Workspace, options, cancellationToken); + document.Project.Solution.Workspace, options, cancellationToken).ToImmutableArray(); } - public async Task> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + public async Task> GetFormattingChangesOnPasteAsync( + Document document, + TextSpan textSpan, + DocumentOptionSet? documentOptions, + CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -119,15 +126,20 @@ public async Task> GetFormattingChangesOnPasteAsync(Document d var service = document.GetLanguageService(); if (service == null) { - return SpecializedCollections.EmptyList(); + return ImmutableArray.Empty; } var rules = new List() { new PasteFormattingRule() }; rules.AddRange(service.GetDefaultFormattingRules()); - var options = await document.GetDocumentOptionsWithInferredIndentationAsync(explicitFormat: false, indentationManagerService: _indentationManagerService, cancellationToken: cancellationToken).ConfigureAwait(false); + var options = documentOptions; + if (options == null) + { + var inferredIndentationService = document.Project.Solution.Workspace.Services.GetRequiredService(); + options = await inferredIndentationService.GetDocumentOptionsWithInferredIndentationAsync(document, explicitFormat: false, cancellationToken: cancellationToken).ConfigureAwait(false); + } - return Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), document.Project.Solution.Workspace, options, rules, cancellationToken); + return Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), document.Project.Solution.Workspace, options, rules, cancellationToken).ToImmutableArray(); } private static IEnumerable GetFormattingRules(Document document, int position, SyntaxToken tokenBeforeCaret) @@ -137,8 +149,9 @@ private static IEnumerable GetFormattingRules(Document d return formattingRuleFactory.CreateRule(document, position).Concat(GetTypingRules(tokenBeforeCaret)).Concat(Formatter.GetDefaultFormattingRules(document)); } - Task?> IEditorFormattingService.GetFormattingChangesOnReturnAsync(Document document, int caretPosition, CancellationToken cancellationToken) - => SpecializedTasks.Null>(); + Task> IFormattingInteractionService.GetFormattingChangesOnReturnAsync( + Document document, int caretPosition, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) + => SpecializedTasks.EmptyImmutableArray(); private static async Task TokenShouldNotFormatOnTypeCharAsync( SyntaxToken token, CancellationToken cancellationToken) @@ -173,7 +186,12 @@ private static async Task TokenShouldNotFormatOnTypeCharAsync( return false; } - public async Task?> GetFormattingChangesAsync(Document document, char typedChar, int caretPosition, CancellationToken cancellationToken) + public async Task> GetFormattingChangesAsync( + Document document, + char typedChar, + int caretPosition, + DocumentOptionSet? documentOptions, + CancellationToken cancellationToken) { // first, find the token user just typed. var token = await GetTokenBeforeTheCaretAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); @@ -181,7 +199,7 @@ private static async Task TokenShouldNotFormatOnTypeCharAsync( !ValidSingleOrMultiCharactersTokenKind(typedChar, token.Kind()) || token.IsKind(SyntaxKind.EndOfFileToken, SyntaxKind.None)) { - return null; + return ImmutableArray.Empty; } var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -190,13 +208,20 @@ private static async Task TokenShouldNotFormatOnTypeCharAsync( var service = document.GetLanguageService(); if (service != null && service.IsInNonUserCode(token.SyntaxTree, caretPosition, cancellationToken)) { - return null; + return ImmutableArray.Empty; } var shouldNotFormat = await TokenShouldNotFormatOnTypeCharAsync(token, cancellationToken).ConfigureAwait(false); if (shouldNotFormat) { - return null; + return ImmutableArray.Empty; + } + + var options = documentOptions; + if (options == null) + { + var inferredIndentationService = document.Project.Solution.Workspace.Services.GetRequiredService(); + options = await inferredIndentationService.GetDocumentOptionsWithInferredIndentationAsync(document, explicitFormat: false, cancellationToken: cancellationToken).ConfigureAwait(false); } // Do not attempt to format on open/close brace if autoformat on close brace feature is @@ -227,8 +252,6 @@ private static async Task TokenShouldNotFormatOnTypeCharAsync( // If the user hits `}` then we will properly smart indent the `}` to match the `{`. // However, we won't touch any of the other code in that block, unlike if we were // formatting. - var options = await document.GetDocumentOptionsWithInferredIndentationAsync(explicitFormat: false, indentationManagerService: _indentationManagerService, cancellationToken: cancellationToken).ConfigureAwait(false); - var onlySmartIndent = (token.IsKind(SyntaxKind.CloseBraceToken) && OnlySmartIndentCloseBrace(options)) || (token.IsKind(SyntaxKind.OpenBraceToken) && OnlySmartIndentOpenBrace(options)); @@ -239,17 +262,17 @@ private static async Task TokenShouldNotFormatOnTypeCharAsync( // the span of the token. They're irrelevant and may screw up other code the user doesn't // want touched. var tokenEdits = await FormatTokenAsync(document, options, token, formattingRules, cancellationToken).ConfigureAwait(false); - return tokenEdits.Where(t => t.Span.Start >= token.FullSpan.Start).ToList(); + return tokenEdits.Where(t => t.Span.Start >= token.FullSpan.Start).ToImmutableArray(); } // if formatting range fails, do format token one at least var changes = await FormatRangeAsync(document, options, token, formattingRules, cancellationToken).ConfigureAwait(false); - if (changes.Count > 0) + if (changes.Length > 0) { return changes; } - return await FormatTokenAsync(document, options, token, formattingRules, cancellationToken).ConfigureAwait(false); + return (await FormatTokenAsync(document, options, token, formattingRules, cancellationToken).ConfigureAwait(false)).ToImmutableArray(); } private static bool OnlySmartIndentCloseBrace(DocumentOptionSet options) @@ -257,7 +280,7 @@ private static bool OnlySmartIndentCloseBrace(DocumentOptionSet options) // User does not want auto-formatting (either in general, or for close braces in // specific). So we only smart indent close braces when typed. return !options.GetOption(BraceCompletionOptions.AutoFormattingOnCloseBrace) || - !options.GetOption(FeatureOnOffOptions.AutoFormattingOnTyping); + !options.GetOption(FormattingOptions2.AutoFormattingOnTyping); } private static bool OnlySmartIndentOpenBrace(DocumentOptionSet options) @@ -265,7 +288,7 @@ private static bool OnlySmartIndentOpenBrace(DocumentOptionSet options) // User does not want auto-formatting . So we only smart indent open braces when typed. // Note: there is no specific option for controlling formatting on open brace. So we // don't have the symmetry with OnlySmartIndentCloseBrace. - return !options.GetOption(FeatureOnOffOptions.AutoFormattingOnTyping); + return !options.GetOption(FormattingOptions2.AutoFormattingOnTyping); } private static async Task GetTokenBeforeTheCaretAsync(Document document, int caretPosition, CancellationToken cancellationToken) @@ -289,7 +312,7 @@ private static async Task> FormatTokenAsync(Document document, private static ISmartTokenFormatter CreateSmartTokenFormatter(OptionSet optionSet, IEnumerable formattingRules, SyntaxNode root) => new CSharpSmartTokenFormatter(optionSet, formattingRules, (CompilationUnitSyntax)root); - private static async Task> FormatRangeAsync( + private static async Task> FormatRangeAsync( Document document, OptionSet options, SyntaxToken endToken, @@ -298,18 +321,18 @@ private static async Task> FormatRangeAsync( { if (!IsEndToken(endToken)) { - return SpecializedCollections.EmptyList(); + return ImmutableArray.Empty; } var tokenRange = FormattingRangeHelper.FindAppropriateRange(endToken); if (tokenRange == null || tokenRange.Value.Item1.Equals(tokenRange.Value.Item2)) { - return SpecializedCollections.EmptyList(); + return ImmutableArray.Empty; } if (IsInvalidTokenKind(tokenRange.Value.Item1) || IsInvalidTokenKind(tokenRange.Value.Item2)) { - return SpecializedCollections.EmptyList(); + return ImmutableArray.Empty; } var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -317,7 +340,7 @@ private static async Task> FormatRangeAsync( var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)root); var changes = formatter.FormatRange(document.Project.Solution.Workspace, tokenRange.Value.Item1, tokenRange.Value.Item2, cancellationToken); - return changes; + return changes.ToImmutableArray(); } private static IEnumerable GetTypingRules(SyntaxToken tokenBeforeCaret) diff --git a/src/EditorFeatures/CSharp/Formatting/TypingFormattingRule.cs b/src/Features/CSharp/Portable/Formatting/TypingFormattingRule.cs similarity index 90% rename from src/EditorFeatures/CSharp/Formatting/TypingFormattingRule.cs rename to src/Features/CSharp/Portable/Formatting/TypingFormattingRule.cs index b7c60a3812da9..811799d5ed05f 100644 --- a/src/EditorFeatures/CSharp/Formatting/TypingFormattingRule.cs +++ b/src/Features/CSharp/Portable/Formatting/TypingFormattingRule.cs @@ -2,17 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Formatting.Rules; -namespace Microsoft.CodeAnalysis.Editor.CSharp.Formatting +namespace Microsoft.CodeAnalysis.CSharp.Formatting { internal class TypingFormattingRule : BaseFormattingRule { @@ -76,7 +71,7 @@ private static bool TryAddSuppressionOnMissingCloseBraceCase(List= 1) + if (node is BlockSyntax { Statements: { Count: >= 1 } statements }) { // In the case of a block, see if the first statement is on the same line // as the open curly. If so then we'll want to consider the end of the @@ -85,7 +80,7 @@ private static bool TryAddSuppressionOnMissingCloseBraceCase(List AllDiagnosticIds = - ImmutableArray.Create(CS0122, CS1729, CS1739, CS1503, CS1660, CS7036, IDEDiagnosticIds.UnboundConstructorId); + ImmutableArray.Create(CS0122, CS1729, CS1739, CS1503, CS1660, CS7036); public static readonly ImmutableArray TooManyArgumentsDiagnosticIds = ImmutableArray.Create(CS1729); diff --git a/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs b/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs index 2bbebe4eb7164..34766918196a9 100644 --- a/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs +++ b/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs @@ -100,9 +100,7 @@ expression.Parent is BaseTypeSyntax baseType && // If it's in the base list of an interface or struct, then it's definitely an // interface. - return - baseList.IsParentKind(SyntaxKind.InterfaceDeclaration) || - baseList.IsParentKind(SyntaxKind.StructDeclaration); + return baseList.IsParentKind(SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration); } if (expression is TypeSyntax && @@ -673,7 +671,7 @@ internal override bool TryGetBaseList(ExpressionSyntax expression, out TypeKindO { if (node is BaseListSyntax) { - if (node.Parent != null && (node.Parent is InterfaceDeclarationSyntax || node.Parent is StructDeclarationSyntax)) + if (node.Parent.IsKind(SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration)) { typeKindValue = TypeKindOptions.Interface; return true; diff --git a/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs b/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs index 3f2aacc12ab14..7a1f1fdb11055 100644 --- a/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs +++ b/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs @@ -4,8 +4,10 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.GoToDefinition { @@ -20,5 +22,102 @@ public CSharpGoToDefinitionSymbolService() protected override ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, Compilation compilation) => symbol; + + protected override int? GetTargetPositionIfControlFlow(SemanticModel semanticModel, SyntaxToken token) + { + var node = token.GetRequiredParent(); + + switch (token.Kind()) + { + case SyntaxKind.ContinueKeyword: + var foundContinuedLoop = TryFindContinuableConstruct(node); + + return foundContinuedLoop?.IsContinuableConstruct() == true + ? foundContinuedLoop.GetFirstToken().Span.Start + : null; + + case SyntaxKind.BreakKeyword: + if (token.GetPreviousToken().IsKind(SyntaxKind.YieldKeyword)) + { + goto case SyntaxKind.YieldKeyword; + } + + var foundBrokenLoop = TryFindBreakableConstruct(node); + + return foundBrokenLoop?.IsBreakableConstruct() == true + ? foundBrokenLoop.GetLastToken().Span.End + : null; + + case SyntaxKind.YieldKeyword: + case SyntaxKind.ReturnKeyword: + { + var foundReturnableConstruct = TryFindContainingReturnableConstruct(node); + if (foundReturnableConstruct is null) + { + return null; + } + + var symbol = semanticModel.GetDeclaredSymbol(foundReturnableConstruct); + if (symbol is null) + { + // for lambdas + return foundReturnableConstruct.GetFirstToken().Span.Start; + } + + return symbol.Locations.FirstOrNone().SourceSpan.Start; + } + } + + return null; + + static SyntaxNode? TryFindContinuableConstruct(SyntaxNode? node) + { + while (node is not null && !node.IsContinuableConstruct()) + { + var kind = node.Kind(); + + if (node.IsReturnableConstruct() || + SyntaxFacts.GetTypeDeclarationKind(kind) != SyntaxKind.None) + { + return null; + } + + node = node.Parent; + } + + return node; + } + + static SyntaxNode? TryFindBreakableConstruct(SyntaxNode? node) + { + while (node is not null && !node.IsBreakableConstruct()) + { + if (node.IsReturnableConstruct() || + SyntaxFacts.GetTypeDeclarationKind(node.Kind()) != SyntaxKind.None) + { + return null; + } + + node = node.Parent; + } + + return node; + } + + static SyntaxNode? TryFindContainingReturnableConstruct(SyntaxNode? node) + { + while (node is not null && !node.IsReturnableConstruct()) + { + if (SyntaxFacts.GetTypeDeclarationKind(node.Kind()) != SyntaxKind.None) + { + return null; + } + + node = node.Parent; + } + + return node; + } + } } } diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs index 74bdae4b8143a..644850fb6aa4f 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs @@ -41,7 +41,7 @@ protected override bool TryInitializeState( baseType.IsParentKind(SyntaxKind.BaseList) && baseType.Type == interfaceNode) { - if (interfaceNode.Parent.Parent.IsParentKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration)) + if (interfaceNode.Parent.Parent.IsParentKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration)) { var interfaceSymbolInfo = model.GetSymbolInfo(interfaceNode, cancellationToken); if (interfaceSymbolInfo.CandidateReason != CandidateReason.WrongArity) diff --git a/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs b/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs index 7a53dcad4cb55..f757edebde3f4 100644 --- a/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs +++ b/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs @@ -83,7 +83,7 @@ private static HintKind GetKind(ExpressionSyntax arg) CastExpressionSyntax cast => GetKind(cast.Expression), PrefixUnaryExpressionSyntax prefix => GetKind(prefix.Operand), // Treat `expr!` the same as `expr` (i.e. treat `!` as if it's just trivia). - PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.SuppressNullableWarningExpression } postfix => GetKind(postfix.Operand), + PostfixUnaryExpressionSyntax(SyntaxKind.SuppressNullableWarningExpression) postfix => GetKind(postfix.Operand), _ => HintKind.Other, }; } diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceParameterCodeRefactoringProvider.cs new file mode 100644 index 0000000000000..e51a10a4edaed --- /dev/null +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceParameterCodeRefactoringProvider.cs @@ -0,0 +1,45 @@ +// 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.Composition; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.IntroduceVariable; + +namespace Microsoft.CodeAnalysis.CSharp.IntroduceVariable +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.IntroduceParameter), Shared] + internal partial class CSharpIntroduceParameterCodeRefactoringProvider : AbstractIntroduceParameterService< + ExpressionSyntax, + InvocationExpressionSyntax, + ObjectCreationExpressionSyntax, + IdentifierNameSyntax> + { + [ImportingConstructor] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + public CSharpIntroduceParameterCodeRefactoringProvider() + { + } + + protected override SyntaxNode GenerateExpressionFromOptionalParameter(IParameterSymbol parameterSymbol) + { + return ExpressionGenerator.GenerateExpression(parameterSymbol.Type, parameterSymbol.ExplicitDefaultValue, canUseFieldReference: true); + } + + protected override SyntaxNode? GetLocalDeclarationFromDeclarator(SyntaxNode variableDecl) + { + return variableDecl.Parent?.Parent as LocalDeclarationStatementSyntax; + } + + protected override bool IsDestructor(IMethodSymbol methodSymbol) + { + return false; + } + + protected override SyntaxNode UpdateArgumentListSyntax(SyntaxNode argumentList, SeparatedSyntaxList arguments) + => ((ArgumentListSyntax)argumentList).WithArguments(arguments); + } +} diff --git a/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs b/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs index a96163df9589d..5feea61fb0469 100644 --- a/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs +++ b/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs @@ -81,6 +81,14 @@ protected override void AddAwaitableExtensionPrefix() Space()); } + protected override void AddEnumUnderlyingTypeSeparator() + { + AddToGroup(SymbolDescriptionGroups.MainDescription, + Space(), + Punctuation(":"), + Space()); + } + protected override Task> GetInitializerSourcePartsAsync( ISymbol symbol) { diff --git a/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeFixHelper.cs b/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeFixHelper.cs index 98f7b9a059cc6..b2034c56f76b9 100644 --- a/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeFixHelper.cs +++ b/src/Features/CSharp/Portable/MakeLocalFunctionStatic/MakeLocalFunctionStaticCodeFixHelper.cs @@ -43,6 +43,7 @@ public static async Task MakeLocalFunctionStaticAsync( var root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; var semanticModel = (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false))!; var localFunctionSymbol = semanticModel.GetDeclaredSymbol(localFunction, cancellationToken); + Contract.ThrowIfNull(localFunctionSymbol, "We should have gotten a method symbol for a local function."); var documentImmutableSet = ImmutableHashSet.Create(document); // Finds all the call sites of the local function diff --git a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 79c2085b9d102..c8557c23d54ff 100644 --- a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -59,9 +59,7 @@ protected override SyntaxNode AddAsyncTokenAndFixReturnType( { case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, knownTypes); case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, knownTypes); - case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); - case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); - case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); + case AnonymousFunctionExpressionSyntax anonymous: return FixAnonymousFunction(anonymous); default: return node; } } @@ -167,22 +165,10 @@ private static SyntaxTokenList AddAsyncModifierWithCorrectedTrivia(SyntaxTokenLi return result; } - private static SyntaxNode FixParenthesizedLambda(ParenthesizedLambdaExpressionSyntax lambda) + private static SyntaxNode FixAnonymousFunction(AnonymousFunctionExpressionSyntax anonymous) { - return lambda.WithoutLeadingTrivia() - .WithAsyncKeyword(s_asyncToken.WithPrependedLeadingTrivia(lambda.GetLeadingTrivia())); - } - - private static SyntaxNode FixSimpleLambda(SimpleLambdaExpressionSyntax lambda) - { - return lambda.WithoutLeadingTrivia() - .WithAsyncKeyword(s_asyncToken.WithPrependedLeadingTrivia(lambda.GetLeadingTrivia())); - } - - private static SyntaxNode FixAnonymousMethod(AnonymousMethodExpressionSyntax method) - { - return method.WithoutLeadingTrivia() - .WithAsyncKeyword(s_asyncToken.WithPrependedLeadingTrivia(method.GetLeadingTrivia())); + return anonymous.WithoutLeadingTrivia() + .WithAsyncKeyword(s_asyncToken.WithPrependedLeadingTrivia(anonymous.GetLeadingTrivia())); } } } diff --git a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj index 47d616b13cae2..8f804914858a2 100644 --- a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj +++ b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj @@ -26,6 +26,8 @@ + + + /// + /// public int GetF(int x, int y) // Generated method + /// { + /// return x * y; + /// } + /// + /// public void M(int x, int y, int f) + /// { + /// Console.WriteLine(f); + /// } + /// + /// public void InvokeMethod() + /// { + /// M(5, 6, GetF(5, 6)); //Fills in with call to generated method + /// } + /// + /// ----------------------------------------------------------------------- + /// ****Overload Example:**** + /// public void M(int x, int y) + /// { + /// int f = [|x * y|]; + /// Console.WriteLine(f); + /// } + /// + /// public void InvokeMethod() + /// { + /// M(5, 6); + /// } + /// + /// ----------------------------------------------------> + /// + /// public void M(int x, int y) // Generated overload + /// { + /// M(x, y, x * y) + /// } + /// + /// public void M(int x, int y, int f) + /// { + /// Console.WriteLine(f); + /// } + /// + /// public void InvokeMethod() + /// { + /// M(5, 6); + /// } + /// + private async Task ModifyDocumentInvocationsTrampolineOverloadAndIntroduceParameterAsync(Compilation compilation, Document currentDocument, + List invocations, int insertionIndex, CancellationToken cancellationToken) + { + var invocationSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var editor = new SyntaxEditor(root, _generator); + var parameterName = await GetNewParameterNameAsync(cancellationToken).ConfigureAwait(false); + var expressionParameterMap = await MapExpressionToParametersAsync(cancellationToken).ConfigureAwait(false); + // Creating a new method name by concatenating the parameter name that has been upper-cased. + var newMethodIdentifier = "Get" + parameterName.ToPascalCase(); + var validParameters = _methodSymbol.Parameters.Intersect(expressionParameterMap.Values).ToImmutableArray(); + + if (_actionKind is IntroduceParameterCodeActionKind.Trampoline) + { + // Creating an empty map here to reuse so that we do not create a new dictionary for + // every single invocation. + var parameterToArgumentMap = new Dictionary(); + foreach (var invocation in invocations) + { + var argumentListSyntax = _syntaxFacts.GetArgumentListOfInvocationExpression(invocation); + editor.ReplaceNode(argumentListSyntax, (currentArgumentListSyntax, _) => + { + return GenerateNewArgumentListSyntaxForTrampoline(compilation, invocationSemanticModel, + parameterToArgumentMap, currentArgumentListSyntax, argumentListSyntax, invocation, + validParameters, parameterName, newMethodIdentifier, insertionIndex, cancellationToken); + }); + } + } + + // If you are at the original document, then also introduce the new method and introduce the parameter. + if (currentDocument.Id == _originalDocument.Id) + { + var newMethodNode = _actionKind is IntroduceParameterCodeActionKind.Trampoline + ? await ExtractMethodAsync(validParameters, newMethodIdentifier, _generator, cancellationToken).ConfigureAwait(false) + : await GenerateNewMethodOverloadAsync(insertionIndex, _generator, cancellationToken).ConfigureAwait(false); + editor.InsertBefore(_containerMethod, newMethodNode); + + await UpdateExpressionInOriginalFunctionAsync(editor, cancellationToken).ConfigureAwait(false); + var parameterType = await GetTypeOfExpressionAsync(cancellationToken).ConfigureAwait(false); + var parameter = _generator.ParameterDeclaration(parameterName, _generator.TypeExpression(parameterType)); + editor.InsertParameter(_containerMethod, insertionIndex, parameter); + } + + return editor.GetChangedRoot(); + + // Adds an argument which is an invocation of the newly created method to the callsites + // of the method invocations where a parameter was added. + // Example: + // public void M(int x, int y) + // { + // int f = [|x * y|]; + // Console.WriteLine(f); + // } + // + // public void InvokeMethod() + // { + // M(5, 6); + // } + // + // ----------------------------------------------------> + // + // public int GetF(int x, int y) + // { + // return x * y; + // } + // + // public void M(int x, int y) + // { + // int f = x * y; + // Console.WriteLine(f); + // } + // + // public void InvokeMethod() + // { + // M(5, 6, GetF(5, 6)); // This is the generated invocation which is a new argument at the call site + // } + SyntaxNode GenerateNewArgumentListSyntaxForTrampoline(Compilation compilation, SemanticModel invocationSemanticModel, + Dictionary parameterToArgumentMap, SyntaxNode currentArgumentListSyntax, + SyntaxNode argumentListSyntax, SyntaxNode invocation, ImmutableArray validParameters, + string parameterName, string newMethodIdentifier, int insertionIndex, CancellationToken cancellationToken) + { + var invocationArguments = _syntaxFacts.GetArgumentsOfArgumentList(argumentListSyntax); + parameterToArgumentMap.Clear(); + MapParameterToArgumentsAtInvocation(parameterToArgumentMap, invocationArguments, invocationSemanticModel, cancellationToken); + var currentInvocationArguments = _syntaxFacts.GetArgumentsOfArgumentList(currentArgumentListSyntax); + var requiredArguments = new List(); + + foreach (var parameterSymbol in validParameters) + { + if (parameterToArgumentMap.TryGetValue(parameterSymbol, out var index)) + { + requiredArguments.Add(currentInvocationArguments[index]); + } + } + + var conditionalRoot = _syntaxFacts.GetRootConditionalAccessExpression(invocation); + var named = ShouldArgumentBeNamed(compilation, invocationSemanticModel, invocationArguments, insertionIndex, cancellationToken); + var newMethodInvocation = GenerateNewMethodInvocation(invocation, requiredArguments, newMethodIdentifier); + + SeparatedSyntaxList allArguments; + if (conditionalRoot is null) + { + allArguments = AddArgumentToArgumentList(currentInvocationArguments, newMethodInvocation, parameterName, insertionIndex, named); + } + else + { + // Conditional Access expressions are parents of invocations, so it is better to just replace the + // invocation in place then rebuild the tree structure. + var expressionsWithConditionalAccessors = conditionalRoot.ReplaceNode(invocation, newMethodInvocation); + allArguments = AddArgumentToArgumentList(currentInvocationArguments, expressionsWithConditionalAccessors, parameterName, insertionIndex, named); + } + + return _service.UpdateArgumentListSyntax(currentArgumentListSyntax, allArguments); + } + } + + private async Task GetTypeOfExpressionAsync(CancellationToken cancellationToken) + { + var semanticModel = await _originalDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var typeSymbol = semanticModel.GetTypeInfo(_expression, cancellationToken).ConvertedType ?? semanticModel.Compilation.ObjectType; + return typeSymbol; + } + + private SyntaxNode GenerateNewMethodInvocation(SyntaxNode invocation, List arguments, string newMethodIdentifier) + { + var methodName = _generator.IdentifierName(newMethodIdentifier); + var fullExpression = _syntaxFacts.GetExpressionOfInvocationExpression(invocation); + if (_syntaxFacts.IsAnyMemberAccessExpression(fullExpression)) + { + var receiverExpression = _syntaxFacts.GetExpressionOfMemberAccessExpression(fullExpression); + methodName = _generator.MemberAccessExpression(receiverExpression, newMethodIdentifier); + } + else if (_syntaxFacts.IsMemberBindingExpression(fullExpression)) + { + methodName = _generator.MemberBindingExpression(_generator.IdentifierName(newMethodIdentifier)); + } + + return _generator.InvocationExpression(methodName, arguments); + } + + /// + /// Generates a method declaration containing a return expression of the highlighted expression. + /// Example: + /// public void M(int x, int y) + /// { + /// int f = [|x * y|]; + /// } + /// + /// ----------------------------------------------------> + /// + /// public int GetF(int x, int y) + /// { + /// return x * y; + /// } + /// + /// public void M(int x, int y) + /// { + /// int f = x * y; + /// } + /// + private async Task ExtractMethodAsync(ImmutableArray validParameters, string newMethodIdentifier, SyntaxGenerator generator, CancellationToken cancellationToken) + { + // Remove trivia so the expression is in a single line and does not affect the spacing of the following line + var returnStatement = generator.ReturnStatement(_expression.WithoutTrivia()); + var typeSymbol = await GetTypeOfExpressionAsync(cancellationToken).ConfigureAwait(false); + var newMethodDeclaration = await CreateMethodDeclarationAsync(returnStatement, + validParameters, newMethodIdentifier, typeSymbol, isTrampoline: true, cancellationToken).ConfigureAwait(false); + return newMethodDeclaration; + } + + /// + /// Generates a method declaration containing a call to the method that introduced the parameter. + /// Example: + /// + /// ***This is an intermediary step in which the original function has not be updated yet + /// public void M(int x, int y) + /// { + /// int f = [|x * y|]; + /// } + /// + /// ----------------------------------------------------> + /// + /// public void M(int x, int y) // Generated overload + /// { + /// M(x, y, x * y); + /// } + /// + /// public void M(int x, int y) // Original function (which will be mutated in a later step) + /// { + /// int f = x * y; + /// } + /// + private async Task GenerateNewMethodOverloadAsync(int insertionIndex, SyntaxGenerator generator, CancellationToken cancellationToken) + { + // Need the parameters from the original function as arguments for the invocation + var arguments = generator.CreateArguments(_methodSymbol.Parameters); + + // Remove trivia so the expression is in a single line and does not affect the spacing of the following line + arguments = arguments.Insert(insertionIndex, generator.Argument(_expression.WithoutTrivia())); + var memberName = _methodSymbol.IsGenericMethod + ? generator.GenericName(_methodSymbol.Name, _methodSymbol.TypeArguments) + : generator.IdentifierName(_methodSymbol.Name); + var invocation = generator.InvocationExpression(memberName, arguments); + + var newStatement = _methodSymbol.ReturnsVoid + ? generator.ExpressionStatement(invocation) + : generator.ReturnStatement(invocation); + + var newMethodDeclaration = await CreateMethodDeclarationAsync(newStatement, + validParameters: null, newMethodIdentifier: null, typeSymbol: null, isTrampoline: false, cancellationToken).ConfigureAwait(false); + return newMethodDeclaration; + } + + private async Task CreateMethodDeclarationAsync(SyntaxNode newStatement, ImmutableArray? validParameters, + string? newMethodIdentifier, ITypeSymbol? typeSymbol, bool isTrampoline, CancellationToken cancellationToken) + { + var codeGenerationService = _originalDocument.GetRequiredLanguageService(); + var options = await _originalDocument.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + var newMethod = isTrampoline + ? CodeGenerationSymbolFactory.CreateMethodSymbol(_methodSymbol, name: newMethodIdentifier, parameters: validParameters, statements: ImmutableArray.Create(newStatement), returnType: typeSymbol) + : CodeGenerationSymbolFactory.CreateMethodSymbol(_methodSymbol, statements: ImmutableArray.Create(newStatement), containingType: _methodSymbol.ContainingType); + var newMethodDeclaration = codeGenerationService.CreateMethodDeclaration(newMethod, options: new CodeGenerationOptions(options: options, parseOptions: _expression.SyntaxTree.Options)); + return newMethodDeclaration; + } + + /// + /// This method goes through all the invocation sites and adds a new argument with the expression to be added. + /// It also introduces a parameter at the original method site. + /// + /// Example: + /// public void M(int x, int y) + /// { + /// int f = [|x * y|]; + /// } + /// + /// public void InvokeMethod() + /// { + /// M(5, 6); + /// } + /// + /// ----------------------------------------------------> + /// + /// public void M(int x, int y, int f) // parameter gets introduced + /// { + /// } + /// + /// public void InvokeMethod() + /// { + /// M(5, 6, 5 * 6); // argument gets added to callsite + /// } + /// + private async Task ModifyDocumentInvocationsAndIntroduceParameterAsync(Compilation compilation, Document document, int insertionIndex, + List invocations, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var editor = new SyntaxEditor(root, _generator); + var invocationSemanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var parameterToArgumentMap = new Dictionary(); + var expressionToParameterMap = await MapExpressionToParametersAsync(cancellationToken).ConfigureAwait(false); + var parameterName = await GetNewParameterNameAsync(cancellationToken).ConfigureAwait(false); + foreach (var invocation in invocations) + { + var expressionEditor = new SyntaxEditor(_expression, _generator); + + var argumentListSyntax = invocation is TObjectCreationExpressionSyntax + ? _syntaxFacts.GetArgumentListOfObjectCreationExpression(invocation) + : _syntaxFacts.GetArgumentListOfInvocationExpression(invocation); + + var invocationArguments = _syntaxFacts.GetArgumentsOfArgumentList(argumentListSyntax); + parameterToArgumentMap.Clear(); + MapParameterToArgumentsAtInvocation(parameterToArgumentMap, invocationArguments, invocationSemanticModel, cancellationToken); + + if (argumentListSyntax is not null) + { + editor.ReplaceNode(argumentListSyntax, (currentArgumentListSyntax, _) => + { + var updatedInvocationArguments = _syntaxFacts.GetArgumentsOfArgumentList(currentArgumentListSyntax); + var updatedExpression = CreateNewArgumentExpression(expressionEditor, expressionToParameterMap, parameterToArgumentMap, updatedInvocationArguments); + var named = ShouldArgumentBeNamed(compilation, invocationSemanticModel, invocationArguments, insertionIndex, cancellationToken); + var allArguments = AddArgumentToArgumentList(updatedInvocationArguments, + updatedExpression.WithAdditionalAnnotations(Formatter.Annotation), parameterName, insertionIndex, named); + return _service.UpdateArgumentListSyntax(currentArgumentListSyntax, allArguments); + }); + } + } + + // If you are at the original document, then also introduce the new method and introduce the parameter. + if (document.Id == _originalDocument.Id) + { + await UpdateExpressionInOriginalFunctionAsync(editor, cancellationToken).ConfigureAwait(false); + var parameterType = await GetTypeOfExpressionAsync(cancellationToken).ConfigureAwait(false); + var parameter = _generator.ParameterDeclaration(name: parameterName, type: + _generator.TypeExpression(parameterType)); + editor.InsertParameter(_containerMethod, insertionIndex, parameter); + } + + return editor.GetChangedRoot(); + } + + /// + /// This method iterates through the variables in the expression and maps the variables back to the parameter + /// it is associated with. It then maps the parameter back to the argument at the invocation site and gets the + /// index to retrieve the updated arguments at the invocation. + /// + private TExpressionSyntax CreateNewArgumentExpression(SyntaxEditor editor, + Dictionary mappingDictionary, + Dictionary parameterToArgumentMap, + SeparatedSyntaxList updatedInvocationArguments) + { + foreach (var (variable, mappedParameter) in mappingDictionary) + { + var parameterMapped = parameterToArgumentMap.TryGetValue(mappedParameter, out var index); + if (parameterMapped) + { + var updatedInvocationArgument = updatedInvocationArguments[index]; + var argumentExpression = _syntaxFacts.GetExpressionOfArgument(updatedInvocationArgument); + var parenthesizedArgumentExpression = editor.Generator.AddParentheses(argumentExpression, includeElasticTrivia: false); + editor.ReplaceNode(variable, parenthesizedArgumentExpression); + } + else if (mappedParameter.HasExplicitDefaultValue) + { + var generatedExpression = _service.GenerateExpressionFromOptionalParameter(mappedParameter); + var parenthesizedGeneratedExpression = editor.Generator.AddParentheses(generatedExpression, includeElasticTrivia: false); + editor.ReplaceNode(variable, parenthesizedGeneratedExpression); + } + } + + return (TExpressionSyntax)editor.GetChangedRoot(); + } + + /// + /// If the parameter is optional and the invocation does not specify the parameter, then + /// a named argument needs to be introduced. + /// + private SeparatedSyntaxList AddArgumentToArgumentList( + SeparatedSyntaxList invocationArguments, SyntaxNode newArgumentExpression, + string parameterName, int insertionIndex, bool named) + { + var argument = named + ? _generator.Argument(parameterName, RefKind.None, newArgumentExpression) + : _generator.Argument(newArgumentExpression); + return invocationArguments.Insert(insertionIndex, argument); + } + + private bool ShouldArgumentBeNamed(Compilation compilation, SemanticModel semanticModel, + SeparatedSyntaxList invocationArguments, int methodInsertionIndex, + CancellationToken cancellationToken) + { + var invocationInsertIndex = 0; + foreach (var invocationArgument in invocationArguments) + { + var argumentParameter = _semanticFacts.FindParameterForArgument(semanticModel, invocationArgument, cancellationToken); + if (argumentParameter is not null && ShouldParameterBeSkipped(compilation, argumentParameter)) + { + invocationInsertIndex++; + } + else + { + break; + } + } + + return invocationInsertIndex < methodInsertionIndex; + } + + private static bool ShouldParameterBeSkipped(Compilation compilation, IParameterSymbol parameter) + => !parameter.HasExplicitDefaultValue && + !parameter.IsParams && + !parameter.Type.Equals(compilation.GetTypeByMetadataName(typeof(CancellationToken)?.FullName!)); + + private void MapParameterToArgumentsAtInvocation( + Dictionary mapping, SeparatedSyntaxList arguments, + SemanticModel invocationSemanticModel, CancellationToken cancellationToken) + { + for (var i = 0; i < arguments.Count; i++) + { + var argumentParameter = _semanticFacts.FindParameterForArgument(invocationSemanticModel, arguments[i], cancellationToken); + if (argumentParameter is not null) + { + mapping[argumentParameter] = i; + } + } + } + + /// + /// Gets the matches of the expression and replaces them with the identifier. + /// Special case for the original matching expression, if its parent is a LocalDeclarationStatement then it can + /// be removed because assigning the local dec variable to a parameter is repetitive. Does not need a rename + /// annotation since the user has already named the local declaration. + /// Otherwise, it needs to have a rename annotation added to it because the new parameter gets a randomly + /// generated name that the user can immediately change. + /// + private async Task UpdateExpressionInOriginalFunctionAsync(SyntaxEditor editor, CancellationToken cancellationToken) + { + var generator = editor.Generator; + var matches = await FindMatchesAsync(cancellationToken).ConfigureAwait(false); + var parameterName = await GetNewParameterNameAsync(cancellationToken).ConfigureAwait(false); + var replacement = (TIdentifierNameSyntax)generator.IdentifierName(parameterName); + + foreach (var match in matches) + { + // Special case the removal of the originating expression to either remove the local declaration + // or to add a rename annotation. + if (!match.Equals(_expression)) + { + editor.ReplaceNode(match, replacement); + } + else + { + if (ShouldRemoveVariableDeclaratorContainingExpression(out _, out var localDeclaration)) + { + editor.RemoveNode(localDeclaration); + } + else + { + // Found the initially selected expression. Replace it with the new name we choose, but also annotate + // that name with the RenameAnnotation so a rename session is started where the user can pick their + // own preferred name. + replacement = (TIdentifierNameSyntax)generator.IdentifierName(generator.Identifier(parameterName) + .WithAdditionalAnnotations(RenameAnnotation.Create())); + editor.ReplaceNode(match, replacement); + } + } + } + } + + /// + /// Finds the matches of the expression within the same block. + /// + private async Task> FindMatchesAsync(CancellationToken cancellationToken) + { + if (!_allOccurrences) + { + return SpecializedCollections.SingletonEnumerable(_expression); + } + + var syntaxFacts = _originalDocument.GetRequiredLanguageService(); + var originalSemanticModel = await _originalDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var matches = from nodeInCurrent in _containerMethod.DescendantNodesAndSelf().OfType() + where NodeMatchesExpression(originalSemanticModel, nodeInCurrent, cancellationToken) + select nodeInCurrent; + return matches; + } + + private bool NodeMatchesExpression(SemanticModel originalSemanticModel, TExpressionSyntax currentNode, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (currentNode == _expression) + { + return true; + } + + return SemanticEquivalence.AreEquivalent( + originalSemanticModel, originalSemanticModel, _expression, currentNode); + } + } + } +} diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs new file mode 100644 index 0000000000000..15f6ea0cd5e6c --- /dev/null +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceParameterService.cs @@ -0,0 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.CodeActions.CodeAction; + +namespace Microsoft.CodeAnalysis.IntroduceVariable +{ + internal abstract partial class AbstractIntroduceParameterService< + TExpressionSyntax, + TInvocationExpressionSyntax, + TObjectCreationExpressionSyntax, + TIdentifierNameSyntax> : CodeRefactoringProvider + where TExpressionSyntax : SyntaxNode + where TInvocationExpressionSyntax : TExpressionSyntax + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TIdentifierNameSyntax : TExpressionSyntax + { + protected abstract SyntaxNode GenerateExpressionFromOptionalParameter(IParameterSymbol parameterSymbol); + protected abstract SyntaxNode UpdateArgumentListSyntax(SyntaxNode argumentList, SeparatedSyntaxList arguments); + protected abstract SyntaxNode? GetLocalDeclarationFromDeclarator(SyntaxNode variableDecl); + protected abstract bool IsDestructor(IMethodSymbol methodSymbol); + + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var (document, textSpan, cancellationToken) = context; + if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles) + { + return; + } + + var expression = await document.TryGetRelevantNodeAsync(textSpan, cancellationToken).ConfigureAwait(false); + if (expression == null || CodeRefactoringHelpers.IsNodeUnderselected(expression, textSpan)) + { + return; + } + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var expressionType = semanticModel.GetTypeInfo(expression, cancellationToken).Type; + if (expressionType is null or IErrorTypeSymbol) + { + return; + } + + var syntaxFacts = document.GetRequiredLanguageService(); + + // Need to special case for expressions that are contained within a parameter + // because it is technically "contained" within a method, but an expression in a parameter does not make + // sense to introduce. + var parameterNode = expression.FirstAncestorOrSelf(node => syntaxFacts.IsParameter(node)); + if (parameterNode is not null) + { + return; + } + + var generator = SyntaxGenerator.GetGenerator(document); + var containingMethod = expression.FirstAncestorOrSelf(node => generator.GetParameterListNode(node) is not null); + + if (containingMethod is null) + { + return; + } + + var containingSymbol = semanticModel.GetDeclaredSymbol(containingMethod, cancellationToken); + if (containingSymbol is not IMethodSymbol methodSymbol) + { + return; + } + + // Code actions for trampoline and overloads will not be offered if the method is a constructor. + // Code actions for overloads will not be offered if the method if the method is a local function. + var methodKind = methodSymbol.MethodKind; + if (methodKind is not (MethodKind.Ordinary or MethodKind.LocalFunction or MethodKind.Constructor)) + { + return; + } + + if (IsDestructor(methodSymbol)) + { + return; + } + + var actions = await GetActionsAsync(document, expression, methodSymbol, containingMethod, cancellationToken).ConfigureAwait(false); + + if (actions is null) + { + return; + } + + var singleLineExpression = syntaxFacts.ConvertToSingleLine(expression); + var nodeString = singleLineExpression.ToString(); + + context.RegisterRefactoring(new CodeActionWithNestedActions( + string.Format(FeaturesResources.Introduce_parameter_for_0, nodeString), actions.Value.actions, isInlinable: false), textSpan); + context.RegisterRefactoring(new CodeActionWithNestedActions( + string.Format(FeaturesResources.Introduce_parameter_for_all_occurrences_of_0, nodeString), actions.Value.actionsAllOccurrences, isInlinable: false), textSpan); + } + + /// + /// Creates new code actions for each introduce parameter possibility. + /// Does not create actions for overloads/trampoline if there are optional parameters or if the methodSymbol + /// is a constructor. + /// + private async Task<(ImmutableArray actions, ImmutableArray actionsAllOccurrences)?> GetActionsAsync(Document document, + TExpressionSyntax expression, IMethodSymbol methodSymbol, SyntaxNode containingMethod, + CancellationToken cancellationToken) + { + var (shouldDisplay, containsClassExpression) = await ShouldExpressionDisplayCodeActionAsync( + document, expression, cancellationToken).ConfigureAwait(false); + if (!shouldDisplay) + { + return null; + } + + using var actionsBuilder = TemporaryArray.Empty; + using var actionsBuilderAllOccurrences = TemporaryArray.Empty; + var syntaxFacts = document.GetRequiredLanguageService(); + + if (!containsClassExpression) + { + actionsBuilder.Add(CreateNewCodeAction(FeaturesResources.and_update_call_sites_directly, allOccurrences: false, IntroduceParameterCodeActionKind.Refactor)); + actionsBuilderAllOccurrences.Add(CreateNewCodeAction(FeaturesResources.and_update_call_sites_directly, allOccurrences: true, IntroduceParameterCodeActionKind.Refactor)); + } + + if (methodSymbol.MethodKind is not MethodKind.Constructor) + { + actionsBuilder.Add(CreateNewCodeAction( + FeaturesResources.into_extracted_method_to_invoke_at_call_sites, allOccurrences: false, IntroduceParameterCodeActionKind.Trampoline)); + actionsBuilderAllOccurrences.Add(CreateNewCodeAction( + FeaturesResources.into_extracted_method_to_invoke_at_call_sites, allOccurrences: true, IntroduceParameterCodeActionKind.Trampoline)); + + if (methodSymbol.MethodKind is not MethodKind.LocalFunction) + { + actionsBuilder.Add(CreateNewCodeAction( + FeaturesResources.into_new_overload, allOccurrences: false, IntroduceParameterCodeActionKind.Overload)); + actionsBuilderAllOccurrences.Add(CreateNewCodeAction( + FeaturesResources.into_new_overload, allOccurrences: true, IntroduceParameterCodeActionKind.Overload)); + } + } + + return (actionsBuilder.ToImmutableAndClear(), actionsBuilderAllOccurrences.ToImmutableAndClear()); + + // Local function to create a code action with more ease + MyCodeAction CreateNewCodeAction(string actionName, bool allOccurrences, IntroduceParameterCodeActionKind selectedCodeAction) + { + return new MyCodeAction(actionName, c => IntroduceParameterAsync( + document, expression, methodSymbol, containingMethod, allOccurrences, selectedCodeAction, c)); + } + } + + /// + /// Determines if the expression is something that should have code actions displayed for it. + /// Depends upon the identifiers in the expression mapping back to parameters. + /// Does not handle params parameters. + /// + private static async Task<(bool shouldDisplay, bool containsClassExpression)> ShouldExpressionDisplayCodeActionAsync( + Document document, TExpressionSyntax expression, CancellationToken cancellationToken) + { + var variablesInExpression = expression.DescendantNodes(); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + foreach (var variable in variablesInExpression) + { + var symbol = semanticModel.GetSymbolInfo(variable, cancellationToken).Symbol; + + // If the expression contains locals or range variables then we do not want to offer + // code actions since there will be errors at call sites. + if (symbol is IRangeVariableSymbol or ILocalSymbol) + { + return (false, false); + } + + if (symbol is IParameterSymbol parameter) + { + // We do not want to offer code actions if the expressions contains references + // to params parameters because it is difficult to know what is being referenced + // at the callsites. + if (parameter.IsParams) + { + return (false, false); + } + } + } + + // If expression contains this or base keywords, implicitly or explicitly, + // then we do not want to refactor call sites that are not overloads/trampolines + // because we do not know if the class specific information is available in other documents. + var operation = semanticModel.GetOperation(expression, cancellationToken); + var containsClassSpecificStatement = false; + if (operation is not null) + { + containsClassSpecificStatement = operation.Descendants().Any(op => op.Kind == OperationKind.InstanceReference); + } + + return (true, containsClassSpecificStatement); + } + + /// + /// Introduces a new parameter and refactors all the call sites based on the selected code action. + /// + private async Task IntroduceParameterAsync(Document originalDocument, TExpressionSyntax expression, + IMethodSymbol methodSymbol, SyntaxNode containingMethod, bool allOccurrences, IntroduceParameterCodeActionKind selectedCodeAction, + CancellationToken cancellationToken) + { + var methodCallSites = await FindCallSitesAsync(originalDocument, methodSymbol, cancellationToken).ConfigureAwait(false); + + var modifiedSolution = originalDocument.Project.Solution; + var rewriter = new IntroduceParameterDocumentRewriter(this, originalDocument, + expression, methodSymbol, containingMethod, selectedCodeAction, allOccurrences); + + foreach (var (project, projectCallSites) in methodCallSites.GroupBy(kvp => kvp.Key.Project)) + { + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + foreach (var (document, invocations) in projectCallSites) + { + var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); + modifiedSolution = modifiedSolution.WithDocumentSyntaxRoot(originalDocument.Id, newRoot); + } + } + + return modifiedSolution; + } + + /// + /// Locates all the call sites of the method that introduced the parameter + /// + protected static async Task>> FindCallSitesAsync( + Document document, IMethodSymbol methodSymbol, CancellationToken cancellationToken) + { + var methodCallSites = new Dictionary>(); + var progress = new StreamingProgressCollector(); + await SymbolFinder.FindReferencesAsync( + methodSymbol, document.Project.Solution, progress, + documents: null, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); + var referencedSymbols = progress.GetReferencedSymbols(); + + // Ordering by descending to sort invocations by starting span to account for nested invocations + var referencedLocations = referencedSymbols.SelectMany(referencedSymbol => referencedSymbol.Locations) + .Distinct().Where(reference => !reference.IsImplicit) + .OrderByDescending(reference => reference.Location.SourceSpan.Start); + + // Adding the original document to ensure that it will be seen again when processing the call sites + // in order to update the original expression and containing method. + methodCallSites.Add(document, new List()); + var syntaxFacts = document.GetRequiredLanguageService(); + + foreach (var refLocation in referencedLocations) + { + // Does not support cross-language references currently + if (refLocation.Document.Project.Language == document.Project.Language) + { + var reference = refLocation.Location.FindNode(cancellationToken).GetRequiredParent(); + if (reference is not (TObjectCreationExpressionSyntax or TInvocationExpressionSyntax)) + { + reference = reference.GetRequiredParent(); + } + + // Only adding items that are of type InvocationExpressionSyntax or TObjectCreationExpressionSyntax + var invocationOrCreation = reference as TObjectCreationExpressionSyntax ?? (SyntaxNode?)(reference as TInvocationExpressionSyntax); + if (invocationOrCreation is null) + { + continue; + } + + if (!methodCallSites.TryGetValue(refLocation.Document, out var list)) + { + list = new List(); + methodCallSites.Add(refLocation.Document, list); + } + + list.Add(invocationOrCreation); + } + } + + return methodCallSites; + } + + private class MyCodeAction : SolutionChangeAction + { + public MyCodeAction(string title, Func> createChangedSolution) + : base(title, createChangedSolution) + { + } + } + + private enum IntroduceParameterCodeActionKind + { + Refactor, + Trampoline, + Overload + } + } +} diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.State.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.State.cs index 9a7393f8c96c9..92f3098b42486 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.State.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.State.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -63,22 +64,19 @@ private async Task TryInitializeAsync( TextSpan textSpan, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) - { - return false; - } + cancellationToken.ThrowIfCancellationRequested(); Expression = await document.Document.TryGetRelevantNodeAsync(textSpan, cancellationToken).ConfigureAwait(false); if (Expression == null || CodeRefactoringHelpers.IsNodeUnderselected(Expression, textSpan)) - { return false; - } + + // Don't introduce constant for another constant. Doesn't apply to sub-expression of constant. + if (IsInitializerOfConstant(document, Expression)) + return false; var expressionType = Document.SemanticModel.GetTypeInfo(Expression, cancellationToken).Type; if (expressionType is IErrorTypeSymbol) - { return false; - } var containingType = Expression.AncestorsAndSelf() .Select(n => Document.SemanticModel.GetDeclaredSymbol(n, cancellationToken)) @@ -88,19 +86,15 @@ private async Task TryInitializeAsync( containingType ??= Document.SemanticModel.Compilation.ScriptClass; if (containingType == null || containingType.TypeKind == TypeKind.Interface) - { return false; - } if (!CanIntroduceVariable(textSpan.IsEmpty, cancellationToken)) - { return false; - } IsConstant = IsExpressionConstant(Document, Expression, _service, cancellationToken); - // Note: the ordering of these clauses are important. They go, generally, from - // innermost to outermost order. + // Note: the ordering of these clauses are important. They go, generally, from + // innermost to outermost order. if (IsInQueryContext(cancellationToken)) { if (CanGenerateInto(cancellationToken)) @@ -139,7 +133,7 @@ private async Task TryInitializeAsync( return false; } - /* NOTE: All checks from this point forward are intentionally ordered to be AFTER the check for Block Context. */ + // NOTE: All checks from this point forward are intentionally ordered to be AFTER the check for Block Context. // If we are inside a block within an Expression bodied member we should generate inside the block, // instead of rewriting a concise expression bodied member to its equivalent that has a body with a block. @@ -186,6 +180,29 @@ private async Task TryInitializeAsync( return false; + static bool IsInitializerOfConstant(SemanticDocument document, TExpressionSyntax expression) + { + var syntaxFacts = document.Document.GetRequiredLanguageService(); + + var current = expression; + while (syntaxFacts.IsParenthesizedExpression(current.Parent)) + current = (TExpressionSyntax)current.Parent; + + if (!syntaxFacts.IsEqualsValueClause(current.Parent)) + return false; + + var equalsValue = current.Parent; + if (!syntaxFacts.IsVariableDeclarator(equalsValue.Parent)) + return false; + + var declaration = equalsValue.AncestorsAndSelf().FirstOrDefault(n => syntaxFacts.IsLocalDeclarationStatement(n) || syntaxFacts.IsFieldDeclaration(n)); + if (declaration == null) + return false; + + var generator = SyntaxGenerator.GetGenerator(document.Document); + return generator.GetModifiers(declaration).IsConst; + } + static bool IsExpressionConstant(SemanticDocument document, TExpressionSyntax expression, TService service, CancellationToken cancellationToken) { if (document.SemanticModel.GetConstantValue(expression, cancellationToken) is { HasValue: true, Value: var value }) @@ -231,7 +248,7 @@ private bool CanIntroduceVariable( return false; } - if (Expression is TTypeSyntax && !(Expression is TNameSyntax)) + if (Expression is TTypeSyntax and not TNameSyntax) { // name syntax can introduce variables, but not other type syntaxes return false; diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs index 6acb305468168..837c9bba3023d 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.cs @@ -472,7 +472,7 @@ protected static IEnumerable GetAnonymousMethodParameters( newSemanticDocument.Document, expandInsideNode: node => { - return !(node is TExpressionSyntax expression) + return node is not TExpressionSyntax expression || !newMatches.Contains(expression); }, cancellationToken: ct) diff --git a/src/Features/Core/Portable/InvertConditional/AbstractInvertConditionalCodeRefactoringProvider.cs b/src/Features/Core/Portable/InvertConditional/AbstractInvertConditionalCodeRefactoringProvider.cs index 6afd34408df23..2907b80212202 100644 --- a/src/Features/Core/Portable/InvertConditional/AbstractInvertConditionalCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InvertConditional/AbstractInvertConditionalCodeRefactoringProvider.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.Threading; using System.Threading.Tasks; @@ -12,6 +10,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.InvertConditional { @@ -37,7 +36,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte conditional.Span); } - private static async Task FindConditionalAsync( + private static async Task FindConditionalAsync( Document document, TextSpan span, CancellationToken cancellationToken) => await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); @@ -45,9 +44,10 @@ private static async Task InvertConditionalAsync( Document document, TextSpan span, CancellationToken cancellationToken) { var conditional = await FindConditionalAsync(document, span, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(conditional); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var editor = new SyntaxEditor(root, document.Project.Solution.Workspace); diff --git a/src/Features/Core/Portable/LanguageServiceIndexFormat/SymbolMoniker.cs b/src/Features/Core/Portable/LanguageServiceIndexFormat/SymbolMoniker.cs index 01a94a91815f3..7e873a2cbbac6 100644 --- a/src/Features/Core/Portable/LanguageServiceIndexFormat/SymbolMoniker.cs +++ b/src/Features/Core/Portable/LanguageServiceIndexFormat/SymbolMoniker.cs @@ -25,10 +25,10 @@ public SymbolMoniker(string scheme, string identifier) // Skip all local things that cannot escape outside of a single file: downstream consumers simply treat this as meaning a references/definition result // doesn't need to be stitched together across files or multiple projects or repositories. - if (symbol.Kind == SymbolKind.Local || - symbol.Kind == SymbolKind.RangeVariable || - symbol.Kind == SymbolKind.Label || - symbol.Kind == SymbolKind.Alias) + if (symbol.Kind is SymbolKind.Local or + SymbolKind.RangeVariable or + SymbolKind.Label or + SymbolKind.Alias) { return null; } diff --git a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractAnonymousTypeDisplayService.cs b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractAnonymousTypeDisplayService.cs index 855260bce1d8d..b4fd5fcc44e70 100644 --- a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractAnonymousTypeDisplayService.cs +++ b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractAnonymousTypeDisplayService.cs @@ -80,7 +80,7 @@ private static Dictionary GenerateAnonymousTypeNames( private static string GenerateAnonymousTypeName(int current) { var c = (char)('a' + current); - if (c >= 'a' && c <= 'z') + if (c is >= 'a' and <= 'z') { return "'" + c.ToString(); } diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index 191cdcc46d648..c06f04b943495 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -109,6 +109,7 @@ protected AbstractSymbolDescriptionBuilder( protected abstract void AddAwaitablePrefix(); protected abstract void AddAwaitableExtensionPrefix(); protected abstract void AddDeprecatedPrefix(); + protected abstract void AddEnumUnderlyingTypeSeparator(); protected abstract Task> GetInitializerSourcePartsAsync(ISymbol symbol); protected abstract ImmutableArray ToMinimalDisplayParts(ISymbol symbol, SemanticModel semanticModel, int position, SymbolDisplayFormat format); protected abstract string GetNavigationHint(ISymbol symbol); @@ -436,7 +437,18 @@ private IDictionary> BuildDe // Merge the two maps into one final result. var result = new Dictionary>(_documentationMap); foreach (var (group, parts) in _groupMap) - result[group] = parts.ToTaggedText(_getNavigationHint); + { + var taggedText = parts.ToTaggedText(_getNavigationHint); + if (group == SymbolDescriptionGroups.MainDescription) + { + // Mark the main description as a code block. + taggedText = taggedText + .Insert(0, new TaggedText(TextTags.CodeBlockStart, string.Empty)) + .Add(new TaggedText(TextTags.CodeBlockEnd, string.Empty)); + } + + result[group] = taggedText; + } return result; } @@ -465,6 +477,13 @@ private void AddDescriptionForNamedType(INamedTypeSymbol symbol) AddTypeParameterMapPart(allTypeParameters, allTypeArguments); } + + if (symbol.IsEnumType() && symbol.EnumUnderlyingType.SpecialType != SpecialType.System_Int32) + { + AddEnumUnderlyingTypeSeparator(); + var underlyingTypeDisplayParts = symbol.EnumUnderlyingType.ToDisplayParts(s_descriptionStyle.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes)); + AddToGroup(SymbolDescriptionGroups.MainDescription, underlyingTypeDisplayParts); + } } private void AddSymbolDescription(INamedTypeSymbol symbol) diff --git a/src/Features/Core/Portable/MakeMemberStatic/AbstractMakeMemberStaticCodeFixProvider.cs b/src/Features/Core/Portable/MakeMemberStatic/AbstractMakeMemberStaticCodeFixProvider.cs index 3f8a9a1d7c65b..e2dacdac3da69 100644 --- a/src/Features/Core/Portable/MakeMemberStatic/AbstractMakeMemberStaticCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMemberStatic/AbstractMakeMemberStaticCodeFixProvider.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -19,6 +18,8 @@ internal abstract class AbstractMakeMemberStaticCodeFixProvider : SyntaxEditorBa { internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.Compile; + protected abstract bool IsValidMemberNode([NotNullWhen(true)] SyntaxNode? node); + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { if (context.Diagnostics.Length == 1 && @@ -49,8 +50,6 @@ protected sealed override Task FixAllAsync(Document document, ImmutableArray> createChangedDocument) diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs index fdf065ea90e57..8439fc7c3bd27 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs @@ -28,14 +28,14 @@ public WrappedNamedTypeSymbol(INamedTypeSymbol symbol, bool canImplementImplicit var allMembers = _symbol.GetMembers(); var filteredMembers = from m in allMembers where !m.HasUnsupportedMetadata - where m.DeclaredAccessibility == Accessibility.Public || - m.DeclaredAccessibility == Accessibility.Protected || - m.DeclaredAccessibility == Accessibility.ProtectedOrInternal - where m.Kind == SymbolKind.Event || - m.Kind == SymbolKind.Field || - m.Kind == SymbolKind.Method || - m.Kind == SymbolKind.NamedType || - m.Kind == SymbolKind.Property + where m.DeclaredAccessibility is Accessibility.Public or + Accessibility.Protected or + Accessibility.ProtectedOrInternal + where m.Kind is SymbolKind.Event or + SymbolKind.Field or + SymbolKind.Method or + SymbolKind.NamedType or + SymbolKind.Property select WrapMember(m, canImplementImplicitly, docCommentFormattingService); _members = ImmutableArray.CreateRange(filteredMembers); diff --git a/src/Features/Core/Portable/MetadataAsSource/SymbolMappingServiceFactory.cs b/src/Features/Core/Portable/MetadataAsSource/SymbolMappingServiceFactory.cs index fd41ee3be919b..e4c5d32488f63 100644 --- a/src/Features/Core/Portable/MetadataAsSource/SymbolMappingServiceFactory.cs +++ b/src/Features/Core/Portable/MetadataAsSource/SymbolMappingServiceFactory.cs @@ -29,7 +29,7 @@ private sealed class SymbolMappingService : ISymbolMappingService { public Task MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken) { - if (!(document.Project.Solution.Workspace is MetadataAsSourceWorkspace workspace)) + if (document.Project.Solution.Workspace is not MetadataAsSourceWorkspace workspace) { throw new ArgumentException(FeaturesResources.Document_must_be_contained_in_the_workspace_that_created_this_service, nameof(document)); } diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 951e1852c0e89..3f12ca28d393e 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -86,6 +86,9 @@ + + + diff --git a/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs b/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs index 01b68a2eb8b8f..4d7b9aa1b6c88 100644 --- a/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.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.Composition; using System.Threading; @@ -31,14 +29,14 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - var syntaxFacts = document.GetLanguageService(); + var syntaxFacts = document.GetRequiredLanguageService(); var variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(declaration); if (variables.Count != 1) { return; } - var service = document.GetLanguageService(); + var service = document.GetRequiredLanguageService(); if (!await service.CanMoveDeclarationNearReferenceAsync(document, declaration, cancellationToken).ConfigureAwait(false)) { return; @@ -52,7 +50,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte private static async Task MoveDeclarationNearReferenceAsync( Document document, SyntaxNode statement, CancellationToken cancellationToken) { - var service = document.GetLanguageService(); + var service = document.GetRequiredLanguageService(); return await service.MoveDeclarationNearReferenceAsync(document, statement, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/NameTupleElement/AbstractNameTupleElementCodeRefactoringProvider.cs b/src/Features/Core/Portable/NameTupleElement/AbstractNameTupleElementCodeRefactoringProvider.cs index aa6fed6cd19d8..5d02c5c973f23 100644 --- a/src/Features/Core/Portable/NameTupleElement/AbstractNameTupleElementCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/NameTupleElement/AbstractNameTupleElementCodeRefactoringProvider.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.Linq; using System.Threading; @@ -47,7 +45,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return default; } - var syntaxFacts = document.GetLanguageService(); + var syntaxFacts = document.GetRequiredLanguageService(); var potentialArguments = await document.GetRelevantNodesAsync(span, cancellationToken).ConfigureAwait(false); var argument = potentialArguments.FirstOrDefault(n => n?.Parent is TTupleExpressionSyntax); if (argument == null || !syntaxFacts.IsSimpleArgument(argument)) @@ -55,10 +53,10 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return default; } - var tuple = (TTupleExpressionSyntax)argument.Parent; + var tuple = (TTupleExpressionSyntax)argument.GetRequiredParent(); - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (!(semanticModel.GetTypeInfo(tuple, cancellationToken).ConvertedType is INamedTypeSymbol tupleType)) + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (semanticModel.GetTypeInfo(tuple, cancellationToken).ConvertedType is not INamedTypeSymbol tupleType) { return default; } @@ -77,7 +75,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return default; } - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); return (root, argument, element.Name); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index be4b472ed866d..168fc796e8acd 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PersistentStorage; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -179,8 +180,6 @@ private static async Task ProcessIndexAsync( using var nameMatcher = PatternMatcher.CreatePatternMatcher(patternName, includeMatchedSpans: true, allowFuzzyMatching: true); using var _1 = containerMatcher; - using var _2 = ArrayBuilder.GetInstance(out var nameMatches); - using var _3 = ArrayBuilder.GetInstance(out var containerMatches); foreach (var declaredSymbolInfo in index.DeclaredSymbolInfos) { @@ -189,7 +188,6 @@ await AddResultIfMatchAsync( declaredSymbolInfo, nameMatcher, containerMatcher, kinds, - nameMatches, containerMatches, onResultFound, cancellationToken).ConfigureAwait(false); } } @@ -199,30 +197,40 @@ private static async Task AddResultIfMatchAsync( DeclaredSymbolInfo declaredSymbolInfo, PatternMatcher nameMatcher, PatternMatcher? containerMatcher, DeclaredSymbolInfoKindSet kinds, - ArrayBuilder nameMatches, ArrayBuilder containerMatches, Func onResultFound, CancellationToken cancellationToken) { - nameMatches.Clear(); - containerMatches.Clear(); + using var nameMatches = TemporaryArray.Empty; + using var containerMatches = TemporaryArray.Empty; cancellationToken.ThrowIfCancellationRequested(); if (kinds.Contains(declaredSymbolInfo.Kind) && - nameMatcher.AddMatches(declaredSymbolInfo.Name, nameMatches) && - containerMatcher?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, containerMatches) != false) + nameMatcher.AddMatches(declaredSymbolInfo.Name, ref nameMatches.AsRef()) && + containerMatcher?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, ref containerMatches.AsRef()) != false) { - var result = await ConvertResultAsync( - documentId, document, declaredSymbolInfo, nameMatches, containerMatches, cancellationToken).ConfigureAwait(false); + // See if we have a match in a linked file. If so, see if we have the same match in + // other projects that this file is linked in. If so, include the full set of projects + // the match is in so we can display that well in the UI. + // + // We can only do this in the case where the solution is loaded and thus we can examine + // the relationship between this document and the other documents linked to it. In the + // case where the solution isn't fully loaded and we're just reading in cached data, we + // don't know what other files we're linked to and can't merge results in this fashion. + var additionalMatchingProjects = await GetAdditionalProjectsWithMatchAsync( + document, declaredSymbolInfo, cancellationToken).ConfigureAwait(false); + + var result = ConvertResult( + documentId, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); await onResultFound(result).ConfigureAwait(false); } } - private static async Task ConvertResultAsync( + private static RoslynNavigateToItem ConvertResult( DocumentId documentId, Document? document, DeclaredSymbolInfo declaredSymbolInfo, - ArrayBuilder nameMatches, - ArrayBuilder containerMatches, - CancellationToken cancellationToken) + in TemporaryArray nameMatches, + in TemporaryArray containerMatches, + ImmutableArray additionalMatchingProjects) { var matchKind = GetNavigateToMatchKind(nameMatches); @@ -231,22 +239,10 @@ private static async Task ConvertResultAsync( var isCaseSensitive = nameMatches.All(m => m.IsCaseSensitive) && containerMatches.All(m => m.IsCaseSensitive); var kind = GetItemKind(declaredSymbolInfo); - using var _ = ArrayBuilder.GetInstance(out var matchedSpans); + using var matchedSpans = TemporaryArray.Empty; foreach (var match in nameMatches) matchedSpans.AddRange(match.MatchedSpans); - // See if we have a match in a linked file. If so, see if we have the same match in - // other projects that this file is linked in. If so, include the full set of projects - // the match is in so we can display that well in the UI. - // - // We can only do this in the case where the solution is loaded and thus we can examine - // the relationship between this document and the other documents linked to it. In the - // case where the solution isn't fully loaded and we're just reading in cached data, we - // don't know what other files we're linked to and can't merge results in this fashion. - var additionalMatchingProjects = document == null - ? ImmutableArray.Empty - : await GetAdditionalProjectsWithMatchAsync(document, declaredSymbolInfo, cancellationToken).ConfigureAwait(false); - // If we were not given a Document instance, then we're finding matches in cached data // and thus could be 'stale'. return new RoslynNavigateToItem( @@ -257,12 +253,15 @@ private static async Task ConvertResultAsync( kind, matchKind, isCaseSensitive, - matchedSpans.ToImmutable()); + matchedSpans.ToImmutableAndClear()); } - private static async Task> GetAdditionalProjectsWithMatchAsync( - Document document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) + private static async ValueTask> GetAdditionalProjectsWithMatchAsync( + Document? document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) { + if (document == null) + return ImmutableArray.Empty; + using var _ = ArrayBuilder.GetInstance(out var result); var solution = document.Project.Solution; @@ -287,9 +286,10 @@ private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) switch (declaredSymbolInfo.Kind) { case DeclaredSymbolInfoKind.Class: - return NavigateToItemKind.Class; case DeclaredSymbolInfoKind.Record: - return NavigateToItemKind.Record; + return NavigateToItemKind.Class; + case DeclaredSymbolInfoKind.RecordStruct: + return NavigateToItemKind.Structure; case DeclaredSymbolInfoKind.Constant: return NavigateToItemKind.Constant; case DeclaredSymbolInfoKind.Delegate: @@ -320,7 +320,7 @@ private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) } } - private static NavigateToMatchKind GetNavigateToMatchKind(ArrayBuilder nameMatches) + private static NavigateToMatchKind GetNavigateToMatchKind(in TemporaryArray nameMatches) { // work backwards through the match kinds. That way our result is as bad as our worst match part. For // example, say the user searches for `Console.Write` and we find `Console.Write` (exact, exact), and @@ -356,8 +356,6 @@ public DeclaredSymbolInfoKindSet(IEnumerable navigateToItemKinds) { case NavigateToItemKind.Class: lookupTable[(int)DeclaredSymbolInfoKind.Class] = true; - break; - case NavigateToItemKind.Record: lookupTable[(int)DeclaredSymbolInfoKind.Record] = true; break; case NavigateToItemKind.Constant: @@ -405,6 +403,7 @@ public DeclaredSymbolInfoKindSet(IEnumerable navigateToItemKinds) case NavigateToItemKind.Structure: lookupTable[(int)DeclaredSymbolInfoKind.Struct] = true; + lookupTable[(int)DeclaredSymbolInfoKind.RecordStruct] = true; break; default: diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 139d21fe1d9bf..7f77e87431778 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -16,7 +16,6 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea { public IImmutableSet KindsProvided { get; } = ImmutableHashSet.Create( NavigateToItemKind.Class, - NavigateToItemKind.Record, NavigateToItemKind.Constant, NavigateToItemKind.Delegate, NavigateToItemKind.Enum, diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs b/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs index d2e17e5e14446..8790ecb998ca8 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs @@ -11,7 +11,6 @@ internal static class NavigateToItemKind public const string Line = nameof(Line); public const string File = nameof(File); public const string Class = nameof(Class); - public const string Record = nameof(Record); public const string Structure = nameof(Structure); public const string Interface = nameof(Interface); public const string Delegate = nameof(Delegate); diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.NoOpDocumentTrackingService.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.NoOpDocumentTrackingService.cs deleted file mode 100644 index b502fa41e384d..0000000000000 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.NoOpDocumentTrackingService.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.NavigateTo -{ - internal partial class NavigateToSearcher - { - private class NoOpDocumentTrackingService : IDocumentTrackingService - { - public static readonly IDocumentTrackingService Instance = new NoOpDocumentTrackingService(); - - private NoOpDocumentTrackingService() - { - } - -#pragma warning disable CS0067 - public event EventHandler? ActiveDocumentChanged; - public event EventHandler? NonRoslynBufferTextChanged; -#pragma warning restore CS0067 - - public ImmutableArray GetVisibleDocuments() - => ImmutableArray.Empty; - - public DocumentId? TryGetActiveDocument() - => null; - } - } -} diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index 794a44e13f216..8e1abce982f88 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo { - internal partial class NavigateToSearcher + internal class NavigateToSearcher { private readonly INavigateToSearcherHost _host; private readonly Solution _solution; @@ -49,20 +49,13 @@ private NavigateToSearcher( _searchPattern = searchPattern; _searchCurrentDocument = searchCurrentDocument; _kinds = kinds; - _progress = new StreamingProgressTracker((current, maximum) => + _progress = new StreamingProgressTracker((current, maximum, ct) => { callback.ReportProgress(current, maximum); return new ValueTask(); }); - if (_searchCurrentDocument) - { - var documentService = _solution.Workspace.Services.GetRequiredService(); - var activeId = documentService.TryGetActiveDocument(); - _currentDocument = activeId != null ? _solution.GetDocument(activeId) : null; - } - - var docTrackingService = _solution.Workspace.Services.GetService() ?? NoOpDocumentTrackingService.Instance; + var docTrackingService = _solution.Workspace.Services.GetRequiredService(); // If the workspace is tracking documents, use that to prioritize our search // order. That way we provide results for the documents the user is working @@ -70,6 +63,11 @@ private NavigateToSearcher( _activeDocument = docTrackingService.GetActiveDocument(_solution); _visibleDocuments = docTrackingService.GetVisibleDocuments(_solution) .WhereAsArray(d => d != _activeDocument); + + if (_searchCurrentDocument) + { + _currentDocument = _activeDocument; + } } public static NavigateToSearcher Create( @@ -138,7 +136,7 @@ private async Task SearchAllProjectsAsync(CancellationToken cancellationTo // We didn't have any items reported *and* we weren't fully loaded. If it turns out that some of our // projects were using cached data then we can try searching them again, but this tell them to use the // latest data. The ensures the user at least gets some result instead of nothing. - var projectsUsingCache = projectResults.Where(t => t.location == NavigateToSearchLocation.Cache).SelectAsArray(t => t.project); + var projectsUsingCache = projectResults.SelectAsArray(t => t.location == NavigateToSearchLocation.Cache, t => t.project); await ProcessProjectsAsync(ImmutableArray.Create(projectsUsingCache), isFullyLoaded: true, cancellationToken).ConfigureAwait(false); // We attempted a full oop sync and an uncached search. However, we still may need to tell the user that @@ -228,7 +226,7 @@ private ImmutableArray GetPriorityDocuments(Project project) private async Task<(int itemsReported, ImmutableArray<(Project project, NavigateToSearchLocation location)>)> ProcessProjectsAsync( ImmutableArray> orderedProjects, bool isFullyLoaded, CancellationToken cancellationToken) { - await _progress.AddItemsAsync(orderedProjects.Sum(p => p.Length)).ConfigureAwait(false); + await _progress.AddItemsAsync(orderedProjects.Sum(p => p.Length), cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder<(Project project, NavigateToSearchLocation location)>.GetInstance(out var result); @@ -249,7 +247,7 @@ private ImmutableArray GetPriorityDocuments(Project project) } finally { - await _progress.ItemCompletedAsync().ConfigureAwait(false); + await _progress.ItemCompletedAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 979ee6a3c5c4d..cab6cecd66a4e 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -172,6 +172,7 @@ private bool IsNamedType() case DeclaredSymbolInfoKind.Interface: case DeclaredSymbolInfoKind.Module: case DeclaredSymbolInfoKind.Struct: + case DeclaredSymbolInfoKind.RecordStruct: return true; default: return false; @@ -236,6 +237,7 @@ private static Glyph GetPublicGlyph(DeclaredSymbolInfoKind kind) DeclaredSymbolInfoKind.Module => Glyph.ModulePublic, DeclaredSymbolInfoKind.Property => Glyph.PropertyPublic, DeclaredSymbolInfoKind.Struct => Glyph.StructurePublic, + DeclaredSymbolInfoKind.RecordStruct => Glyph.StructurePublic, _ => Glyph.ClassPublic, }; diff --git a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs index 5e200c64f7bd0..e83f04659ab10 100644 --- a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs @@ -2,25 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Navigation { internal class DefaultSymbolNavigationService : ISymbolNavigationService { - public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options = null, CancellationToken cancellationToken = default) + public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet? options = null, CancellationToken cancellationToken = default) => false; - public bool TrySymbolNavigationNotify(ISymbol symbol, Project project, CancellationToken cancellationToken) - => false; + public Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) + => SpecializedTasks.False; public bool WouldNavigateToSymbol( DefinitionItem definitionItem, Solution solution, CancellationToken cancellationToken, - out string filePath, out int lineNumber, out int charOffset) + [NotNullWhen(true)] out string? filePath, out int lineNumber, out int charOffset) { filePath = null; lineNumber = 0; diff --git a/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs b/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs index 17f7b9d427faa..ad30f8cf4da83 100644 --- a/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; @@ -22,15 +22,15 @@ internal interface ISymbolNavigationService : IWorkspaceService /// A set of options. If these options are not supplied the /// current set of options from the project's workspace will be used. /// The token to check for cancellation - bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options = null, CancellationToken cancellationToken = default); + bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet? options = null, CancellationToken cancellationToken = default); /// True if the navigation was handled, indicating that the caller should not /// perform the navigation. - bool TrySymbolNavigationNotify(ISymbol symbol, Project project, CancellationToken cancellationToken); + Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken); /// True if the navigation would be handled. bool WouldNavigateToSymbol( DefinitionItem definitionItem, Solution solution, CancellationToken cancellationToken, - out string filePath, out int lineNumber, out int charOffset); + [NotNullWhen(true)] out string? filePath, out int lineNumber, out int charOffset); } } diff --git a/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs b/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs index 0bccb858821fd..6ab315a7b763f 100644 --- a/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs +++ b/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs @@ -2,10 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Text; +using static Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem; namespace Microsoft.CodeAnalysis.NavigationBar { @@ -33,5 +38,58 @@ public async Task> GetItemsAsync(Documen var items = await GetItemsInCurrentProcessAsync(document, supportsCodeGeneration, cancellationToken).ConfigureAwait(false); return items; } + + protected static SymbolItemLocation? GetSymbolLocation( + Solution solution, ISymbol symbol, SyntaxTree tree, Func computeFullSpan) + { + return GetSymbolLocation(solution, symbol, tree, computeFullSpan, symbol.DeclaringSyntaxReferences); + } + + private static SymbolItemLocation? GetSymbolLocation( + Solution solution, ISymbol symbol, SyntaxTree tree, + Func computeFullSpan, + ImmutableArray allReferences) + { + if (allReferences.Length == 0) + return null; + + // See if there are any references in the starting file. We always prefer those for any symbol we find. + var referencesInCurrentFile = allReferences.WhereAsArray(r => r.SyntaxTree == tree); + if (referencesInCurrentFile.Length > 0) + { + // the symbol had one or more declarations in this file. We want to include all those spans in what we + // return so that if the use enters any of its spans we highlight it in the list. An example of having + // multiple locations in the same file would be a a partial type with multiple parts in the same file. + + // If we're not able to find a narrower navigation location in this file though then just navigate to + // the first reference itself. + var navigationLocationSpan = symbol.Locations.FirstOrDefault(loc => loc.SourceTree == tree)?.SourceSpan ?? + referencesInCurrentFile.First().Span; + + var spans = referencesInCurrentFile.SelectAsArray(r => computeFullSpan(r)); + return new SymbolItemLocation((spans, navigationLocationSpan), otherDocumentInfo: null); + } + else + { + // the symbol was defined in another file altogether. We don't care about it's full span + // (since that is only needed for intersecting with the caret. Instead, we just need a + // reasonable location to navigate them to. First try to find a narrow location to navigate to. + // And, if we can't, just go to the first reference we can find. + var navigationLocation = symbol.Locations.FirstOrDefault(loc => loc.SourceTree != null && loc.SourceTree != tree) ?? + Location.Create(allReferences.First().SyntaxTree, allReferences.First().Span); + + var documentId = solution.GetDocumentId(navigationLocation.SourceTree); + if (documentId == null) + return null; + + return new SymbolItemLocation(inDocumentInfo: null, (documentId, navigationLocation.SourceSpan)); + } + } + + protected static SymbolItemLocation? GetSymbolLocation( + Solution solution, ISymbol symbol, SyntaxTree tree, ISymbolDeclarationService symbolDeclarationService) + { + return GetSymbolLocation(solution, symbol, tree, r => r.GetSyntax().FullSpan, symbolDeclarationService.GetDeclarations(symbol)); + } } } diff --git a/src/Features/Core/Portable/NavigationBar/IRemoteNavigationBarItemService.cs b/src/Features/Core/Portable/NavigationBar/IRemoteNavigationBarItemService.cs index c94010b12fd55..4507ca3d8ec0d 100644 --- a/src/Features/Core/Portable/NavigationBar/IRemoteNavigationBarItemService.cs +++ b/src/Features/Core/Portable/NavigationBar/IRemoteNavigationBarItemService.cs @@ -7,8 +7,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem; namespace Microsoft.CodeAnalysis.NavigationBar { @@ -36,16 +36,15 @@ internal sealed class SerializableNavigationBarItem public readonly int Indent; [DataMember(Order = 6)] public readonly ImmutableArray ChildItems; - [DataMember(Order = 7)] - public readonly ImmutableArray Spans; // Set when kind == RoslynNavigationBarItemKind.Symbol + [DataMember(Order = 7)] + public readonly string? Name; [DataMember(Order = 8)] - public readonly SymbolKey? NavigationSymbolId; - + public readonly bool IsObsolete; [DataMember(Order = 9)] - public readonly int NavigationSymbolIndex; + public readonly SymbolItemLocation? Location; // Set for all GenerateCode kinds. @@ -72,9 +71,9 @@ private SerializableNavigationBarItem( bool grayed, int indent, ImmutableArray childItems, - ImmutableArray spans, - SymbolKey? navigationSymbolId, - int navigationSymbolIndex, + string? name, + bool isObsolete, + SymbolItemLocation? location, SymbolKey? destinationTypeSymbolKey, string? containerName, SymbolKey? eventSymbolKey, @@ -83,13 +82,13 @@ private SerializableNavigationBarItem( Kind = kind; Text = text; Glyph = glyph; - Spans = spans.NullToEmpty(); ChildItems = childItems.NullToEmpty(); + Name = name; + IsObsolete = isObsolete; + Location = location; Indent = indent; Bolded = bolded; Grayed = grayed; - NavigationSymbolId = navigationSymbolId; - NavigationSymbolIndex = navigationSymbolIndex; DestinationTypeSymbolKey = destinationTypeSymbolKey; ContainerName = containerName; EventSymbolKey = eventSymbolKey; @@ -99,34 +98,34 @@ private SerializableNavigationBarItem( public RoslynNavigationBarItem Rehydrate() => this.Kind switch { - RoslynNavigationBarItemKind.Symbol => new RoslynNavigationBarItem.SymbolItem(Text, Glyph, Spans, NavigationSymbolId!.Value, NavigationSymbolIndex, ChildItems.SelectAsArray(i => i.Rehydrate()), Indent, Bolded, Grayed), - RoslynNavigationBarItemKind.GenerateDefaultConstructor => new RoslynNavigationBarItem.GenerateDefaultConstructor(Text, DestinationTypeSymbolKey!.Value), - RoslynNavigationBarItemKind.GenerateEventHandler => new RoslynNavigationBarItem.GenerateEventHandler(Text, Glyph, ContainerName!, EventSymbolKey!.Value, DestinationTypeSymbolKey!.Value), - RoslynNavigationBarItemKind.GenerateFinalizer => new RoslynNavigationBarItem.GenerateFinalizer(Text, DestinationTypeSymbolKey!.Value), - RoslynNavigationBarItemKind.GenerateMethod => new RoslynNavigationBarItem.GenerateMethod(Text, Glyph, DestinationTypeSymbolKey!.Value, MethodToReplicateSymbolKey!.Value), - RoslynNavigationBarItemKind.Actionless => new RoslynNavigationBarItem.ActionlessItem(Text, Glyph, Spans, ChildItems.SelectAsArray(v => v.Rehydrate()), Indent, Bolded, Grayed), + RoslynNavigationBarItemKind.Symbol => new SymbolItem(Name!, Text, Glyph, IsObsolete, Location!.Value, ChildItems.SelectAsArray(i => i.Rehydrate()), Indent, Bolded), + RoslynNavigationBarItemKind.GenerateDefaultConstructor => new GenerateDefaultConstructor(Text, DestinationTypeSymbolKey!.Value), + RoslynNavigationBarItemKind.GenerateEventHandler => new GenerateEventHandler(Text, Glyph, ContainerName!, EventSymbolKey!.Value, DestinationTypeSymbolKey!.Value), + RoslynNavigationBarItemKind.GenerateFinalizer => new GenerateFinalizer(Text, DestinationTypeSymbolKey!.Value), + RoslynNavigationBarItemKind.GenerateMethod => new GenerateMethod(Text, Glyph, DestinationTypeSymbolKey!.Value, MethodToReplicateSymbolKey!.Value), + RoslynNavigationBarItemKind.Actionless => new ActionlessItem(Text, Glyph, ChildItems.SelectAsArray(v => v.Rehydrate()), Indent, Bolded, Grayed), _ => throw ExceptionUtilities.UnexpectedValue(this.Kind), }; public static ImmutableArray Dehydrate(ImmutableArray values) => values.SelectAsArray(v => v.Dehydrate()); - public static SerializableNavigationBarItem ActionlessItem(string text, Glyph glyph, ImmutableArray spans, ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) - => new(RoslynNavigationBarItemKind.Actionless, text, glyph, bolded, grayed, indent, childItems, spans, null, 0, null, null, null, null); + public static SerializableNavigationBarItem ActionlessItem(string text, Glyph glyph, ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) + => new(RoslynNavigationBarItemKind.Actionless, text, glyph, bolded, grayed, indent, childItems, null, false, null, null, null, null, null); - public static SerializableNavigationBarItem SymbolItem(string text, Glyph glyph, ImmutableArray spans, SymbolKey navigationSymbolId, int navigationSymbolIndex, ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) - => new(RoslynNavigationBarItemKind.Symbol, text, glyph, bolded, grayed, indent, childItems, spans, navigationSymbolId, navigationSymbolIndex, null, null, null, null); + public static SerializableNavigationBarItem SymbolItem(string text, Glyph glyph, string name, bool isObsolete, SymbolItemLocation location, ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) + => new(RoslynNavigationBarItemKind.Symbol, text, glyph, bolded, grayed, indent, childItems, name, isObsolete, location, null, null, null, null); public static SerializableNavigationBarItem GenerateFinalizer(string text, SymbolKey destinationTypeSymbolKey) - => new(RoslynNavigationBarItemKind.GenerateFinalizer, text, Glyph.MethodProtected, bolded: false, grayed: false, indent: 0, default, default, null, 0, destinationTypeSymbolKey, null, null, null); + => new(RoslynNavigationBarItemKind.GenerateFinalizer, text, Glyph.MethodProtected, bolded: false, grayed: false, indent: 0, default, null, false, null, destinationTypeSymbolKey, null, null, null); public static SerializableNavigationBarItem GenerateEventHandler(string eventName, Glyph glyph, string containerName, SymbolKey eventSymbolKey, SymbolKey destinationTypeSymbolKey) - => new(RoslynNavigationBarItemKind.GenerateEventHandler, eventName, glyph, bolded: false, grayed: false, indent: 0, default, default, null, 0, destinationTypeSymbolKey, containerName, eventSymbolKey, null); + => new(RoslynNavigationBarItemKind.GenerateEventHandler, eventName, glyph, bolded: false, grayed: false, indent: 0, default, null, false, null, destinationTypeSymbolKey, containerName, eventSymbolKey, null); public static SerializableNavigationBarItem GenerateMethod(string text, Glyph glyph, SymbolKey destinationTypeSymbolId, SymbolKey methodToReplicateSymbolId) - => new(RoslynNavigationBarItemKind.GenerateMethod, text, glyph, bolded: false, grayed: false, indent: 0, default, default, null, 0, destinationTypeSymbolId, null, null, methodToReplicateSymbolId); + => new(RoslynNavigationBarItemKind.GenerateMethod, text, glyph, bolded: false, grayed: false, indent: 0, default, null, false, null, destinationTypeSymbolId, null, null, methodToReplicateSymbolId); public static SerializableNavigationBarItem GenerateDefaultConstructor(string text, SymbolKey destinationTypeSymbolKey) - => new(RoslynNavigationBarItemKind.GenerateDefaultConstructor, text, Glyph.MethodPublic, bolded: false, grayed: false, indent: 0, default, default, null, 0, destinationTypeSymbolKey, null, null, null); + => new(RoslynNavigationBarItemKind.GenerateDefaultConstructor, text, Glyph.MethodPublic, bolded: false, grayed: false, indent: 0, default, null, false, null, destinationTypeSymbolKey, null, null, null); } } diff --git a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.AbstractGenerateCodeItem.cs b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.AbstractGenerateCodeItem.cs index 41c9cf7b942d8..395d86999a227 100644 --- a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.AbstractGenerateCodeItem.cs +++ b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.AbstractGenerateCodeItem.cs @@ -15,7 +15,7 @@ public abstract class AbstractGenerateCodeItem : RoslynNavigationBarItem public readonly SymbolKey DestinationTypeSymbolKey; protected AbstractGenerateCodeItem(RoslynNavigationBarItemKind kind, string text, Glyph glyph, SymbolKey destinationTypeSymbolKey) - : base(kind, text, glyph, bolded: false, grayed: false, indent: 0, childItems: default, ImmutableArray.Empty) + : base(kind, text, glyph, bolded: false, grayed: false, indent: 0, childItems: default) { DestinationTypeSymbolKey = destinationTypeSymbolKey; } diff --git a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.ActionlessItem.cs b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.ActionlessItem.cs index 14cf776711be0..b9d5e0ba07f3b 100644 --- a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.ActionlessItem.cs +++ b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.ActionlessItem.cs @@ -18,17 +18,16 @@ public class ActionlessItem : RoslynNavigationBarItem public ActionlessItem( string text, Glyph glyph, - ImmutableArray spans, ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) - : base(RoslynNavigationBarItemKind.Actionless, text, glyph, bolded, grayed, indent, childItems, spans) + : base(RoslynNavigationBarItemKind.Actionless, text, glyph, bolded, grayed, indent, childItems) { } protected internal override SerializableNavigationBarItem Dehydrate() - => SerializableNavigationBarItem.ActionlessItem(Text, Glyph, Spans, SerializableNavigationBarItem.Dehydrate(ChildItems), Indent, Bolded, Grayed); + => SerializableNavigationBarItem.ActionlessItem(Text, Glyph, SerializableNavigationBarItem.Dehydrate(ChildItems), Indent, Bolded, Grayed); } } } diff --git a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.SymbolItem.cs b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.SymbolItem.cs index f4930297176dc..58fbf0960aac0 100644 --- a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.SymbolItem.cs +++ b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.SymbolItem.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigationBar { @@ -11,27 +13,79 @@ internal abstract partial class RoslynNavigationBarItem { public sealed class SymbolItem : RoslynNavigationBarItem { - public readonly SymbolKey NavigationSymbolId; - public readonly int NavigationSymbolIndex; + public readonly string Name; + public readonly bool IsObsolete; + + public readonly SymbolItemLocation Location; public SymbolItem( + string name, string text, Glyph glyph, - ImmutableArray spans, - SymbolKey navigationSymbolId, - int navigationSymbolIndex, + bool isObsolete, + SymbolItemLocation location, ImmutableArray childItems = default, int indent = 0, - bool bolded = false, - bool grayed = false) - : base(RoslynNavigationBarItemKind.Symbol, text, glyph, bolded, grayed, indent, childItems, spans) + bool bolded = false) + : base( + RoslynNavigationBarItemKind.Symbol, + text, + glyph, + bolded, + grayed: location.OtherDocumentInfo != null, + indent, + childItems) { - this.NavigationSymbolId = navigationSymbolId; - this.NavigationSymbolIndex = navigationSymbolIndex; + + Name = name; + IsObsolete = isObsolete; + Location = location; } protected internal override SerializableNavigationBarItem Dehydrate() - => SerializableNavigationBarItem.SymbolItem(Text, Glyph, Spans, NavigationSymbolId, NavigationSymbolIndex, SerializableNavigationBarItem.Dehydrate(ChildItems), Indent, Bolded, Grayed); + => SerializableNavigationBarItem.SymbolItem(Text, Glyph, Name, IsObsolete, Location, SerializableNavigationBarItem.Dehydrate(ChildItems), Indent, Bolded, Grayed); + } + + [DataContract] + public readonly struct SymbolItemLocation + { + /// + /// The entity spans and navigation span in the originating document where this symbol was found. Any time + /// the caret is within any of the entity spans, the item should be appropriately 'selected' in whatever UI + /// is displaying these. The navigation span is the location in the starting document that should be + /// navigated to when this item is selected If this symbol's location is in another document then this will + /// be . + /// + /// Exactly one of and will be + /// non-null. + [DataMember(Order = 0)] + public readonly (ImmutableArray spans, TextSpan navigationSpan)? InDocumentInfo; + + /// + /// The document and navigation span this item should navigate to when the definition is not in the + /// originating document. This is used for partial symbols where a child symbol is declared in another file, + /// but should still be shown in the UI when in a part in a different file. + /// + /// Exactly one of and will be + /// non-null. + [DataMember(Order = 1)] + public readonly (DocumentId documentId, TextSpan navigationSpan)? OtherDocumentInfo; + + public SymbolItemLocation( + (ImmutableArray spans, TextSpan navigationSpan)? inDocumentInfo, + (DocumentId documentId, TextSpan navigationSpan)? otherDocumentInfo) + { + Contract.ThrowIfTrue(inDocumentInfo == null && otherDocumentInfo == null, "Both locations were null"); + Contract.ThrowIfTrue(inDocumentInfo != null && otherDocumentInfo != null, "Both locations were not null"); + + if (inDocumentInfo != null) + { + Contract.ThrowIfTrue(inDocumentInfo.Value.spans.IsEmpty, "If location is in document, it must have non-empty spans"); + } + + InDocumentInfo = inDocumentInfo; + OtherDocumentInfo = otherDocumentInfo; + } } } } diff --git a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.cs b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.cs index 11dd62758b594..5bb1f08a62d6a 100644 --- a/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.cs +++ b/src/Features/Core/Portable/NavigationBar/NavigationBarItems/RoslynNavigationBarItem.cs @@ -21,7 +21,6 @@ internal abstract partial class RoslynNavigationBarItem public readonly bool Grayed; public readonly int Indent; public readonly ImmutableArray ChildItems; - public readonly ImmutableArray Spans; protected RoslynNavigationBarItem( RoslynNavigationBarItemKind kind, @@ -30,13 +29,11 @@ protected RoslynNavigationBarItem( bool bolded, bool grayed, int indent, - ImmutableArray childItems, - ImmutableArray spans) + ImmutableArray childItems) { Kind = kind; Text = text; Glyph = glyph; - Spans = spans.NullToEmpty(); ChildItems = childItems.NullToEmpty(); Indent = indent; Bolded = bolded; diff --git a/src/Features/Core/Portable/NavigationBar/NavigationBarSymbolIdIndexProvider.cs b/src/Features/Core/Portable/NavigationBar/NavigationBarSymbolIdIndexProvider.cs deleted file mode 100644 index 1f95e04e311b8..0000000000000 --- a/src/Features/Core/Portable/NavigationBar/NavigationBarSymbolIdIndexProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.NavigationBar -{ - /// - /// A little helper to produce indexes when producing NavigationBarItems when we have multiple - /// symbols with the same symbol ID. - /// - internal class NavigationBarSymbolIdIndexProvider - { - private readonly Dictionary _nextIds; - - public NavigationBarSymbolIdIndexProvider(bool caseSensitive) - { - _nextIds = new Dictionary(caseSensitive - ? SymbolKey.GetComparer(ignoreCase: true, ignoreAssemblyKeys: false) - : SymbolKey.GetComparer(ignoreCase: false, ignoreAssemblyKeys: false)); - } - - public int GetIndexForSymbolId(SymbolKey id) - { - _nextIds.TryGetValue(id, out var nextId); - _nextIds[id] = nextId + 1; - return nextId; - } - } -} diff --git a/src/Features/Core/Portable/PickMembers/IPickMembersService.cs b/src/Features/Core/Portable/PickMembers/IPickMembersService.cs index 96e11a7436865..d8a0934c5c499 100644 --- a/src/Features/Core/Portable/PickMembers/IPickMembersService.cs +++ b/src/Features/Core/Portable/PickMembers/IPickMembersService.cs @@ -13,7 +13,8 @@ internal interface IPickMembersService : IWorkspaceService { PickMembersResult PickMembers( string title, ImmutableArray members, - ImmutableArray options = default); + ImmutableArray options = default, + bool selectAll = true); } internal class PickMembersOption diff --git a/src/Features/Core/Portable/PickMembers/PickMembersResult.cs b/src/Features/Core/Portable/PickMembers/PickMembersResult.cs index 2a69634b7f6e0..6ce9e5000e870 100644 --- a/src/Features/Core/Portable/PickMembers/PickMembersResult.cs +++ b/src/Features/Core/Portable/PickMembers/PickMembersResult.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.Collections.Immutable; namespace Microsoft.CodeAnalysis.PickMembers @@ -16,15 +14,22 @@ internal class PickMembersResult public readonly ImmutableArray Members; public readonly ImmutableArray Options; + /// + /// if 'Select All' was chosen. if 'Deselect All' was chosen. + /// + public readonly bool SelectedAll; + private PickMembersResult(bool isCanceled) => IsCanceled = isCanceled; public PickMembersResult( ImmutableArray members, - ImmutableArray options) + ImmutableArray options, + bool selectedAll) { Members = members; Options = options; + SelectedAll = selectedAll; } } } diff --git a/src/Features/Core/Portable/PreferFrameworkType/PreferFrameworkTypeDiagnosticAnalyzerBase.cs b/src/Features/Core/Portable/PreferFrameworkType/PreferFrameworkTypeDiagnosticAnalyzerBase.cs index 5ca4d6de8975a..815b0a7761060 100644 --- a/src/Features/Core/Portable/PreferFrameworkType/PreferFrameworkTypeDiagnosticAnalyzerBase.cs +++ b/src/Features/Core/Portable/PreferFrameworkType/PreferFrameworkTypeDiagnosticAnalyzerBase.cs @@ -73,7 +73,7 @@ protected void AnalyzeNode(SyntaxNodeAnalysisContext context) } // check we have a symbol so that the fixer can generate the right type syntax from it. - if (!(semanticModel.GetSymbolInfo(predefinedTypeNode, context.CancellationToken).Symbol is ITypeSymbol)) + if (semanticModel.GetSymbolInfo(predefinedTypeNode, context.CancellationToken).Symbol is not ITypeSymbol) { return; } diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index d62edd3868059..5542acfb736be 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ const Microsoft.CodeAnalysis.TextTags.Record = "Record" -> string +const Microsoft.CodeAnalysis.TextTags.RecordStruct = "RecordStruct" -> string Microsoft.CodeAnalysis.Completion.CompletionItem.IsComplexTextEdit.get -> bool Microsoft.CodeAnalysis.Completion.CompletionItem.WithIsComplexTextEdit(bool isComplexTextEdit) -> Microsoft.CodeAnalysis.Completion.CompletionItem static Microsoft.CodeAnalysis.Completion.CompletionChange.Create(Microsoft.CodeAnalysis.Text.TextChange textChange, System.Collections.Immutable.ImmutableArray textChanges, int? newPosition = null, bool includesCommitCharacter = false) -> Microsoft.CodeAnalysis.Completion.CompletionChange diff --git a/src/Features/Core/Portable/PullMemberUp/MemberAndDestinationValidator.cs b/src/Features/Core/Portable/PullMemberUp/MemberAndDestinationValidator.cs index cb955e100229f..2ff5115530857 100644 --- a/src/Features/Core/Portable/PullMemberUp/MemberAndDestinationValidator.cs +++ b/src/Features/Core/Portable/PullMemberUp/MemberAndDestinationValidator.cs @@ -15,7 +15,7 @@ internal static class MemberAndDestinationValidator public static bool IsDestinationValid(Solution solution, INamedTypeSymbol destination, CancellationToken cancellationToken) { // Make sure destination is class or interface since it could be ErrorTypeSymbol - if (destination.TypeKind != TypeKind.Interface && destination.TypeKind != TypeKind.Class) + if (destination.TypeKind is not TypeKind.Interface and not TypeKind.Class) { return false; } diff --git a/src/Features/Core/Portable/QuickInfo/CommonQuickInfoContext.cs b/src/Features/Core/Portable/QuickInfo/CommonQuickInfoContext.cs new file mode 100644 index 0000000000000..7d716e33f0916 --- /dev/null +++ b/src/Features/Core/Portable/QuickInfo/CommonQuickInfoContext.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace Microsoft.CodeAnalysis.QuickInfo +{ + internal readonly struct CommonQuickInfoContext + { + public readonly Workspace Workspace; + public readonly SemanticModel SemanticModel; + public readonly int Position; + public readonly CancellationToken CancellationToken; + + public CommonQuickInfoContext( + Workspace workspace, + SemanticModel semanticModel, + int position, + CancellationToken cancellationToken) + { + Workspace = workspace; + SemanticModel = semanticModel; + Position = position; + CancellationToken = cancellationToken; + } + } +} diff --git a/src/Features/Core/Portable/QuickInfo/CommonQuickInfoProvider.cs b/src/Features/Core/Portable/QuickInfo/CommonQuickInfoProvider.cs index 749f7a17b15da..5d2645b0139f1 100644 --- a/src/Features/Core/Portable/QuickInfo/CommonQuickInfoProvider.cs +++ b/src/Features/Core/Portable/QuickInfo/CommonQuickInfoProvider.cs @@ -2,52 +2,94 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Threading; +using System.Collections.Immutable; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.QuickInfo { internal abstract class CommonQuickInfoProvider : QuickInfoProvider { + protected abstract Task BuildQuickInfoAsync(QuickInfoContext context, SyntaxToken token); + protected abstract Task BuildQuickInfoAsync(CommonQuickInfoContext context, SyntaxToken token); + public override async Task GetQuickInfoAsync(QuickInfoContext context) { - var document = context.Document; - var position = context.Position; var cancellationToken = context.CancellationToken; + var tree = await context.Document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var tokens = await GetTokensAsync(tree, context.Position, context.CancellationToken).ConfigureAwait(false); - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var token = await tree.GetTouchingTokenAsync(position, cancellationToken, findInsideTrivia: true).ConfigureAwait(false); + foreach (var token in tokens) + { + var info = await GetQuickInfoAsync(context, token).ConfigureAwait(false); + if (info != null) + return info; + } - var info = await GetQuickInfoAsync(document, token, position, cancellationToken).ConfigureAwait(false); + return null; + } - if (info == null && ShouldCheckPreviousToken(token)) + public async Task GetQuickInfoAsync(CommonQuickInfoContext context) + { + var tokens = await GetTokensAsync(context.SemanticModel.SyntaxTree, context.Position, context.CancellationToken).ConfigureAwait(false); + + foreach (var token in tokens) { - var previousToken = token.GetPreviousToken(); - info = await GetQuickInfoAsync(document, previousToken, position, cancellationToken).ConfigureAwait(false); + var info = await GetQuickInfoAsync(context, token).ConfigureAwait(false); + if (info != null) + return info; } - return info; + return null; + } + + protected async Task> GetTokensAsync(SyntaxTree tree, int position, System.Threading.CancellationToken cancellationToken) + { + using var result = TemporaryArray.Empty; + var token = await tree.GetTouchingTokenAsync(position, cancellationToken, findInsideTrivia: true).ConfigureAwait(false); + if (token != default) + { + result.Add(token); + + if (ShouldCheckPreviousToken(token)) + { + token = token.GetPreviousToken(); + if (token != default && token.Span.IntersectsWith(position)) + result.Add(token); + } + } + + return result.ToImmutableAndClear(); } protected virtual bool ShouldCheckPreviousToken(SyntaxToken token) => true; private async Task GetQuickInfoAsync( - Document document, - SyntaxToken token, - int position, - CancellationToken cancellationToken) + QuickInfoContext context, + SyntaxToken token) { if (token != default && - token.Span.IntersectsWith(position)) + token.Span.IntersectsWith(context.Position)) { - return await BuildQuickInfoAsync(document, token, cancellationToken).ConfigureAwait(false); + return await BuildQuickInfoAsync(context, token).ConfigureAwait(false); } return null; } - protected abstract Task BuildQuickInfoAsync(Document document, SyntaxToken token, CancellationToken cancellationToken); + private async Task GetQuickInfoAsync( + CommonQuickInfoContext context, + SyntaxToken token) + { + if (token != default && + token.Span.IntersectsWith(context.Position)) + { + return await BuildQuickInfoAsync(context, token).ConfigureAwait(false); + } + + return null; + } } } diff --git a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs index 7b0a6cd49bcc9..61b21dfa2d5e9 100644 --- a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs +++ b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs @@ -8,12 +8,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Tags; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.QuickInfo @@ -21,43 +19,50 @@ namespace Microsoft.CodeAnalysis.QuickInfo internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInfoProvider { protected override async Task BuildQuickInfoAsync( - Document document, - SyntaxToken token, - CancellationToken cancellationToken) + QuickInfoContext context, SyntaxToken token) { - var (model, tokenInformation, supportedPlatforms) = await ComputeQuickInfoDataAsync(document, token, cancellationToken).ConfigureAwait(false); + var (tokenInformation, supportedPlatforms) = await ComputeQuickInfoDataAsync(context, token).ConfigureAwait(false); + if (tokenInformation.Symbols.IsDefaultOrEmpty) + return null; + var cancellationToken = context.CancellationToken; + var semanticModel = await context.Document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return await CreateContentAsync( + context.Document.Project.Solution.Workspace, semanticModel, token, tokenInformation, supportedPlatforms, cancellationToken).ConfigureAwait(false); + } + + protected override async Task BuildQuickInfoAsync( + CommonQuickInfoContext context, SyntaxToken token) + { + var tokenInformation = BindToken(context.Workspace, context.SemanticModel, token, context.CancellationToken); if (tokenInformation.Symbols.IsDefaultOrEmpty) - { return null; - } - return await CreateContentAsync(document.Project.Solution.Workspace, - token, model, tokenInformation, supportedPlatforms, - cancellationToken).ConfigureAwait(false); + return await CreateContentAsync( + context.Workspace, context.SemanticModel, token, tokenInformation, supportedPlatforms: null, context.CancellationToken).ConfigureAwait(false); } - private async Task<(SemanticModel model, TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms)> ComputeQuickInfoDataAsync( - Document document, - SyntaxToken token, - CancellationToken cancellationToken) + private async Task<(TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms)> ComputeQuickInfoDataAsync( + QuickInfoContext context, + SyntaxToken token) { + var cancellationToken = context.CancellationToken; + var document = context.Document; + var linkedDocumentIds = document.GetLinkedDocumentIds(); if (linkedDocumentIds.Any()) - { - return await ComputeFromLinkedDocumentsAsync(document, linkedDocumentIds, token, cancellationToken).ConfigureAwait(false); - } + return await ComputeFromLinkedDocumentsAsync(context, token, linkedDocumentIds).ConfigureAwait(false); - var (model, tokenInformation) = await BindTokenAsync(document, token, cancellationToken).ConfigureAwait(false); - - return (model, tokenInformation, supportedPlatforms: null); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var tokenInformation = BindToken( + document.Project.Solution.Workspace, semanticModel, token, cancellationToken); + return (tokenInformation, supportedPlatforms: null); } - private async Task<(SemanticModel model, TokenInformation, SupportedPlatformData supportedPlatforms)> ComputeFromLinkedDocumentsAsync( - Document document, - ImmutableArray linkedDocumentIds, + private async Task<(TokenInformation, SupportedPlatformData supportedPlatforms)> ComputeFromLinkedDocumentsAsync( + QuickInfoContext context, SyntaxToken token, - CancellationToken cancellationToken) + ImmutableArray linkedDocumentIds) { // Linked files/shared projects: imagine the following when GOO is false // #if GOO @@ -69,27 +74,34 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf // Instead, we need to find the head in which we get the best binding, // which in this case is the one with no errors. - var (model, tokenInformation) = await BindTokenAsync(document, token, cancellationToken).ConfigureAwait(false); + var cancellationToken = context.CancellationToken; + var document = context.Document; + var solution = document.Project.Solution; + var workspace = solution.Workspace; + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var mainTokenInformation = BindToken(workspace, semanticModel, token, cancellationToken); - var candidateProjects = new List() { document.Project.Id }; + var candidateProjects = new List { document.Project.Id }; var invalidProjects = new List(); - var candidateResults = new List<(DocumentId docId, SemanticModel model, TokenInformation tokenInformation)> + var candidateResults = new List<(DocumentId docId, TokenInformation tokenInformation)> { - (document.Id, model, tokenInformation) + (document.Id, mainTokenInformation) }; foreach (var linkedDocumentId in linkedDocumentIds) { - var linkedDocument = document.Project.Solution.GetRequiredDocument(linkedDocumentId); - var linkedToken = await FindTokenInLinkedDocumentAsync(token, linkedDocument, cancellationToken).ConfigureAwait(false); + var linkedDocument = solution.GetRequiredDocument(linkedDocumentId); + var linkedModel = await linkedDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var linkedToken = FindTokenInLinkedDocument(token, linkedModel, cancellationToken); if (linkedToken != default) { // Not in an inactive region, so this file is a candidate. candidateProjects.Add(linkedDocumentId.ProjectId); - var (linkedModel, linkedSymbols) = await BindTokenAsync(linkedDocument, linkedToken, cancellationToken).ConfigureAwait(false); - candidateResults.Add((linkedDocumentId, linkedModel, linkedSymbols)); + var linkedSymbols = BindToken(workspace, linkedModel, linkedToken, cancellationToken); + candidateResults.Add((linkedDocumentId, linkedSymbols)); } } @@ -99,58 +111,45 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf ?? candidateResults.First(); if (bestBinding.tokenInformation.Symbols.IsDefaultOrEmpty) - { return default; - } // We calculate the set of supported projects candidateResults.Remove(bestBinding); - foreach (var candidate in candidateResults) + foreach (var (docId, tokenInformation) in candidateResults) { // Does the candidate have anything remotely equivalent? - if (!candidate.tokenInformation.Symbols.Intersect(bestBinding.tokenInformation.Symbols, LinkedFilesSymbolEquivalenceComparer.Instance).Any()) - { - invalidProjects.Add(candidate.docId.ProjectId); - } + if (!tokenInformation.Symbols.Intersect(bestBinding.tokenInformation.Symbols, LinkedFilesSymbolEquivalenceComparer.Instance).Any()) + invalidProjects.Add(docId.ProjectId); } - var supportedPlatforms = new SupportedPlatformData(invalidProjects, candidateProjects, document.Project.Solution.Workspace); - - return (bestBinding.model, bestBinding.tokenInformation, supportedPlatforms); + var supportedPlatforms = new SupportedPlatformData(invalidProjects, candidateProjects, workspace); + return (bestBinding.tokenInformation, supportedPlatforms); } private static bool HasNoErrors(ImmutableArray symbols) => symbols.Length > 0 && !ErrorVisitor.ContainsError(symbols.FirstOrDefault()); - private static async Task FindTokenInLinkedDocumentAsync( + private static SyntaxToken FindTokenInLinkedDocument( SyntaxToken token, - Document linkedDocument, + SemanticModel linkedModel, CancellationToken cancellationToken) { - var root = await linkedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - + var root = linkedModel.SyntaxTree.GetRoot(cancellationToken); if (root == null) - { return default; - } // Don't search trivia because we want to ignore inactive regions var linkedToken = root.FindToken(token.SpanStart); // The new and old tokens should have the same span? - if (token.Span == linkedToken.Span) - { - return linkedToken; - } - - return default; + return token.Span == linkedToken.Span ? linkedToken : default; } protected static Task CreateContentAsync( Workspace workspace, - SyntaxToken token, SemanticModel semanticModel, + SyntaxToken token, TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms, CancellationToken cancellationToken) @@ -178,14 +177,15 @@ protected static Task CreateContentAsync( protected virtual NullableFlowState GetNullabilityAnalysis(Workspace workspace, SemanticModel semanticModel, ISymbol symbol, SyntaxNode node, CancellationToken cancellationToken) => NullableFlowState.None; - private async Task<(SemanticModel semanticModel, TokenInformation tokenInformation)> BindTokenAsync( - Document document, SyntaxToken token, CancellationToken cancellationToken) + private TokenInformation BindToken( + Workspace workspace, SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { - var syntaxFacts = document.GetRequiredLanguageService(); - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var hostServices = workspace.Services; + var languageServices = hostServices.GetLanguageServices(semanticModel.Language); + var syntaxFacts = languageServices.GetRequiredService(); var enclosingType = semanticModel.GetEnclosingNamedType(token.SpanStart, cancellationToken); - var symbols = GetSymbolsFromToken(token, document.Project.Solution.Workspace, semanticModel, cancellationToken); + var symbols = GetSymbolsFromToken(token, workspace, semanticModel, cancellationToken); var bindableParent = syntaxFacts.TryGetBindableParent(token); var overloads = bindableParent != null @@ -205,10 +205,10 @@ protected static Task CreateContentAsync( var nullableFlowState = NullableFlowState.None; if (bindableParent != null) { - nullableFlowState = GetNullabilityAnalysis(document.Project.Solution.Workspace, semanticModel, firstSymbol, bindableParent, cancellationToken); + nullableFlowState = GetNullabilityAnalysis(workspace, semanticModel, firstSymbol, bindableParent, cancellationToken); } - return (semanticModel, new TokenInformation(symbols, isAwait, nullableFlowState)); + return new TokenInformation(symbols, isAwait, nullableFlowState); } // Couldn't bind the token to specific symbols. If it's an operator, see if we can at @@ -218,11 +218,11 @@ protected static Task CreateContentAsync( var typeInfo = semanticModel.GetTypeInfo(token.Parent!, cancellationToken); if (IsOk(typeInfo.Type)) { - return (semanticModel, new TokenInformation(ImmutableArray.Create(typeInfo.Type))); + return new TokenInformation(ImmutableArray.Create(typeInfo.Type)); } } - return (semanticModel, new TokenInformation(ImmutableArray.Empty)); + return new TokenInformation(ImmutableArray.Empty); } private ImmutableArray GetSymbolsFromToken(SyntaxToken token, Workspace workspace, SemanticModel semanticModel, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoServiceWithProviders.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoServiceWithProviders.cs index e3dd71e44d64a..f74089aa5d72d 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoServiceWithProviders.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoServiceWithProviders.cs @@ -78,5 +78,38 @@ private ImmutableArray GetProviders() return null; } + + internal async Task GetQuickInfoAsync(Workspace workspace, SemanticModel semanticModel, int position, CancellationToken cancellationToken) + { + var extensionManager = _workspace.Services.GetRequiredService(); + + // returns the first non-empty quick info found (based on provider order) + foreach (var provider in GetProviders().OfType()) + { + try + { + if (!extensionManager.IsDisabled(provider)) + { + var context = new CommonQuickInfoContext(workspace, semanticModel, position, cancellationToken); + + var info = await provider.GetQuickInfoAsync(context).ConfigureAwait(false); + if (info != null) + { + return info; + } + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) when (extensionManager.CanHandleException(provider, e)) + { + extensionManager.HandleException(provider, e); + } + } + + return null; + } } } diff --git a/src/Features/Core/Portable/RQName/RQNodeBuilder.cs b/src/Features/Core/Portable/RQName/RQNodeBuilder.cs index ffd0366788531..eb8528feb2e19 100644 --- a/src/Features/Core/Portable/RQName/RQNodeBuilder.cs +++ b/src/Features/Core/Portable/RQName/RQNodeBuilder.cs @@ -197,12 +197,12 @@ private static IList GetNameParts(INamespaceSymbol @namespace) private static RQMethod? BuildMethod(IMethodSymbol symbol) { - if (symbol.MethodKind == MethodKind.UserDefinedOperator || - symbol.MethodKind == MethodKind.BuiltinOperator || - symbol.MethodKind == MethodKind.EventAdd || - symbol.MethodKind == MethodKind.EventRemove || - symbol.MethodKind == MethodKind.PropertySet || - symbol.MethodKind == MethodKind.PropertyGet) + if (symbol.MethodKind is MethodKind.UserDefinedOperator or + MethodKind.BuiltinOperator or + MethodKind.EventAdd or + MethodKind.EventRemove or + MethodKind.PropertySet or + MethodKind.PropertyGet) { return null; } diff --git a/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs index f40edcaeee032..371cb86f28a70 100644 --- a/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs @@ -55,7 +55,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var methodName = generator.GetName(methodDeclaration); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (!(semanticModel.GetDeclaredSymbol(methodDeclaration) is IMethodSymbol methodSymbol) || + if (semanticModel.GetDeclaredSymbol(methodDeclaration) is not IMethodSymbol methodSymbol || !IsValidGetMethod(methodSymbol)) { return; diff --git a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs index fcc8f12db2d7d..3ac0667b42987 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs @@ -95,9 +95,9 @@ public static Glyph GetGlyph(this ISymbol symbol) { var methodSymbol = (IMethodSymbol)symbol; - if (methodSymbol.MethodKind == MethodKind.UserDefinedOperator || - methodSymbol.MethodKind == MethodKind.Conversion || - methodSymbol.MethodKind == MethodKind.BuiltinOperator) + if (methodSymbol.MethodKind is MethodKind.UserDefinedOperator or + MethodKind.Conversion or + MethodKind.BuiltinOperator) { return Glyph.Operator; } @@ -241,7 +241,7 @@ public static ImmutableArray GetValueDocumentationParts(this ISymbol return null; } - if (symbolName == WellKnownMemberNames.DelegateInvokeName || symbolName == WellKnownMemberNames.DelegateBeginInvokeName) + if (symbolName is WellKnownMemberNames.DelegateInvokeName or WellKnownMemberNames.DelegateBeginInvokeName) { // We know that containingSymbol is the [Begin]Invoke() method of a delegate type, so we need to go up a level and take the method's containing symbol (i.e. the delegate), which contains the documentation. containingSymbol = containingSymbol.ContainingSymbol; diff --git a/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs b/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs index 9779f51e86529..c55bbdda570c3 100644 --- a/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs +++ b/src/Features/Core/Portable/Shared/Naming/IdentifierNameParts.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.NamingStyles; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -28,7 +29,8 @@ public static IdentifierNameParts CreateIdentifierNameParts(ISymbol symbol, Immu { var baseName = RemovePrefixesAndSuffixes(symbol, rules, symbol.Name); - var parts = StringBreaker.GetWordParts(baseName); + using var parts = TemporaryArray.Empty; + StringBreaker.AddWordParts(baseName, ref parts.AsRef()); var words = CreateWords(parts, baseName); return new IdentifierNameParts(baseName, words); @@ -71,15 +73,13 @@ private static string RemovePrefixesAndSuffixes(ISymbol symbol, ImmutableArray CreateWords(ArrayBuilder parts, string name) + private static ImmutableArray CreateWords(in TemporaryArray parts, string name) { - using var resultDisposer = ArrayBuilder.GetInstance(parts.Count, out var result); + using var words = TemporaryArray.Empty; foreach (var part in parts) - { - result.Add(name.Substring(part.Start, part.Length)); - } + words.Add(name.Substring(part.Start, part.Length)); - return result.ToImmutable(); + return words.ToImmutableAndClear(); } } } diff --git a/src/Features/Core/Portable/SimplifyThisOrMe/AbstractSimplifyThisOrMeDiagnosticAnalyzer.cs b/src/Features/Core/Portable/SimplifyThisOrMe/AbstractSimplifyThisOrMeDiagnosticAnalyzer.cs index a3b2b73d6d8c5..4a10a37a66066 100644 --- a/src/Features/Core/Portable/SimplifyThisOrMe/AbstractSimplifyThisOrMeDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/SimplifyThisOrMe/AbstractSimplifyThisOrMeDiagnosticAnalyzer.cs @@ -59,7 +59,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) var syntaxFacts = GetSyntaxFacts(); var expr = syntaxFacts.GetExpressionOfMemberAccessExpression(node); - if (!(expr is TThisExpressionSyntax)) + if (expr is not TThisExpressionSyntax) { return; } diff --git a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs b/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs new file mode 100644 index 0000000000000..8d6fdb8f56cd8 --- /dev/null +++ b/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.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.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.SolutionCrawler +{ + [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default)] + [Shared] + internal sealed class DefaultDocumentTrackingService : IDocumentTrackingService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DefaultDocumentTrackingService() + { + } + + public bool SupportsDocumentTracking => false; + + public event EventHandler ActiveDocumentChanged { add { } remove { } } + public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } + + public ImmutableArray GetVisibleDocuments() + => ImmutableArray.Empty; + + public DocumentId? TryGetActiveDocument() + => null; + } +} diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs b/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs index 05b2cb8bbedc1..c4ff3783ed268 100644 --- a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs @@ -10,6 +10,8 @@ namespace Microsoft.CodeAnalysis { internal interface IDocumentTrackingService : IWorkspaceService { + bool SupportsDocumentTracking { get; } + /// /// Get the of the active document. May be null if there is no active document /// or the active document is not in the workspace. @@ -21,7 +23,7 @@ internal interface IDocumentTrackingService : IWorkspaceService /// ImmutableArray GetVisibleDocuments(); - event EventHandler ActiveDocumentChanged; + event EventHandler ActiveDocumentChanged; /// /// Raised when a text buffer that's not part of a workspace is changed. diff --git a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerProgressReporter.cs b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerProgressReporter.cs index 32dee5e2778f8..ec74b31ae7cf6 100644 --- a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerProgressReporter.cs +++ b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerProgressReporter.cs @@ -65,7 +65,7 @@ public IDisposable GetEvaluatingScope() private void ChangeProgressStatus(ref int referenceCount, ProgressStatus status) { - var start = status == ProgressStatus.Started || status == ProgressStatus.Evaluating; + var start = status is ProgressStatus.Started or ProgressStatus.Evaluating; if (start ? (Interlocked.Increment(ref referenceCount) == 1) : (Interlocked.Decrement(ref referenceCount) == 0)) { var progressData = new ProgressData(status, pendingItemCount: null); diff --git a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs index aa51f79e56732..852981b0f8aaf 100644 --- a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -306,7 +307,8 @@ public Registration(int correlationId, Workspace workspace, SolutionCrawlerProgr ProgressReporter = progressReporter; } - public Solution CurrentSolution => Workspace.CurrentSolution; + public Solution GetSolutionToAnalyze() + => Workspace.CurrentSolution; } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs index 542f353aba62f..10f16c389c9cd 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs @@ -38,11 +38,7 @@ public AbstractPriorityProcessor( _lazyAnalyzers = lazyAnalyzers; Processor = processor; - - if (Processor._documentTracker != null) - { - Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged; - } + Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged; } public ImmutableArray Analyzers @@ -107,10 +103,7 @@ public override void Shutdown() { base.Shutdown(); - if (Processor._documentTracker != null) - { - Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged; - } + Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged; } private void OnNonRoslynBufferTextChanged(object? sender, EventArgs e) diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index edcb55e313bd0..b857367086186 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -92,9 +92,14 @@ public void Enqueue(WorkItem item) return; } + if (!_processor._documentTracker.SupportsDocumentTracking + && _processor._registration.Workspace.Kind is WorkspaceKind.RemoteWorkspace or WorkspaceKind.RemoteTemporaryWorkspace) + { + Debug.Fail($"Unexpected use of '{nameof(ExportIncrementalAnalyzerProviderAttribute.HighPriorityForActiveFile)}' in workspace kind '{_processor._registration.Workspace.Kind}' that cannot support active file tracking."); + } + // check whether given item is for active document, otherwise, nothing to do here - if (_processor._documentTracker == null || - _processor._documentTracker.TryGetActiveDocument() != item.DocumentId) + if (_processor._documentTracker.TryGetActiveDocument() != item.DocumentId) { return; } @@ -135,7 +140,7 @@ protected override async Task ExecuteAsync() // see whether we have work item for the document Contract.ThrowIfFalse(GetNextWorkItem(out var workItem, out var documentCancellation)); - var solution = _processor.CurrentSolution; + var solution = _processor._registration.GetSolutionToAnalyze(); // okay now we have work to do await ProcessDocumentAsync(solution, Analyzers, workItem, documentCancellation).ConfigureAwait(false); @@ -154,7 +159,7 @@ protected override async Task ExecuteAsync() private bool GetNextWorkItem(out WorkItem workItem, out CancellationToken cancellationToken) { // GetNextWorkItem since it can't fail. we still return bool to confirm that this never fail. - var documentId = _processor._documentTracker?.TryGetActiveDocument(); + var documentId = _processor._documentTracker.TryGetActiveDocument(); if (documentId != null) { if (_workItemQueue.TryTake(documentId, out workItem, out cancellationToken)) diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index a8380d1682143..0b5f7d08d5f4a 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -33,7 +33,7 @@ private partial class IncrementalAnalyzerProcessor private readonly Registration _registration; private readonly IAsynchronousOperationListener _listener; - private readonly IDocumentTrackingService? _documentTracker; + private readonly IDocumentTrackingService _documentTracker; private readonly IProjectCacheService? _cacheService; private readonly HighPriorityProcessor _highPriorityProcessor; @@ -77,7 +77,7 @@ public IncrementalAnalyzerProcessor( } // event and worker queues - _documentTracker = _registration.Workspace.Services.GetService(); + _documentTracker = _registration.Workspace.Services.GetRequiredService(); var globalNotificationService = _registration.Workspace.Services.GetRequiredService(); @@ -105,83 +105,11 @@ public void Enqueue(WorkItem item) { Contract.ThrowIfNull(item.DocumentId); - var options = _registration.Workspace.Options; - var analysisScope = SolutionCrawlerOptions.GetBackgroundAnalysisScope(options, item.Language); - - if (ShouldEnqueueForAllQueues(item, analysisScope)) - { - _highPriorityProcessor.Enqueue(item); - _normalPriorityProcessor.Enqueue(item); - _lowPriorityProcessor.Enqueue(item); - } - else - { - if (TryGetItemWithOverriddenAnalysisScope(item, _highPriorityProcessor.Analyzers, options, analysisScope, _listener, out var newWorkItem)) - { - _highPriorityProcessor.Enqueue(newWorkItem.Value); - } - - if (TryGetItemWithOverriddenAnalysisScope(item, _normalPriorityProcessor.Analyzers, options, analysisScope, _listener, out newWorkItem)) - { - _normalPriorityProcessor.Enqueue(newWorkItem.Value); - } - - if (TryGetItemWithOverriddenAnalysisScope(item, _lowPriorityProcessor.Analyzers, options, analysisScope, _listener, out newWorkItem)) - { - _lowPriorityProcessor.Enqueue(newWorkItem.Value); - } - - item.AsyncToken.Dispose(); - } + _highPriorityProcessor.Enqueue(item); + _normalPriorityProcessor.Enqueue(item); + _lowPriorityProcessor.Enqueue(item); ReportPendingWorkItemCount(); - - return; - - bool ShouldEnqueueForAllQueues(WorkItem item, BackgroundAnalysisScope analysisScope) - { - var reasons = item.InvocationReasons; - - // For active file analysis scope we only process following: - // 1. Active documents - // 2. Closed and removed documents to ensure that data for removed and closed documents - // is no longer held in memory and removed from any user visible components. - // For example, this ensures that diagnostics for closed/removed documents are removed from error list. - // Note that we don't need to specially handle "Project removed" or "Project closed" case, as the solution crawler - // enqueues individual "DocumentRemoved" work items for each document in the removed project. - if (analysisScope == BackgroundAnalysisScope.ActiveFile && - !reasons.Contains(PredefinedInvocationReasons.DocumentClosed) && - !reasons.Contains(PredefinedInvocationReasons.DocumentRemoved)) - { - return item.DocumentId == _documentTracker?.TryGetActiveDocument(); - } - - return true; - } - } - - private static bool TryGetItemWithOverriddenAnalysisScope( - WorkItem item, - ImmutableArray allAnalyzers, - OptionSet options, - BackgroundAnalysisScope analysisScope, - IAsynchronousOperationListener listener, - [NotNullWhen(returnValue: true)] out WorkItem? newWorkItem) - { - var analyzersToExecute = item.GetApplicableAnalyzers(allAnalyzers); - - var analyzersWithOverriddenAnalysisScope = analyzersToExecute - .Where(a => a.GetOverriddenBackgroundAnalysisScope(options, analysisScope) != analysisScope) - .ToImmutableHashSet(); - - if (!analyzersWithOverriddenAnalysisScope.IsEmpty) - { - newWorkItem = item.With(analyzersWithOverriddenAnalysisScope, listener.BeginAsyncOperation("WorkItem")); - return true; - } - - newWorkItem = null; - return false; } public void AddAnalyzer(IIncrementalAnalyzer analyzer, bool highPriorityForActiveFile) @@ -204,8 +132,7 @@ public void Shutdown() public ImmutableArray Analyzers => _normalPriorityProcessor.Analyzers; - private Solution CurrentSolution => _registration.CurrentSolution; - private ProjectDependencyGraph DependencyGraph => CurrentSolution.GetProjectDependencyGraph(); + private ProjectDependencyGraph DependencyGraph => _registration.GetSolutionToAnalyze().GetProjectDependencyGraph(); private IDiagnosticAnalyzerService? DiagnosticAnalyzerService => _lazyDiagnosticAnalyzerService?.Value; public Task AsyncProcessorTask @@ -244,7 +171,7 @@ private async Task ProcessDocumentAnalyzersAsync( await RunAnalyzersAsync(analyzers, textDocument, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); } - if (!(textDocument is Document document)) + if (textDocument is not Document document) { // Semantic analysis is not supported for non-source documents. return; @@ -391,9 +318,6 @@ static bool ReportWithoutCrashUnlessAllCanceledAndPropagate(AggregateException a return service.IsMethodLevelMember(memberNode) ? memberNode : null; } - internal ProjectId? GetActiveProjectId() - => _documentTracker?.TryGetActiveDocument()?.ProjectId; - private static string EnqueueLogger(int tick, object documentOrProjectId, bool replaced) { if (documentOrProjectId is DocumentId documentId) @@ -443,11 +367,11 @@ public void Dispose() { } private class AnalyzersGetter { private readonly List> _analyzerProviders; - private readonly Dictionary>> _analyzerMap; + private readonly Dictionary> _analyzerMap; public AnalyzersGetter(IEnumerable> analyzerProviders) { - _analyzerMap = new Dictionary>>(); + _analyzerMap = new Dictionary>(); _analyzerProviders = analyzerProviders.ToList(); } @@ -458,9 +382,9 @@ public ImmutableArray GetOrderedAnalyzers(Workspace worksp if (!_analyzerMap.TryGetValue(workspace, out var analyzers)) { // Sort list so DiagnosticIncrementalAnalyzers (if any) come first. OrderBy orders 'false' keys before 'true'. - analyzers = _analyzerProviders.Select(p => ValueTuple.Create(p.Value.CreateIncrementalAnalyzer(workspace), p.Metadata.HighPriorityForActiveFile)) - .Where(t => t.Item1 != null) - .OrderBy(t => !(t.Item1 is DiagnosticIncrementalAnalyzer)) + analyzers = _analyzerProviders.Select(p => (analyzer: p.Value.CreateIncrementalAnalyzer(workspace), highPriorityForActiveFile: p.Metadata.HighPriorityForActiveFile)) + .Where(t => t.analyzer != null) + .OrderBy(t => t.analyzer is not DiagnosticIncrementalAnalyzer) .ToImmutableArray()!; _analyzerMap[workspace] = analyzers; @@ -469,11 +393,11 @@ public ImmutableArray GetOrderedAnalyzers(Workspace worksp if (onlyHighPriorityAnalyzer) { // include only high priority analyzer for active file - return analyzers.Where(t => t.Item2).Select(t => t.Item1).ToImmutableArray(); + return analyzers.SelectAsArray(t => t.highPriorityForActiveFile, t => t.analyzer); } // return all analyzers - return analyzers.Select(t => t.Item1).ToImmutableArray(); + return analyzers.Select(t => t.analyzer).ToImmutableArray(); } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index 3c4eb2fca1e26..19b00ffb7a35b 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -52,8 +52,12 @@ protected override async Task ExecuteAsync() await WaitForHigherPriorityOperationsAsync().ConfigureAwait(false); // process any available project work, preferring the active project. + var preferableProjectId = Processor._documentTracker.SupportsDocumentTracking + ? Processor._documentTracker.TryGetActiveDocument()?.ProjectId + : null; + if (_workItemQueue.TryTakeAnyWork( - Processor.GetActiveProjectId(), Processor.DependencyGraph, Processor.DiagnosticAnalyzerService, + preferableProjectId, Processor.DependencyGraph, Processor.DiagnosticAnalyzerService, out var workItem, out var projectCancellation)) { await ProcessProjectAsync(Analyzers, workItem, projectCancellation).ConfigureAwait(false); @@ -126,7 +130,7 @@ private async Task ProcessProjectAsync(ImmutableArray anal // we do have work item for this project var projectId = workItem.ProjectId; var processedEverything = false; - var processingSolution = Processor.CurrentSolution; + var processingSolution = Processor._registration.GetSolutionToAnalyze(); try { diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 6794c08832399..213ac6b540b6f 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -227,20 +227,17 @@ private void EnableProjectCacheIfNecessary(ProjectId currentProject) private IEnumerable GetPrioritizedPendingDocuments() { - if (Processor._documentTracker != null) + // First the active document + var activeDocumentId = Processor._documentTracker.TryGetActiveDocument(); + if (activeDocumentId != null) { - // First the active document - var activeDocumentId = Processor._documentTracker.TryGetActiveDocument(); - if (activeDocumentId != null) - { - yield return activeDocumentId; - } + yield return activeDocumentId; + } - // Now any visible documents - foreach (var visibleDocumentId in Processor._documentTracker.GetVisibleDocuments()) - { - yield return visibleDocumentId; - } + // Now any visible documents + foreach (var visibleDocumentId in Processor._documentTracker.GetVisibleDocuments()) + { + yield return visibleDocumentId; } // Any other high priority documents @@ -254,6 +251,11 @@ private async Task TryProcessOneHigherPriorityDocumentAsync() { try { + if (!Processor._documentTracker.SupportsDocumentTracking) + { + return false; + } + foreach (var documentId in GetPrioritizedPendingDocuments()) { if (CancellationToken.IsCancellationRequested) @@ -327,7 +329,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana // using later version of solution is always fine since, as long as there is new work item in the queue, // solution crawler will eventually call the last workitem with the lastest solution // making everything to catch up - var solution = Processor.CurrentSolution; + var solution = Processor._registration.GetSolutionToAnalyze(); try { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) @@ -522,7 +524,10 @@ private async Task ResetStatesAsync() return; } - await Processor.RunAnalyzersAsync(Analyzers, Processor.CurrentSolution, workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync( + Analyzers, + Processor._registration.GetSolutionToAnalyze(), + workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); foreach (var id in Processor.GetOpenDocumentIds()) { @@ -538,7 +543,7 @@ private async Task ResetStatesAsync() bool IsSolutionChanged() { - var currentSolution = Processor.CurrentSolution; + var currentSolution = Processor._registration.GetSolutionToAnalyze(); var oldSolution = _lastSolution; if (currentSolution == oldSolution) @@ -577,7 +582,7 @@ public override void Shutdown() { base.Shutdown(); - SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor.CurrentSolution, Processor._logAggregator, Analyzers); + SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor._registration.GetSolutionToAnalyze(), Processor._logAggregator, Analyzers); _workItemQueue.Dispose(); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs index 2940db02c61bb..fe759ab282674 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs @@ -195,9 +195,9 @@ private async Task EnqueueWorkItemAsync(Document thisDocument, ImmutableArray> analyzerProviders, @@ -51,7 +49,7 @@ public WorkCoordinator( _listener = listener; _optionService = _registration.Workspace.Services.GetRequiredService(); - _documentTrackingService = _registration.Workspace.Services.GetService(); + _documentTrackingService = _registration.Workspace.Services.GetRequiredService(); // event and worker queues _shutdownNotificationSource = new CancellationTokenSource(); @@ -85,11 +83,7 @@ public WorkCoordinator( _optionService.OptionChanged += OnOptionChanged; // subscribe to active document changed event for active file background analysis scope. - if (_documentTrackingService != null) - { - _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.Workspace.CurrentSolution); - _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; - } + _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; } public int CorrelationId => _registration.CorrelationId; @@ -100,18 +94,14 @@ public void AddAnalyzer(IIncrementalAnalyzer analyzer, bool highPriorityForActiv _documentAndProjectWorkerProcessor.AddAnalyzer(analyzer, highPriorityForActiveFile); // and ask to re-analyze whole solution for the given analyzer - var scope = new ReanalyzeScope(_registration.CurrentSolution.Id); + var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); } public void Shutdown(bool blockingShutdown) { _optionService.OptionChanged -= OnOptionChanged; - - if (_documentTrackingService != null) - { - _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; - } + _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; // detach from the workspace _registration.Workspace.WorkspaceChanged -= OnWorkspaceChanged; @@ -196,7 +186,7 @@ private void ReanalyzeOnOptionChange(object? sender, OptionChangedEventArgs e) { if (forceAnalyze || analyzer.NeedsReanalysisOnOptionChanged(sender, e)) { - var scope = new ReanalyzeScope(_registration.CurrentSolution.Id); + var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); } } @@ -212,42 +202,25 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool { // log big reanalysis request from things like fix all, suppress all or option changes // we are not interested in 1 file re-analysis request which can happen from like venus typing - var solution = _registration.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); SolutionCrawlerLogger.LogReanalyze( CorrelationId, analyzer, scope.GetDocumentCount(solution), scope.GetLanguagesStringForTelemetry(solution), highPriority); } } - private void OnActiveDocumentChanged(object? sender, DocumentId activeDocumentId) + private void OnActiveDocumentChanged(object? sender, DocumentId? activeDocumentId) { - var solution = _registration.Workspace.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); + if (solution.GetProject(activeDocumentId?.ProjectId) is not { } activeProject) + return; - // Check if we are only performing backgroung analysis for active file. - if (activeDocumentId != null) + RoslynDebug.AssertNotNull(activeDocumentId); + var analysisScope = SolutionCrawlerOptions.GetBackgroundAnalysisScope(activeProject); + if (analysisScope == BackgroundAnalysisScope.ActiveFile) { - // Change to active document needs to trigger following events in active file analysis scope: - // 1. Request analysis for newly active file, similar to a newly opened file. - // 2. Clear analysis data for prior active file, similar to a closed file. - // Note that if 'activeDocumentId' is null, i.e. user navigated to a non-source file, - // we are treating it as a no-op here. - // As soon as user switches to a source document, we will perform the appropriate analysis callbacks - // on the next active document changed event. - var activeDocument = solution.GetDocument(activeDocumentId); - if (activeDocument != null && - SolutionCrawlerOptions.GetBackgroundAnalysisScope(activeDocument.Project) == BackgroundAnalysisScope.ActiveFile) - { - lock (_gate) - { - if (_lastActiveDocument != null) - { - EnqueueEvent(_lastActiveDocument.Project.Solution, _lastActiveDocument.Id, InvocationReasons.DocumentClosed, "OnDocumentClosed"); - } - - _lastActiveDocument = activeDocument; - } - - EnqueueEvent(activeDocument.Project.Solution, activeDocument.Id, InvocationReasons.DocumentOpened, "OnDocumentOpened"); - } + // When the active document changes and we are only analyzing the active file, trigger a document + // changed event to reanalyze the newly-active file. + EnqueueEvent(solution, activeDocumentId, InvocationReasons.DocumentChanged, nameof(OnActiveDocumentChanged)); } } @@ -272,7 +245,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) ae = ae.Flatten(); // If we had a mix of exceptions, don't eat it - if (ae.InnerExceptions.Any(e => !(e is OperationCanceledException)) || + if (ae.InnerExceptions.Any(e => e is not OperationCanceledException) || ae.InnerExceptions.Cast().Any(NotOurShutdownToken)) { // We had a cancellation with a different token, so don't eat it @@ -509,7 +482,7 @@ private async Task EnqueueWorkItemAsync(Project project, InvocationReasons invoc private async Task EnqueueWorkItemAsync(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool highPriority) { - var solution = _registration.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); var invocationReasons = highPriority ? InvocationReasons.ReanalyzeHighPriority : InvocationReasons.Reanalyze; foreach (var document in scope.GetDocuments(solution)) @@ -683,7 +656,7 @@ internal TestAccessor(WorkCoordinator workCoordinator) internal void WaitUntilCompletion(ImmutableArray workers) { - var solution = _workCoordinator._registration.CurrentSolution; + var solution = _workCoordinator._registration.GetSolutionToAnalyze(); var list = new List(); foreach (var project in solution.Projects) diff --git a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs index 68a960905b158..7be3922ed2e85 100644 --- a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs +++ b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs @@ -193,7 +193,7 @@ private async Task CheckItemsAsync( private static async Task GetInsertionTextAsync(Document document, CompletionItem item, TextSpan completionListSpan, CancellationToken cancellationToken) { var service = CompletionService.GetService(document); - var change = await service.GetChangeAsync(document, item, completionListSpan, commitCharacter: null, disallowAddingImports: false, cancellationToken).ConfigureAwait(false); + var change = await service.GetChangeAsync(document, item, commitCharacter: null, cancellationToken).ConfigureAwait(false); var text = change.TextChange.NewText; var nonCharIndex = text.IndexOfAny(s_punctuation); return nonCharIndex > 0 diff --git a/src/Features/Core/Portable/SplitOrMergeIfStatements/Nested/AbstractMergeNestedIfStatementsCodeRefactoringProvider.cs b/src/Features/Core/Portable/SplitOrMergeIfStatements/Nested/AbstractMergeNestedIfStatementsCodeRefactoringProvider.cs index 4367bb7799e1b..9fec2ebaaba5c 100644 --- a/src/Features/Core/Portable/SplitOrMergeIfStatements/Nested/AbstractMergeNestedIfStatementsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/SplitOrMergeIfStatements/Nested/AbstractMergeNestedIfStatementsCodeRefactoringProvider.cs @@ -196,7 +196,7 @@ private static async Task CanBeMergedAsync( // A statement should always be in a statement container, but we'll do a defensive check anyway so that // we don't crash if the helper is missing some cases or there's a new language feature it didn't account for. Debug.Assert(syntaxFacts.GetStatementContainer(outerIfStatement) is object); - if (!(syntaxFacts.GetStatementContainer(outerIfStatement) is { } container)) + if (syntaxFacts.GetStatementContainer(outerIfStatement) is not { } container) { return false; } diff --git a/src/Features/Core/Portable/Structure/BlockStructureProvider.cs b/src/Features/Core/Portable/Structure/BlockStructureProvider.cs index 8ba2938213225..05aea7498fd13 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureProvider.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureProvider.cs @@ -2,17 +2,10 @@ // 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.Threading.Tasks; - namespace Microsoft.CodeAnalysis.Structure { internal abstract class BlockStructureProvider { - public abstract Task ProvideBlockStructureAsync(BlockStructureContext context); - - public virtual void ProvideBlockStructure(BlockStructureContext context) - => ProvideBlockStructureAsync(context).Wait(context.CancellationToken); + public abstract void ProvideBlockStructure(BlockStructureContext context); } } diff --git a/src/Features/Core/Portable/Structure/BlockStructureService.cs b/src/Features/Core/Portable/Structure/BlockStructureService.cs index 6ff70586b8699..f0c5bd48f30b1 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureService.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureService.cs @@ -26,14 +26,5 @@ public static BlockStructureService GetService(Document document) public abstract string Language { get; } public abstract Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken); - - /// - /// Gets the for the provided document. Note that the - /// default implementation works by calling into - /// and blocking on the async operation. Subclasses should provide more efficient - /// implementations that do not block on async operations if possible. - /// - public virtual BlockStructure GetBlockStructure(Document document, CancellationToken cancellationToken) - => GetBlockStructureAsync(document, cancellationToken).WaitAndGetResult(cancellationToken); } } diff --git a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs index 45cc428bf07e6..0b35ad0fe701c 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs @@ -50,27 +50,9 @@ public override async Task GetBlockStructureAsync( CancellationToken cancellationToken) { var context = await CreateContextAsync(document, cancellationToken).ConfigureAwait(false); - return await GetBlockStructureAsync(context, _providers).ConfigureAwait(false); - } - - public override BlockStructure GetBlockStructure( - Document document, - CancellationToken cancellationToken) - { - var context = CreateContextAsync(document, cancellationToken).WaitAndGetResult(cancellationToken); return GetBlockStructure(context, _providers); } - public async Task GetBlockStructureAsync( - SyntaxTree syntaxTree, - OptionSet options, - bool isMetadataAsSource, - CancellationToken cancellationToken) - { - var context = CreateContext(syntaxTree, options, isMetadataAsSource, cancellationToken); - return await GetBlockStructureAsync(context, _providers).ConfigureAwait(false); - } - public BlockStructure GetBlockStructure( SyntaxTree syntaxTree, OptionSet options, @@ -99,26 +81,12 @@ private static BlockStructureContext CreateContext( return new BlockStructureContext(syntaxTree, optionProvider, cancellationToken); } - private static async Task GetBlockStructureAsync( - BlockStructureContext context, - ImmutableArray providers) - { - foreach (var provider in providers) - { - await provider.ProvideBlockStructureAsync(context).ConfigureAwait(false); - } - - return CreateBlockStructure(context); - } - private static BlockStructure GetBlockStructure( BlockStructureContext context, ImmutableArray providers) { foreach (var provider in providers) - { provider.ProvideBlockStructure(context); - } return CreateBlockStructure(context); } diff --git a/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs b/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs index 1a10cbd97b4cc..025bff73be755 100644 --- a/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs +++ b/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs @@ -30,51 +30,22 @@ protected AbstractBlockStructureProvider( _triviaProviderMap = defaultTriviaOutlinerMap; } - /// - /// Keep in sync with - /// public override void ProvideBlockStructure(BlockStructureContext context) { try { var syntaxRoot = context.SyntaxTree.GetRoot(context.CancellationToken); + using var spans = TemporaryArray.Empty; + BlockSpanCollector.CollectBlockSpans( + syntaxRoot, context.OptionProvider, _nodeProviderMap, _triviaProviderMap, ref spans.AsRef(), context.CancellationToken); - ProvideBlockStructureWorker(context, syntaxRoot); + foreach (var span in spans) + context.AddBlockSpan(span); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } - - /// - /// Keep in sync with - /// - public override async Task ProvideBlockStructureAsync(BlockStructureContext context) - { - try - { - var syntaxRoot = await context.SyntaxTree.GetRootAsync(context.CancellationToken).ConfigureAwait(false); - - ProvideBlockStructureWorker(context, syntaxRoot); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) - { - throw ExceptionUtilities.Unreachable; - } - } - - private void ProvideBlockStructureWorker( - BlockStructureContext context, SyntaxNode syntaxRoot) - { - using var spans = TemporaryArray.Empty; - BlockSpanCollector.CollectBlockSpans( - syntaxRoot, context.OptionProvider, _nodeProviderMap, _triviaProviderMap, ref spans.AsRef(), context.CancellationToken); - - foreach (var span in spans) - { - context.AddBlockSpan(span); - } - } } } diff --git a/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index c42109db9f6a8..511f61851cdd7 100644 --- a/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -29,12 +29,11 @@ internal class UnifiedSuggestedActionsSource /// /// Gets, filters, and orders code fixes. /// - public static async Task> GetFilterAndOrderCodeFixesAsync( + public static async ValueTask> GetFilterAndOrderCodeFixesAsync( Workspace workspace, ICodeFixService codeFixService, Document document, TextSpan selection, - bool includeSuppressionFixes, bool isBlocking, Func addOperationScope, CancellationToken cancellationToken) @@ -43,13 +42,11 @@ public static async Task> GetFilterAnd // it. However, it's deliberate. We want to make sure that the code runs on // the background so that no one takes an accidentally dependency on running on // the UI thread. - var fixes = await Task.Run( - () => codeFixService.GetFixesAsync( - document, selection, includeSuppressionFixes, isBlocking, - addOperationScope, cancellationToken), cancellationToken).ConfigureAwait(false); + var fixes = await Task.Run(() => codeFixService.GetFixesAsync( + document, selection, includeSuppressionFixes: true, isBlocking, addOperationScope, cancellationToken), cancellationToken).ConfigureAwait(false); var filteredFixes = fixes.WhereAsArray(c => c.Fixes.Length > 0); - var organizedFixes = OrganizeFixes(workspace, filteredFixes, includeSuppressionFixes); + var organizedFixes = OrganizeFixes(workspace, filteredFixes); return organizedFixes; } @@ -59,14 +56,13 @@ public static async Task> GetFilterAnd /// private static ImmutableArray OrganizeFixes( Workspace workspace, - ImmutableArray fixCollections, - bool includeSuppressionFixes) + ImmutableArray fixCollections) { var map = ImmutableDictionary.CreateBuilder>(); using var _ = ArrayBuilder.GetInstance(out var order); // First group fixes by diagnostic and priority. - GroupFixes(workspace, fixCollections, map, order, includeSuppressionFixes); + GroupFixes(workspace, fixCollections, map, order); // Then prioritize between the groups. var prioritizedFixes = PrioritizeFixGroups(map.ToImmutable(), order.ToImmutable(), workspace); @@ -80,21 +76,16 @@ private static void GroupFixes( Workspace workspace, ImmutableArray fixCollections, IDictionary> map, - ArrayBuilder order, - bool includeSuppressionFixes) + ArrayBuilder order) { foreach (var fixCollection in fixCollections) - { - ProcessFixCollection( - workspace, map, order, includeSuppressionFixes, fixCollection); - } + ProcessFixCollection(workspace, map, order, fixCollection); } private static void ProcessFixCollection( Workspace workspace, IDictionary> map, ArrayBuilder order, - bool includeSuppressionFixes, CodeFixCollection fixCollection) { var fixes = fixCollection.Fixes; @@ -108,11 +99,8 @@ private static void ProcessFixCollection( // Add suppression fixes to the end of a given SuggestedActionSet so that they // always show up last in a group. - if (includeSuppressionFixes) - { - AddCodeActions(workspace, map, order, fixCollection, - GetFixAllSuggestedActionSet, supressionCodeFixes); - } + AddCodeActions(workspace, map, order, fixCollection, + GetFixAllSuggestedActionSet, supressionCodeFixes); return; @@ -332,8 +320,8 @@ private static ImmutableArray PrioritizeFixGroups( } } - Debug.Assert(set.CategoryName == UnifiedPredefinedSuggestedActionCategoryNames.CodeFix || - set.CategoryName == UnifiedPredefinedSuggestedActionCategoryNames.ErrorFix); + Debug.Assert(set.CategoryName is UnifiedPredefinedSuggestedActionCategoryNames.CodeFix or + UnifiedPredefinedSuggestedActionCategoryNames.ErrorFix); // If this set contains an error fix, then change the result category to ErrorFix if (set.CategoryName == UnifiedPredefinedSuggestedActionCategoryNames.ErrorFix) @@ -520,7 +508,7 @@ private static UnifiedSuggestedActionSetPriority GetUnifiedSuggestedActionSetPri /// Should be called with the results from /// and . /// - public static ImmutableArray? FilterAndOrderActionSets( + public static ImmutableArray FilterAndOrderActionSets( ImmutableArray fixes, ImmutableArray refactorings, TextSpan? selectionOpt) @@ -529,9 +517,7 @@ private static UnifiedSuggestedActionSetPriority GetUnifiedSuggestedActionSetPri // ordered against each other. var result = GetInitiallyOrderedActionSets(selectionOpt, fixes, refactorings); if (result.IsEmpty) - { - return null; - } + return ImmutableArray.Empty; // Now that we have the entire set of action sets, inline, sort and filter // them appropriately against each other. diff --git a/src/Features/Core/Portable/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs b/src/Features/Core/Portable/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs index 0fc8c1dba340a..03adc9af14c93 100644 --- a/src/Features/Core/Portable/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs @@ -68,7 +68,7 @@ public async Task ComputeRefactoringsAsync( return; } - if (!(argument.Parent is TArgumentListSyntax argumentList)) + if (argument.Parent is not TArgumentListSyntax argumentList) { return; } diff --git a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs index 775982f4fec81..1a78e4d6fa0b6 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs @@ -18,6 +18,7 @@ namespace Microsoft.CodeAnalysis.Host internal sealed class BackgroundCompiler : IDisposable { private Workspace _workspace; + private readonly IDocumentTrackingService _documentTrackingService; private readonly TaskQueue _taskQueue; #pragma warning disable IDE0052 // Remove unread private members @@ -31,6 +32,7 @@ internal sealed class BackgroundCompiler : IDisposable public BackgroundCompiler(Workspace workspace) { _workspace = workspace; + _documentTrackingService = _workspace.Services.GetRequiredService(); // make a scheduler that runs on the thread pool var listenerProvider = workspace.Services.GetRequiredService(); @@ -101,12 +103,13 @@ private void Rebuild(Solution solution, ProjectId initialProject = null) // build the current compilations without rebuilding the entire DeclarationTable CancelBuild(releasePreviousCompilations: false); - var allProjects = _workspace.GetOpenDocumentIds().Select(d => d.ProjectId).ToSet(); + var allOpenProjects = _workspace.GetOpenDocumentIds().Select(d => d.ProjectId).ToSet(); + var activeProject = _documentTrackingService.TryGetActiveDocument()?.ProjectId; // don't even get started if there is nothing to do - if (allProjects.Count > 0) + if (allOpenProjects.Count > 0) { - _ = BuildCompilationsAsync(solution, initialProject, allProjects); + _ = BuildCompilationsAsync(solution, initialProject, allOpenProjects, activeProject); } } } @@ -127,12 +130,13 @@ private void CancelBuild(bool releasePreviousCompilations) private Task BuildCompilationsAsync( Solution solution, ProjectId initialProject, - ISet allProjects) + ISet allOpenProjects, + ProjectId activeProject) { var cancellationToken = _cancellationSource.Token; return _taskQueue.ScheduleTask( "BackgroundCompiler.BuildCompilationsAsync", - () => BuildCompilationsAsync(solution, initialProject, allProjects, cancellationToken), + () => BuildCompilationsAsync(solution, initialProject, allOpenProjects, activeProject, cancellationToken), cancellationToken); } @@ -140,6 +144,7 @@ private Task BuildCompilationsAsync( Solution solution, ProjectId initialProject, ISet projectsToBuild, + ProjectId activeProject, CancellationToken cancellationToken) { var allProjectIds = new List(); @@ -156,7 +161,20 @@ private Task BuildCompilationsAsync( // set the background analysis scope to only analyze active files. var compilationTasks = allProjectIds .Select(solution.GetProject) - .Where(p => p != null && SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) != BackgroundAnalysisScope.ActiveFile) + .Where(p => + { + if (p is null) + return false; + + if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) == BackgroundAnalysisScope.ActiveFile) + { + // For open files with Active File analysis scope, only build the compilation if the project is + // active. + return p.Id == activeProject; + } + + return true; + }) .Select(p => p.GetCompilationAsync(cancellationToken)) .ToArray(); return Task.WhenAll(compilationTasks).SafeContinueWith(t => diff --git a/src/Features/Core/Portable/Workspace/BackgroundParser.cs b/src/Features/Core/Portable/Workspace/BackgroundParser.cs index 40a873963aaa9..9f70d143d7a9c 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundParser.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundParser.cs @@ -42,7 +42,8 @@ public BackgroundParser(Workspace workspace) var listenerProvider = workspace.Services.GetRequiredService(); _taskQueue = new TaskQueue(listenerProvider.GetListener(), TaskScheduler.Default); - _documentTrackingService = workspace.Services.GetService(); + _documentTrackingService = workspace.Services.GetRequiredService(); + _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; _workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -50,6 +51,9 @@ public BackgroundParser(Workspace workspace) workspace.DocumentClosed += OnDocumentClosed; } + private void OnActiveDocumentChanged(object sender, DocumentId activeDocumentId) + => Parse(_workspace.CurrentSolution.GetDocument(activeDocumentId)); + private void OnDocumentOpened(object sender, DocumentEventArgs args) => Parse(args.Document); @@ -164,7 +168,7 @@ public void Parse(Document document) CancelParse(document.Id); if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) == BackgroundAnalysisScope.ActiveFile && - _documentTrackingService?.TryGetActiveDocument() != document.Id) + _documentTrackingService.TryGetActiveDocument() != document.Id) { // Avoid performing any background parsing for non-active files // if the user has explicitly set the background analysis scope diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs new file mode 100644 index 0000000000000..e4de0f1091164 --- /dev/null +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Experiments; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices +{ + /// + /// Provides a compile-time view of the current workspace solution. + /// Workaround for Razor projects which generate both design-time and compile-time source files. + /// TODO: remove https://github.com/dotnet/roslyn/issues/51678 + /// + internal sealed class CompileTimeSolutionProvider : ICompileTimeSolutionProvider + { + [ExportWorkspaceServiceFactory(typeof(ICompileTimeSolutionProvider), WorkspaceKind.Host), Shared] + private sealed class Factory : IWorkspaceServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory() + { + } + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + public IWorkspaceService? CreateService(HostWorkspaceServices workspaceServices) + => new CompileTimeSolutionProvider(workspaceServices.Workspace); + } + + private const string RazorEncConfigFileName = "RazorSourceGenerator.razorencconfig"; + + private readonly object _gate = new(); + + private Solution? _lazyCompileTimeSolution; + private int? _correspondingDesignTimeSolutionVersion; + + public CompileTimeSolutionProvider(Workspace workspace) + { + workspace.WorkspaceChanged += (s, e) => + { + if (e.Kind is WorkspaceChangeKind.SolutionCleared or WorkspaceChangeKind.SolutionRemoved) + { + lock (_gate) + { + _lazyCompileTimeSolution = null; + _correspondingDesignTimeSolutionVersion = null; + } + } + }; + } + + private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) + => documentState.FilePath != null && documentState.FilePath.EndsWith(RazorEncConfigFileName, StringComparison.OrdinalIgnoreCase); + + public Solution GetCompileTimeSolution(Solution designTimeSolution) + { + lock (_gate) + { + // Design time solution hasn't changed since we calculated the last compile-time solution: + if (designTimeSolution.WorkspaceVersion == _correspondingDesignTimeSolutionVersion) + { + Contract.ThrowIfNull(_lazyCompileTimeSolution); + return _lazyCompileTimeSolution; + } + + using var _1 = ArrayBuilder.GetInstance(out var configIdsToRemove); + using var _2 = ArrayBuilder.GetInstance(out var documentIdsToRemove); + + var compileTimeSolution = designTimeSolution; + + foreach (var (_, projectState) in designTimeSolution.State.ProjectStates) + { + var anyConfigs = false; + + foreach (var configState in projectState.AnalyzerConfigDocumentStates.States) + { + if (IsRazorAnalyzerConfig(configState)) + { + configIdsToRemove.Add(configState.Id); + anyConfigs = true; + } + } + + // only remove design-time only documents when source-generated ones replace them + if (anyConfigs) + { + foreach (var documentState in projectState.DocumentStates.States) + { + if (documentState.Attributes.DesignTimeOnly) + { + documentIdsToRemove.Add(documentState.Id); + } + } + } + } + + _lazyCompileTimeSolution = designTimeSolution + .RemoveAnalyzerConfigDocuments(configIdsToRemove.ToImmutable()) + .RemoveDocuments(documentIdsToRemove.ToImmutable()); + + _correspondingDesignTimeSolutionVersion = designTimeSolution.WorkspaceVersion; + return _lazyCompileTimeSolution; + } + } + } +} diff --git a/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs new file mode 100644 index 0000000000000..16702bbf87f9d --- /dev/null +++ b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Host +{ + /// + /// Provides a compile-time view of the current workspace solution. + /// Workaround for Razor projects which generate both design-time and compile-time source files. + /// TODO: remove https://github.com/dotnet/roslyn/issues/51678 + /// + internal interface ICompileTimeSolutionProvider : IWorkspaceService + { + Solution GetCompileTimeSolution(Solution designTimeSolution); + } +} diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs index 955a4811d0fd3..c33a371833560 100644 --- a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs +++ b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs @@ -44,7 +44,7 @@ protected AbstractBinaryExpressionWrapper( public sealed override async Task TryCreateComputerAsync( Document document, int position, SyntaxNode node, CancellationToken cancellationToken) { - if (!(node is TBinaryExpressionSyntax binaryExpr)) + if (node is not TBinaryExpressionSyntax binaryExpr) { return null; } diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 3676f4c6da9db..8d5ac4a753f43 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (zkráceno) @@ -92,7 +97,7 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn 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. + Když se do obecného typu přidá {0}, ladicí relace nebude moct pokračovat. @@ -105,6 +110,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Přidání prvku {0} do rozhraní zabrání v pokračování relace ladění. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Když se do záznamu přidá parametr pozice, ladicí relace nebude moct pokračovat. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Přidání metody s explicitním specifikátorem rozhraní zabrání v pokračování relace ladění. @@ -132,17 +142,17 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Apply file header preferences - Apply file header preferences + Použít předvolby hlaviček souborů Apply object/collection initialization preferences - Apply object/collection initialization preferences + Použít předvolby inicializace objektu/kolekce 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. + 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. @@ -227,7 +237,7 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Když se změní viditelnost pro {0}, ladicí relace nebude moct pokračovat. @@ -275,6 +285,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Převést na záznam + + Convert to record struct + Convert to record struct + + Convert to struct Převést na strukturu @@ -305,6 +320,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Vytvořit a přiřadit zbývající položky jako vlastnosti + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Když se ze záznamu odstraní parametr pozice, ladicí relace nebude moct pokračovat. + + Do not change this code. Put cleanup code in '{0}' method Neměňte tento kód. Kód pro vyčištění vložte do metody {0}. @@ -335,6 +355,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Změny provedené v projektu {0} znemožní relaci ladění pokračovat dále: {1} + + Edit and continue is not supported by the runtime. + Modul runtime nepodporuje Upravit a pokračovat. + + Error while reading file '{0}': {1} Při čtení souboru {0} došlo k chybě: {1} @@ -360,6 +385,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Příklady: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitně implementované metody záznamů musí mít názvy parametrů, které odpovídají kompilátorem vygenerovanému ekvivalentu {0}. + + Extract base class... Extrahovat základní třídu... @@ -397,7 +427,7 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Format document - Format document + Formátovat dokument @@ -495,6 +525,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Implementovat přes {0} + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Když se parametru pozice záznamu {0} implementuje jako určený pouze pro čtení, ladicí relace nebude moct pokračovat. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Když se parametru pozice záznamu {0} implementuje pomocí přístupového objektu set, ladicí relace nebude moct pokračovat. + + Incomplete \p{X} character escape Neúplné uvození znaků \p{X} @@ -550,6 +590,21 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Zavést místní + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Zavést proměnnou dotazu @@ -575,6 +630,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Převrátit podmínku + + Making a method an iterator will prevent the debug session from continuing. + Převedení metody na iterátor zabrání v pokračování relace ladění. + + + + Making a method 'async' will prevent the debug session from continuing. + Převedení metody na asynchronní zabrání v pokračování relace ladění. + + malformed chybný formát @@ -620,11 +685,6 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Chybí řídicí znak This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Pokud se změní {0} obsahující výraz switch, relace ladění nebude moct pokračovat. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Když se upraví tělo funkce {0}, ladicí relace nebude moct pokračovat, protože tělo má příliš mnoho příkazů. @@ -682,7 +742,7 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Operators - Operators + Operátory @@ -1867,12 +1927,12 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Remove unnecessary casts - Remove unnecessary casts + Odebrat nepotřebná přetypování Remove unused variables - Remove unused variables + Odebrat nepoužívané proměnné @@ -1892,7 +1952,7 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Sort accessibility modifiers - Sort accessibility modifiers + Seřadit modifikátory dostupnosti @@ -2122,7 +2182,7 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Pro přepínání kontextů můžete použít navigační panel. @@ -2313,12 +2373,12 @@ Ačkoli je možné tuto část sekundy hodnoty času zobrazit, nemusí tato hodn If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - Specifikátor vlastního formátu f představuje platné číslici zlomku sekund. To znamená, že v hodnotě data a času představuje desetiny sekundy. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Pokud se specifikátor formátu f použije bez jiných specifikátorů formátu, interpretuje se jako standardní specifikátor formátu data a času f. +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Když specifikátor formátu f použijete v rámci řetězce formátu, který se předává metodám ParseExact, TryParseExact, ParseExact nebo TryParseExact, počet specifikátorů formátu f určuje počet platných číslic zlomku sekundy, který musí být k dispozici pro úspěšné parsování řetězce. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + code @@ -2527,6 +2592,16 @@ Specifikátor standardního formátu f představuje kombinaci vzorů dlouhého d in Source (atribut) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date dlouhé datum @@ -2549,7 +2624,7 @@ Specifikátor standardního formátu f představuje kombinaci vzorů dlouhého d {0} '{1}' - {0} '{1}' + {0} {1} e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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í. + Když se odstraní {0}, ladicí relace nebude moct pokračovat. @@ -3311,7 +3386,7 @@ 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í. + Když se odebere {0}, které obsahuje aktivní příkaz, ladicí relace nebude moct pokračovat. @@ -3437,7 +3512,7 @@ Chcete pokračovat? Not Available ⚠ - Není k dispozici + Není k dispozici ⚠ @@ -3707,7 +3782,7 @@ Když se tento specifikátor standardního formátu použije, operace formátov static constructor - static constructor + statický konstruktor diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 43d654a8f85a7..5c89e609d8198 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (abgekürzt) @@ -92,7 +97,7 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d 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. + Durch Hinzufügen von "{0}" in einem generischen Typ wird die Fortsetzung der Debugsitzung verhindert. @@ -105,6 +110,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Das Hinzufügen von "{0}" zu einer Schnittstelle verhindert das Fortsetzen der Debugsitzung. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Das Hinzufügen eines Positionsparameters zu einem Datensatz verhindert die Fortsetzung der Debugsitzung. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Das Hinzufügen einer Methode mit einem expliziten Schnittstellenbezeichner verhindert das Fortsetzen der Debugsitzung. @@ -132,17 +142,17 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Apply file header preferences - Apply file header preferences + Dateiheadereinstellungen anwenden Apply object/collection initialization preferences - Apply object/collection initialization preferences + Einstellungen zur Objekt-/Sammlungsinitialisierung anwenden 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. + 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. @@ -227,7 +237,7 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Durch Änderungen an der Sichtbarkeit von "{0}" wird die Fortsetzung der Debugsitzung verhindert. @@ -275,6 +285,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d In Datensatz konvertieren + + Convert to record struct + Convert to record struct + + Convert to struct In Struktur konvertieren @@ -305,6 +320,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Verbleibende Elemente als Eigenschaften erstellen und zuweisen + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Das Löschen eines Positionsparameters aus einem Datensatz verhindert die Fortsetzung der Debugsitzung. + + Do not change this code. Put cleanup code in '{0}' method Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "{0}" ein. @@ -335,6 +355,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Die im Projekt "{0}" vorgenommenen Änderungen verhindern, dass die Debugsitzung fortgesetzt wird: {1} + + Edit and continue is not supported by the runtime. + "Bearbeiten und fortfahren" wird von der Runtime nicht unterstützt. + + Error while reading file '{0}': {1} Fehler beim Lesen der Datei "{0}": {1} @@ -360,6 +385,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Beispiele: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explizit implementierte Methoden von Datensätzen müssen Parameternamen aufweisen, die mit dem vom Compiler generierten Äquivalent "{0}" übereinstimmen. + + Extract base class... Basisklasse extrahieren... @@ -397,7 +427,7 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Format document - Format document + Dokument formatieren @@ -495,6 +525,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Über "{0}" implementieren + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Das Implementieren eines Datensatzpositionsparameters "{0}" als schreibgeschützt verhindert die Fortsetzung der Debugsitzung. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Das Implementieren eines Datensatzpositionsparameters "{0}" mit einer festgelegten Zugriffsmethode verhindert die Fortsetzung der Debugsitzung. + + Incomplete \p{X} character escape Unvollständiges \p{X}-Escapezeichen. @@ -550,6 +590,21 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Lokale Variable einführen + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Abfragevariable bereitstellen @@ -575,6 +630,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Bedingten Operator umkehren + + Making a method an iterator will prevent the debug session from continuing. + Durch Festlegen einer Methode als Iterator wird das Fortsetzen der Debugsitzung verhindert. + + + + Making a method 'async' will prevent the debug session from continuing. + Durch Festlegen einer Methode als asynchron wird das Fortsetzen der Debugsitzung verhindert. + + malformed fehlerhaft @@ -620,11 +685,6 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Fehlendes Steuerzeichen This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Nach dem Ändern von "{0}" mit Schalterausdruck kann die Debugsitzung nicht mehr fortgesetzt werden. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Durch Ändern des Texts von "{0}" wird die Fortsetzung der Debugsitzung verhindert, weil der Text zu viele Anweisungen enthält. @@ -682,7 +742,7 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Operators - Operators + Operatoren @@ -1867,12 +1927,12 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Remove unnecessary casts - Remove unnecessary casts + Nicht erforderliche Umwandlungen entfernen Remove unused variables - Remove unused variables + Nicht verwendete Variablen entfernen @@ -1892,7 +1952,7 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Sort accessibility modifiers - Sort accessibility modifiers + Zugriffsmodifizierer sortieren @@ -2122,7 +2182,7 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Sie können die Navigationsleiste verwenden, um den Kontext zu wechseln. @@ -2313,12 +2373,12 @@ Obwohl die Zehntausendstel der Sekundenkomponente eines Uhrzeitwerts angezeigt w If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - Der benutzerdefinierte Formatbezeichner "f" repräsentiert die signifikanteste Stelle des Sekundenbruchteils, d. h., er stellt die Zehntelsekunden in einem Datums- und Uhrzeitwert dar. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Bei Verwendung des Formatbezeichners "f" ohne weitere benutzerdefinierte Formatbezeichner wird er als Standardformatbezeichner "f" für Datum und Uhrzeit interpretiert. +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Bei Verwendung des Formatbezeichners "f" als Teil einer Formatzeichenfolge zur Übergabe an die ParseExact-, TryParseExact-, ParseExact- oder TryParseExact-Methode gibt die f-Formatbezeichneranzahl die Anzahl der signifikantesten Stellen des Sekundenbruchteils an, die vorhanden sein müssen, um die Zeichenfolge erfolgreich zu analysieren. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + Code @@ -2527,6 +2592,16 @@ Der Standardformatbezeichner "f" repräsentiert eine Kombination aus den Mustern in Quelle (Attribut) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date Langes Datumsformat @@ -2549,7 +2624,7 @@ Der Standardformatbezeichner "f" repräsentiert eine Kombination aus den Mustern {0} '{1}' - {0} '{1}' + {0} "{1}" e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + Durch Löschen von "{0}" wird die Fortsetzung der Debugsitzung verhindert. @@ -3311,7 +3386,7 @@ 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. + Durch Entfernen von "{0}" mit darin enthaltener aktiver Anweisung wird die Fortsetzung der Debugsitzung verhindert. @@ -3437,7 +3512,7 @@ Möchten Sie fortfahren? Not Available ⚠ - Nicht verfügbar + Nicht verfügbar ⚠ @@ -3707,7 +3782,7 @@ Bei Verwendung dieses Standardformatbezeichners wird zur Formatierung oder Analy static constructor - static constructor + statischer Konstruktor diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index ccdae4720dfb6..e6d5265fd14b4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) a.m./p.m. (abreviado) @@ -92,7 +97,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa 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. + Agregar "{0}" a un tipo genérico impedirá que continúe la sesión de depuración. @@ -105,6 +110,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Agregar '{0}' en una interfaz impedirá que continúe la sesión de depuración. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Al agregar un parámetro posicional a un registro impedirá que continúe la sesión de depuración. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Agregar un método con un especificador de interfaz explícita evitará que la sesión de depuración continúe. @@ -132,17 +142,17 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Apply file header preferences - Apply file header preferences + Aplicar preferencias de encabezado de archivo Apply object/collection initialization preferences - Apply object/collection initialization preferences + Aplicar preferencias de inicialización de objetos o colecciones 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. + 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. @@ -187,7 +197,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa TODO - TAREA + TODO "TODO" is an indication that there is work still to be done. @@ -227,7 +237,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Si se cambia la visibilidad de {0}, la sesión de depuración no podrá continuar. @@ -275,6 +285,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Convertir en registro + + Convert to record struct + Convert to record struct + + Convert to struct Convertir a struct @@ -305,6 +320,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Crear y asignar el resto como propiedades + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Eliminar un parámetro posicional desde un registro impedirá que continúe la sesión de depuración. + + Do not change this code. Put cleanup code in '{0}' method No cambie este código. Coloque el código de limpieza en el método "{0}". @@ -335,6 +355,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Los cambios realizados en el proyecto "{0}" impedirán que continúe la sesión de depuración: {1} + + Edit and continue is not supported by the runtime. + El runtime no admite editar y continuar. + + Error while reading file '{0}': {1} Error al leer el archivo "{0}": {1} @@ -360,6 +385,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Ejemplos: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Los métodos de registros implementados explícitamente deben tener nombres de parámetro que coincidan con el equivalente generado por el programa "{0}" + + Extract base class... Extraer clase base... @@ -397,7 +427,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Format document - Format document + Dar formato al documento @@ -495,6 +525,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Implementar a través de "{0}" + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Al implementar un parámetro posicional de registro "{0}" como de solo lectura impedirá que continúe la sesión de depuración, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Al implementar un parámetro posicional de registro "{0}" con un accessor de conjunto impedirá que continúe la sesión de depuración. + + Incomplete \p{X} character escape Escape de carácter incompleto \p{X} @@ -550,6 +590,21 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Introducir local + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Introducir la variable de consulta @@ -575,6 +630,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Invertir condicional + + Making a method an iterator will prevent the debug session from continuing. + Convertir un método en un iterador impedirá que continúe la sesión de depuración. + + + + Making a method 'async' will prevent the debug session from continuing. + Convertir un método en "asincrónico" impedirá que continúe la sesión de depuración. + + malformed con formato incorrecto @@ -620,11 +685,6 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Falta de carácter de control This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Si se modifica "{0}", que contiene una expresión switch, la sesión de depuración no podrá continuar. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Si se modifica el cuerpo de "{0}", se impedirá que continúe la sesión de depuración porque el cuerpo tiene demasiadas instrucciones. @@ -632,7 +692,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Modifying the body of '{0}' will prevent the debug session from continuing due to internal error: {1} - Si se modifica el cuerpo de "{0}", se impedirá que continúe la sesión de depuración debido a un error interno: {1}. + Si se modifica el cuerpo de "{0}", se impedirá que continúe la sesión de depuración debido a un error interno: {1} {1} is a multi-line exception message including a stacktrace. Place it at the end of the message and don’t add any punctation after or around {1} @@ -662,7 +722,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Modifying source file '{0}' will prevent the debug session from continuing due to internal error: {1} - Si se modifica el archivo de código fuente "{0}", se impedirá que continúe la sesión de depuración debido a un error interno: {1}. + Si se modifica el archivo de código fuente "{0}", se impedirá que continúe la sesión de depuración debido a un error interno: {1} {1} is a multi-line exception message including a stacktrace. Place it at the end of the message and don’t add any punctation after or around {1} @@ -682,7 +742,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Operators - Operators + Operadores @@ -1867,12 +1927,12 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Remove unnecessary casts - Remove unnecessary casts + Quitar conversiones innecesarias Remove unused variables - Remove unused variables + Quitar variables no utilizadas @@ -1892,7 +1952,7 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Sort accessibility modifiers - Sort accessibility modifiers + Ordenar modificadores de accesibilidad @@ -2122,7 +2182,7 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Puede usar la barra de navegación para cambiar contextos. @@ -2313,12 +2373,12 @@ Aunque es posible mostrar la diezmilésima parte de un segundo de un valor de ho If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - El especificador de formato personalizado "f" representa el dígito más significativo de la fracción de segundos, es decir, las décimas de segundo de un valor de fecha y hora. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Si el especificador de formato "f" se usa sin otros especificadores de formato, se interpreta como el especificador de formato de fecha y hora estándar "f". +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Cuando se usan especificadores de formato "f" como parte de una cadena de formato proporcionada al método ParseExact, TryParseExact, ParseExact o TryParseExact, el número de especificadores de formato "f" indica el número de dígitos más significativos de la fracción de segundos que deben estar presentes para analizar correctamente la cadena. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + código @@ -2527,6 +2592,16 @@ El especificador de formato estándar "f" representa una combinación de los pat en el origen (atributo) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date fecha larga @@ -2549,7 +2624,7 @@ El especificador de formato estándar "f" representa una combinación de los pat {0} '{1}' - {0} '{1}' + {0} "{1}" e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + Eliminar "{0}" impedirá que continúe la sesión de depuración. @@ -3311,7 +3386,7 @@ 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. + Quitar "{0}", que contiene una instrucción activa, impedirá que continúe la sesión de depuración. @@ -3437,7 +3512,7 @@ Do you want to continue? Not Available ⚠ - No disponible + No disponible ⚠ @@ -3707,7 +3782,7 @@ Cuando se usa este especificador de formato estándar, la operación de formato static constructor - static constructor + constructor estático diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index fa306e3947455..058798e286f84 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (abrégé) @@ -92,7 +97,7 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai 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. + L'ajout de '{0}' à un type générique empêche la session de débogage de se poursuivre. @@ -105,6 +110,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai L'ajout de '{0}' à une interface empêche la session de débogage de se poursuivre. + + Adding a positional parameter to a record will prevent the debug session from continuing. + L’ajout d’un paramètre positionnel à un enregistrement empêche la session de débogage de se poursuivre. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. L'ajout d'une méthode avec un spécificateur d'interface explicite empêche la session de débogage de continuer. @@ -132,17 +142,17 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Apply file header preferences - Apply file header preferences + Appliquer les préférences d'en-tête de fichier Apply object/collection initialization preferences - Apply object/collection initialization preferences + Appliquer les préférences d'initialisation des objets/collections 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. + 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. @@ -227,7 +237,7 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Le changement de visibilité de {0} empêche la session de débogage de se poursuivre. @@ -275,6 +285,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Convertir en enregistrement + + Convert to record struct + Convert to record struct + + Convert to struct Convertir en struct @@ -305,6 +320,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Créer et affecter ce qui reste en tant que propriétés + + Deleting a positional parameter from a record will prevent the debug session from continuing. + La suppression d’un paramètre positionnel d’un enregistrement empêche la session de débogage de se poursuivre. + + Do not change this code. Put cleanup code in '{0}' method Ne changez pas ce code. Placez le code de nettoyage dans la méthode '{0}' @@ -335,6 +355,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Les changements apportés au projet '{0}' empêchent la session de débogage de se poursuivre : {1} + + Edit and continue is not supported by the runtime. + Modifier et continuer n’est pas pris en charge par le Runtime. + + Error while reading file '{0}': {1} Erreur durant la lecture du fichier '{0}' : {1} @@ -360,6 +385,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Exemples : Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Les méthodes d'enregistrement implémentées explicitement doivent avoir des noms de paramètres qui correspondent à l'équivalent « {0} » généré par le compilateur. + + Extract base class... Extraire la classe de base... @@ -397,7 +427,7 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Format document - Format document + Mettre en forme le document @@ -495,6 +525,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Implémenter via '{0}' + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + L'implémentation d'un paramètre positionnel d'enregistrement « {0} » en lecture seule empêchera la session de débogage de se poursuivre. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + L'implémentation d'un paramètre positionnel d'enregistrement « {0} » avec un accesseur d’ensemble empêchera la session de débogage de se poursuivre. + + Incomplete \p{X} character escape Caractère d'échappement \p{X} incomplet @@ -550,6 +590,21 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Introduire un élément local + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Introduire la variable de requête @@ -575,6 +630,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Inverser un élément conditionnel + + Making a method an iterator will prevent the debug session from continuing. + Faire d'une méthode un itérateur empêche la session de débogage de se poursuivre. + + + + Making a method 'async' will prevent the debug session from continuing. + Le fait de rendre une méthode « asynchrone » empêche la session de débogage de se poursuivre. + + malformed incorrecte @@ -620,11 +685,6 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Caractère de contrôle manquant This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - La modification de '{0}', qui contient une expression de switch, va empêcher la session de débogage de se poursuivre. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Le fait de modifier le corps de '{0}' empêche la session de débogage de se poursuivre, car le corps contient trop d'instructions. @@ -682,7 +742,7 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Operators - Operators + Opérateurs @@ -1867,12 +1927,12 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Remove unnecessary casts - Remove unnecessary casts + Supprimer les casts inutiles Remove unused variables - Remove unused variables + Supprimer les variables inutilisées @@ -1892,7 +1952,7 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Sort accessibility modifiers - Sort accessibility modifiers + Trier les modificateurs d'accessibilité @@ -2122,7 +2182,7 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Vous pouvez utiliser la barre de navigation pour changer de contexte. @@ -2313,12 +2373,12 @@ Bien qu'il soit possible d'afficher les dix millièmes d'un deuxième composant If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - Le spécificateur de format personnalisé "f" représente le chiffre le plus significatif de la fraction de seconde. En d'autres termes, il représente les dixièmes de seconde d'une valeur de date et d'heure. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Si le spécificateur de format "f" est utilisé sans autres spécificateurs de format, il est interprété en tant que spécificateur de format de date et d'heure standard : "f". +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Quand vous utilisez les spécificateurs de format "f" dans le cadre d'une chaîne de format fournie à la méthode ParseExact, TryParseExact, ParseExact ou TryParseExact, le nombre de spécificateurs de format "f" indique le nombre de chiffres les plus significatifs de la fraction de seconde qui doivent être présents pour permettre une analyse correcte de la chaîne. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + code @@ -2527,6 +2592,16 @@ Le spécificateur de format standard "f" représente une combinaison des modèle dans la source (attribut) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date date longue @@ -2549,7 +2624,7 @@ Le spécificateur de format standard "f" représente une combinaison des modèle {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + La suppression de {0} empêche la session de débogage de se poursuivre. @@ -3311,7 +3386,7 @@ 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. + La suppression de {0} contenant une instruction active empêche la session de débogage de se poursuivre. @@ -3437,7 +3512,7 @@ Voulez-vous continuer ? Not Available ⚠ - Non disponible + Non disponible ⚠ @@ -3707,7 +3782,7 @@ Quand ce spécificateur de format standard est utilisé, l'opération qui consis static constructor - static constructor + constructeur statique diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 0b536a2a25cd9..95ddae6c1a32f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (abbreviato) @@ -92,7 +97,7 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa 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. + Se si aggiunge '{0}' in un tipo generico, la sessione di debug non potrà continuare. @@ -105,6 +110,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si aggiunge '{0}' in un'interfaccia, la sessione di debug non potrà continuare. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Se si aggiunge un parametro posizionale a un record, la sessione di debug non potrà continuare. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Se si aggiunge un metodo con identificatore di interfaccia esplicita, la sessione di debug non potrà continuare. @@ -132,17 +142,17 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Apply file header preferences - Apply file header preferences + Applica le preferenze relative alle intestazioni di file Apply object/collection initialization preferences - Apply object/collection initialization preferences + Applica le preferenze di inizializzazione di oggetti/raccolte 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. + Un'istruzione attiva è stata rimossa dal metodo originale. Annullare le modifiche per continuare oppure riavviare la sessione di debug. @@ -227,7 +237,7 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Se si modifica la visibilità di {0}, la sessione di debug non potrà continuare. @@ -275,6 +285,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Converti in record + + Convert to record struct + Convert to record struct + + Convert to struct Converti in struct @@ -305,6 +320,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Crea e assegna rimanenti come proprietà + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Se si elimina un parametro posizionale da un record, la sessione di debug non potrà continuare. + + Do not change this code. Put cleanup code in '{0}' method Non modificare questo codice. Inserire il codice di pulizia nel metodo '{0}' @@ -335,6 +355,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Le modifiche apportate al progetto '{0}' impediranno la continuazione della sessione di debug: {1} + + Edit and continue is not supported by the runtime. + La funzione Modifica e continuazione non è supportata dal runtime. + + Error while reading file '{0}': {1} Si è verificato un errore durante la lettura del file '{0}': {1} @@ -360,6 +385,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Esempi: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + I metodi dei record implementati in modo esplicito devono avere nomi di parametro corrispondenti all'equivalente generato dal compilatore '{0}' + + Extract base class... Estrai classe di base... @@ -397,7 +427,7 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Format document - Format document + Formatta documento @@ -495,6 +525,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Implementa tramite '{0}' + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Se si implementa un parametro posizionale del record '{0}' come di sola lettura, la sessione di debug non potrà continuare. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Se si implementa un parametro posizionale del record '{0}' con una funzione di accesso set, la sessione di debug non potrà continuare. + + Incomplete \p{X} character escape Sequenza di caratteri di escape \p{X} incompleta @@ -550,6 +590,21 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Introduci variabile locale + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Introduci la variabile di query @@ -575,6 +630,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Inverti espressione condizionale + + Making a method an iterator will prevent the debug session from continuing. + Se si imposta un metodo come iteratore, la sessione di debug non proseguirà. + + + + Making a method 'async' will prevent the debug session from continuing. + Se si rende un metodo asincrono, la sessione di debug non proseguirà. + + malformed non valido @@ -620,11 +685,6 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Carattere di controllo mancante This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Se si modifica '{0}' che contiene un'espressione switch, la sessione di debug non potrà continuare. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Se si modifica il corpo di '{0}', la sessione di debug non potrà continuare perché il corpo contiene troppe istruzioni. @@ -682,7 +742,7 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Operators - Operators + Operatori @@ -1867,12 +1927,12 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Remove unnecessary casts - Remove unnecessary casts + Rimuovi i cast non necessari Remove unused variables - Remove unused variables + Rimuovi le variabili non usate @@ -1892,7 +1952,7 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Sort accessibility modifiers - Sort accessibility modifiers + Ordina i modificatori di accessibilità @@ -2122,7 +2182,7 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Per cambiare contesto, si può usare la barra di spostamento. @@ -2313,12 +2373,12 @@ Anche se è possibile visualizzare i decimillesimi di un componente relativo ai If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - L'identificatore di formato personalizzato "f" rappresenta la cifra più significativa della frazione di secondi, ovvero i decimi di secondo in un valore di data e ora. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Se l'identificatore di formato "f" viene usato senza altri identificatori di formato, viene interpretato come l'identificatore di formato di data e ora standard "f". +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Quando si usano identificatori di formato "f" come parte di una stringa di formato fornita al metodo ParseExact, TryParseExact, ParseExact o TryParseExact, il numero di identificatori di formato "f" indica il numero di cifre più significative della frazione di secondi che deve essere presente per analizzare correttamente la stringa. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + codice @@ -2527,6 +2592,16 @@ L'identificatore di formato standard "f" rappresenta una combinazione degli sche nell'origine (attributo) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date Data estesa @@ -2549,7 +2624,7 @@ L'identificatore di formato standard "f" rappresenta una combinazione degli sche {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + Se si elimina {0}, la sessione di debug non potrà continuare. @@ -3311,7 +3386,7 @@ 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. + Se si rimuove {0}, che contiene un'istruzione attiva, la sessione di debug non potrà continuare. @@ -3437,7 +3512,7 @@ Continuare? Not Available ⚠ - Non disponibile + Non disponibile ⚠ @@ -3707,7 +3782,7 @@ Quando si usa questo identificatore di formato standard, la formattazione o l'op static constructor - static constructor + costruttore statico diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 8b5db864298a7..42af4b180c2ae 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (短縮) @@ -92,7 +97,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 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. + '{0}' をジェネリック型に追加すると、デバッグ セッションは続行されません。 @@ -105,6 +110,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' をインターフェイスに追加すると、デバッグ セッションは続行されません。 + + Adding a positional parameter to a record will prevent the debug session from continuing. + レコードに位置パラメーターを追加すると、デバッグ セッションが続行されなくなります。 + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 明示的なインターフェイス指定子が含まれるメソッドを追加すると、デバッグ セッションを続行できなくなります。 @@ -132,17 +142,17 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Apply file header preferences - Apply file header preferences + ファイル ヘッダーの基本設定を適用する Apply object/collection initialization preferences - Apply object/collection initialization preferences + オブジェクト/コレクションの初期化の基本設定を適用します 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. + アクティブ ステートメントは元のメソッドから削除されました。変更を元に戻して続行するか、またはデバッグ セッションを再開してください。 @@ -227,7 +237,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + {0} の表示範囲を変更すると、デバッグ セッションを続行できなくなります。 @@ -275,6 +285,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma レコードへの変換 + + Convert to record struct + Convert to record struct + + Convert to struct 構造体に変換 @@ -305,6 +320,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 残りをプロパティとして作成して割り当てる + + Deleting a positional parameter from a record will prevent the debug session from continuing. + レコードから位置パラメーターを削除すると、デバッグ セッションが続行されなくなります。 + + Do not change this code. Put cleanup code in '{0}' method このコードを変更しないでください。クリーンアップ コードを '{0}' メソッドに記述します @@ -335,6 +355,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma プロジェクト '{0}' で変更を行うと、デバッグ セッションは続行されません。{1} + + Edit and continue is not supported by the runtime. + 編集して続行は、ランタイムでサポートされていません。 + + Error while reading file '{0}': {1} ファイル {0}' の読み取り中にエラーが発生しました: {1} @@ -360,6 +385,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 例: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + 明示的に実装されたレコードのメソッドには、コンパイラ生成と同等の '{0}' と一致するパラメーター名が必要です + + Extract base class... 基底クラスの抽出... @@ -397,7 +427,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Format document - Format document + ドキュメントのフォーマット @@ -495,6 +525,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' を通じて実装します + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Set アクセサーを含むレコード位置パラメーター '{0}' を読み取り専用として実装すると、デバッグ セッションは続行されなくなります。 + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Set アクセサーを含むレコード位置パラメーター '{0}' を実装すると、デバッグ セッションは続行されなくなります。 + + Incomplete \p{X} character escape 不完全な \p{X} 文字エスケープです @@ -550,6 +590,21 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma ローカルを導入します + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable クエリ変数を導入します @@ -575,6 +630,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 条件を反転します + + Making a method an iterator will prevent the debug session from continuing. + メソッドを反復子にするとデバッグ セッションは続行されません。 + + + + Making a method 'async' will prevent the debug session from continuing. + メソッドを '非同期' にするとデバッグ セッションは続行されません。 + + malformed 不正な形式 @@ -620,11 +685,6 @@ 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: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - switch 式を含む '{0}' を変更すると、デバッグ セッションは続行されません。 - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. '{0}' の本体を変更すると、本体のステートメントが多くなりすぎるため、デバッグ セッションを続行できなくなります。 @@ -682,7 +742,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Operators - Operators + 演算子 @@ -1867,12 +1927,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Remove unnecessary casts - Remove unnecessary casts + 不要なキャストを削除する Remove unused variables - Remove unused variables + 未使用の変数を削除する @@ -1892,7 +1952,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort accessibility modifiers - Sort accessibility modifiers + アクセシビリティ修飾子を並べ替える @@ -2122,7 +2182,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + ナビゲーション バーを使用してコンテキストを切り替えることができます。 @@ -2313,12 +2373,12 @@ Although it's possible to display the ten thousandths of a second component of a If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - "f" カスタム書式指定子は、秒の小数部分の有効数字最上位桁を表します。つまり、日時値の秒の小数部分を 10 分の 1 秒単位で表します。 + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -"f" 書式指定子を他の書式指定子なしで使用すると、"f" 標準日時書式指定子として解釈されます。 +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -"f" 書式指定子を、ParseExact、TryParseExact、ParseExact、または TryParseExact メソッドに指定する書式文字列の一部として使用した場合、"f" 書式指定子の数は、文字列を正常に解析するために必要な秒の小数点以下の有効桁数を示します。 - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" カスタム書式指定子 (任意の数の "H" 指定子を追加できます) は、時を 00 から 23 の数字で表します。つまり、午前 0 時からの時間をカウントする、ゼロから始まる 24 時間制で時が表されます。1 桁の時は、先行ゼロ付きで書式設定されます。 + + and update call sites directly + and update call sites directly + + code - code + コード @@ -2519,7 +2584,7 @@ The "f" standard format specifier represents a combination of the long date ("D" in {0} ({1} - {2}) - {0} 内 ({1} - {2}) + {0} ({1} - {2}) 内 @@ -2527,6 +2592,16 @@ The "f" standard format specifier represents a combination of the long date ("D" ソース内 (属性) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date 長い日付 @@ -2549,7 +2624,7 @@ The "f" standard format specifier represents a combination of the long date ("D" {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ If the "g" format specifier is used without other custom format specifiers, it's Deleting {0} will prevent the debug session from continuing. - '{0}' を削除すると、デバッグ セッションは続行されません。 + {0} を削除すると、デバッグ セッションは続行されません。 @@ -3311,7 +3386,7 @@ 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}' を削除すると、デバッグ セッションは続行されません。 + アクティブ ステートメントを含む {0} を削除すると、デバッグ セッションは続行されません。 @@ -3437,7 +3512,7 @@ Do you want to continue? Not Available ⚠ - 無効 + 使用できません ⚠ @@ -3707,7 +3782,7 @@ When this standard format specifier is used, the formatting or parsing operation static constructor - static constructor + 静的コンストラクター diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 8fc98c488b435..3d5f84fbefbaa 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM(약식) @@ -92,7 +97,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 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. + '{0}'을(를) 제네릭 형식에 추가하면 디버그 세션을 계속할 수 없습니다. @@ -105,6 +110,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 인터페이스에 '{0}'을(를) 추가하면 디버그 세션을 계속할 수 없습니다. + + Adding a positional parameter to a record will prevent the debug session from continuing. + 레코드에 위치 매개 변수를 추가하면 디버그 세션이 계속 진행되지 않습니다. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 명시적 인터페이스 지정자를 사용하여 메서드를 추가하면 디버그 세션을 계속할 수 없습니다. @@ -132,17 +142,17 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Apply file header preferences - Apply file header preferences + 파일 헤더 기본 설정 적용 Apply object/collection initialization preferences - Apply object/collection initialization preferences + 개체/컬렉션 초기화 기본 설정 적용 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. + 원래 메서드에서 활성 문이 제거되었습니다. 변경 내용을 취소하고 계속하거나 디버깅 세션을 다시 시작해야 합니다. @@ -227,7 +237,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + {0} 표시 여부를 변경하면 디버그 세션을 계속할 수 없습니다. @@ -275,6 +285,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 레코드로 변환 + + Convert to record struct + Convert to record struct + + Convert to struct 구조체로 변환 @@ -305,6 +320,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 나머지를 만들고 속성으로 할당합니다. + + Deleting a positional parameter from a record will prevent the debug session from continuing. + 레코드에서 위치 매개 변수를 삭제하면 디버그 세션이 계속 진행되지 않습니다. + + Do not change this code. Put cleanup code in '{0}' method 이 코드를 변경하지 마세요. '{0}' 메서드에 정리 코드를 입력합니다. @@ -335,6 +355,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' 프로젝트에서 수행한 변경으로 디버그 세션을 계속할 수 없습니다. {1} + + Edit and continue is not supported by the runtime. + 편집하고 계속하기는 런타임에서 지원되지 않습니다. + + Error while reading file '{0}': {1} '{0}' 파일을 읽는 동안 오류가 발생했습니다. {1} @@ -360,6 +385,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 예: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + 명시적으로 구현된 레코드 메서드에는 컴파일러에서 생성된 것과 일치하는 매개 변수 이름 '{0}'이 있어야 합니다. + + Extract base class... 기본 클래스 추출... @@ -397,7 +427,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Format document - Format document + 문서 서식 @@ -495,6 +525,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) 통해 구현 + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + 레코드 위치 매개 변수 '{0}'을(를) 읽기 전용으로 구현 하면 디버그 세션이 계속 진행되지 않습니다. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + 집합 접근자로 레코드 위치 매개 변수 '{0}'을(를) 구현하면 디버그 세션이 계속 진행되지 않습니다. + + Incomplete \p{X} character escape 불완전한 \p{X} 문자 이스케이프 @@ -550,6 +590,21 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 로컬 소개 + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable 쿼리 변수 지정 @@ -575,6 +630,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 조건 반전 + + Making a method an iterator will prevent the debug session from continuing. + 메서드를 반복자로 만들면 디버그 세션이 계속되지 않습니다. + + + + Making a method 'async' will prevent the debug session from continuing. + 'async' 메서드를 만들면 디버그 세션이 계속되지 않습니다. + + malformed 형식이 잘못되었습니다. @@ -620,11 +685,6 @@ 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: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - switch 식을 포함하는 '{0}'을(를) 수정하면 디버그 세션을 계속할 수 없습니다. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. 본문에 문이 너무 많아 '{0}'의 본문을 수정하면 디버그 세션을 계속 진행할 수 없습니다. @@ -682,7 +742,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Operators - Operators + 연산자 @@ -1867,12 +1927,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Remove unnecessary casts - Remove unnecessary casts + 불필요한 캐스트 제거 Remove unused variables - Remove unused variables + 사용하지 않는 변수 제거 @@ -1892,7 +1952,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort accessibility modifiers - Sort accessibility modifiers + 접근성 한정자 정렬 @@ -2122,7 +2182,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + 탐색 모음을 사용하여 컨텍스트를 전환할 수 있습니다. @@ -2313,12 +2373,12 @@ Although it's possible to display the ten thousandths of a second component of a If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - "f" 사용자 지정 형식 지정자는 초 부분의 가장 유효한 숫자를 나타냅니다. 즉, 날짜 및 시간 값에서 10분의 1초를 나타냅니다. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -다른 형식 지정자 없이 "f" 형식 지정자를 사용하면 "f" 표준 날짜 및 시간 형식 지정자로 해석됩니다. +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -ParseExact, TryParseExact, ParseExact 또는 TryParseExact 메서드로 제공되는 형식 문자열의 일부로 "f" 형식 지정자를 사용할 경우 "f" 형식 지정자의 개수는 초 부분에서 문자열을 성공적으로 구문 분석하려면 존재해야 하는 가장 유효한 숫자의 개수를 나타냅니다. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" 사용자 지정 형식 지정자(및 임의 개수의 추가 "H" 지정자)는 시간을 00부터 23까지의 숫자로 나타냅니다. 즉, 시간은 자정부터 경과한 시간을 계산하는 0 기반 24시간제로 표현됩니다. 한 자릿수 시간은 앞에 0이 있는 형식으로 지정됩니다. + + and update call sites directly + and update call sites directly + + code - code + 코드 @@ -2527,6 +2592,16 @@ The "f" standard format specifier represents a combination of the long date ("D" 소스(특성) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date 자세한 날짜 @@ -2549,7 +2624,7 @@ The "f" standard format specifier represents a combination of the long date ("D" {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ If the "g" format specifier is used without other custom format specifiers, it's Deleting {0} will prevent the debug session from continuing. - '{0}'을(를) 삭제하면 디버그 세션을 계속할 수 없습니다. + {0}을(를) 삭제하면 디버그 세션을 계속할 수 없습니다. @@ -3311,7 +3386,7 @@ 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}'을(를) 제거하면 디버그 세션을 계속할 수 없습니다. + 활성 문이 포함된 {0}을(를) 제거하면 디버그 세션을 계속할 수 없습니다. @@ -3437,7 +3512,7 @@ Do you want to continue? Not Available ⚠ - 사용할 수 없음 + 사용할 수 없음 ⚠ @@ -3707,7 +3782,7 @@ When this standard format specifier is used, the formatting or parsing operation static constructor - static constructor + 정적 생성자 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index c21b2d32b3247..909043114c76e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (skrót) @@ -92,7 +97,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k 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. + Dodanie elementu „{0}” do typu ogólnego uniemożliwi kontynuowanie sesji debugowania. @@ -105,6 +110,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Dodanie elementu „{0}” do interfejsu uniemożliwi kontynuowanie sesji debugowania. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Dodanie parametru pozycyjnego do rekordu uniemożliwi kontynuowanie sesji debugowania. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Dodanie metody z jawnym specyfikatorem interfejsu uniemożliwi kontynuowanie sesji debugowania. @@ -132,17 +142,17 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Apply file header preferences - Apply file header preferences + Zastosuj preferencje nagłówka pliku Apply object/collection initialization preferences - Apply object/collection initialization preferences + Zastosuj preferencje inicjowania obiektu/kolekcji 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. + Aktywna instrukcja została usunięta ze swojej oryginalnej metody. Musisz cofnąć zmiany, aby kontynuować, lub uruchomić ponownie sesję debugowania. @@ -227,7 +237,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Zmiana widoczności elementu {0} uniemożliwi kontynuowanie sesji debugowania. @@ -275,6 +285,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Konwertuj na rekord + + Convert to record struct + Convert to record struct + + Convert to struct Konwertuj na strukturę @@ -305,6 +320,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Utwórz i przypisz pozostałe jako właściwości + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Usunięcie parametru pozycyjnego z rekordu uniemożliwi kontynuowanie sesji debugowania. + + Do not change this code. Put cleanup code in '{0}' method Nie zmieniaj tego kodu. Umieść kod czyszczący w metodzie „{0}”. @@ -335,6 +355,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiany wprowadzone w projekcie „{0}” uniemożliwią kontynuowanie sesji debugowania: {1} + + Edit and continue is not supported by the runtime. + Środowisko uruchomieniowe nie obsługuje funkcji edycji i kontynuowania. + + Error while reading file '{0}': {1} Błąd podczas odczytywania pliku „{0}”: {1} @@ -360,6 +385,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Przykłady: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Jawnie zaimplementowane metody rekordów muszą mieć nazwy parametrów pasujące do wygenerowanego odpowiednika kompilatora "{0}" + + Extract base class... Wyodrębnij klasę bazową... @@ -397,7 +427,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Format document - Format document + Formatuj dokument @@ -495,6 +525,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Implementuj za pomocą elementu „{0}” + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Zaimplementowanie parametru pozycyjnego rekordu "{0}" w trybie tylko do odczytu uniemożliwi kontynuowanie sesji debugowania. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Zaimplementowanie parametru pozycyjnego "{0}" przy użyciu ustawionej metody dostępu uniemożliwi kontynuowanie sesji debugowania. + + Incomplete \p{X} character escape Niekompletna sekwencja ucieczki znaku \p{X} @@ -550,6 +590,21 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Wprowadź zmienną lokalną + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Wprowadź zmienną zapytania @@ -575,6 +630,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Odwróć warunkowe + + Making a method an iterator will prevent the debug session from continuing. + Uczynienie metody iteratorem uniemożliwi kontynuowanie sesji debugowania. + + + + Making a method 'async' will prevent the debug session from continuing. + Uczynienie metody „asynchroniczną” uniemożliwi kontynuowanie sesji debugowania. + + malformed źle sformułowane @@ -620,11 +685,6 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Brak znaku kontrolnego This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Zmodyfikowanie elementu „{0}”, który zawiera wyrażenie switch, uniemożliwi kontynuowanie sesji debugowania. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Zmodyfikowanie treści elementu „{0}” uniemożliwi kontynuowanie sesji debugowania, ponieważ treść zawiera zbyt wiele instrukcji. @@ -632,7 +692,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Modifying the body of '{0}' will prevent the debug session from continuing due to internal error: {1} - Zmodyfikowanie treści elementu „{0}” uniemożliwi kontynuowanie sesji debugowania ze względu na błąd wewnętrzny: {1}. + Zmodyfikowanie treści elementu „{0}” uniemożliwi kontynuowanie sesji debugowania ze względu na błąd wewnętrzny: {1} {1} is a multi-line exception message including a stacktrace. Place it at the end of the message and don’t add any punctation after or around {1} @@ -662,7 +722,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Modifying source file '{0}' will prevent the debug session from continuing due to internal error: {1} - Zmodyfikowanie pliku źródłowego „{0}” uniemożliwi kontynuowanie sesji debugowania ze względu na błąd wewnętrzny: {1}. + Zmodyfikowanie pliku źródłowego „{0}” uniemożliwi kontynuowanie sesji debugowania ze względu na błąd wewnętrzny: {1} {1} is a multi-line exception message including a stacktrace. Place it at the end of the message and don’t add any punctation after or around {1} @@ -682,7 +742,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Operators - Operators + Operatory @@ -1867,12 +1927,12 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Remove unnecessary casts - Remove unnecessary casts + Usuń niepotrzebne rzutowania Remove unused variables - Remove unused variables + Usuń nieużywane zmienne @@ -1892,7 +1952,7 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Sort accessibility modifiers - Sort accessibility modifiers + Sortuj modyfikatory dostępności @@ -2122,7 +2182,7 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Za pomocą paska nawigacyjnego można przełączać konteksty. @@ -2313,12 +2373,12 @@ Co prawda możliwe jest wyświetlenie składnika dziesięciotysięcznej części If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - Indywidualny specyfikator formatu „f” reprezentuje najbardziej znaczącą cyfrę ułamka sekundy; tzn. reprezentuje dziesiątą część sekundy w wartości daty i godziny. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Jeśli specyfikator formatu „f” zostanie użyty bez innych indywidualnych specyfikatorów formatu, będzie interpretowany jako standardowy specyfikator daty i godziny „f”. +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Gdy specyfikator formatu „f” jest używany jako część ciągu formatu podanego dla metody arseExact, TryParseExact, ParseExact lub TryParseExact, liczba specyfikatorów formatu „f” wskazuje liczbę najważniejszych cyfr ułamka sekund, jaka musi być reprezentowana, aby pomyślnie przeanalizować ciąg. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + kod @@ -2527,6 +2592,16 @@ Standardowy specyfikator formatu „f” reprezentuje połączenie wzorców dłu w źródle (atrybut) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date data długa @@ -2549,7 +2624,7 @@ Standardowy specyfikator formatu „f” reprezentuje połączenie wzorców dłu {0} '{1}' - {0} '{1}' + {0} „{1}” e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + Usunięcie elementu „{0}” uniemożliwi kontynuowanie sesji debugowania. @@ -3311,7 +3386,7 @@ 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. + Usunięcie elementu {0} zawierającego aktywną instrukcję uniemożliwi kontynuowanie sesji debugowania. @@ -3437,7 +3512,7 @@ Czy chcesz kontynuować? Not Available ⚠ - Niedostępne + Niedostępne ⚠ @@ -3707,7 +3782,7 @@ Gdy jest używany ten standardowy specyfikator formatu, operacja formatowania lu static constructor - static constructor + konstruktor statyczny diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index bb8dba590143d..d237ee5aa35ff 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (abreviado) @@ -92,7 +97,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess 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. + A adição de '{0}' a um tipo genérico impedirá que a sessão de depuração continue. @@ -105,6 +110,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Adicionar '{0}' a uma interface impedirá que a sessão de depuração continue. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adicionar um parâmetro de posição a um registro impedirá que a sessão de depuração continue. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Adicionar um método com um especificador explícito da interface impedirá a sessão de depuração de continuar. @@ -132,17 +142,17 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Apply file header preferences - Apply file header preferences + Aplicar as preferências de cabeçalho de arquivo Apply object/collection initialization preferences - Apply object/collection initialization preferences + Aplicar as preferências de inicialização de objeto/coleção 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. + 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. @@ -187,7 +197,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess TODO - A FAZER + TODO "TODO" is an indication that there is work still to be done. @@ -227,7 +237,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + A alteração da visibilidade de {0} impedirá que a sessão de depuração continue. @@ -275,6 +285,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Converter em Registro + + Convert to record struct + Convert to record struct + + Convert to struct Converter para struct @@ -305,6 +320,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Criar e atribuir os restantes como propriedades + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Excluir um parâmetro de posição de um registro impedirá que a sessão de depuração continue. + + Do not change this code. Put cleanup code in '{0}' method Não altere este código. Coloque o código de limpeza no método '{0}' @@ -335,6 +355,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess As alterações feitas no projeto '{0}' impedirão que a sessão de depuração continue: {1} + + Edit and continue is not supported by the runtime. + Editar e continuar não é suportado pelo tempo de execução. + + Error while reading file '{0}': {1} Erro ao ler o arquivo '{0}': {1} @@ -360,6 +385,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Exemplos: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Métodos explicitamente implementados de registros devem ter nomes de parâmetro que correspondem ao compilador gerado '{0}' equivalente + + Extract base class... Extrair a classe base... @@ -397,7 +427,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Format document - Format document + Formatar o documento @@ -495,6 +525,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Implementar por meio de '{0}' + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementar um parâmetro de posição de registro '{0}' como somente leitura impedirá que a sessão de depuração continue + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementar um parâmetro de posição de registro '{0}' com um accessor definido impedirá que a sessão de depuração continue. + + Incomplete \p{X} character escape Escape de caractere incompleto \p{X} @@ -550,6 +590,21 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Introduzir o local + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Introduzir a variável de consulta @@ -575,6 +630,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Inverter condicional + + Making a method an iterator will prevent the debug session from continuing. + Fazer de um método um iterador impedirá que a sessão de depuração continue. + + + + Making a method 'async' will prevent the debug session from continuing. + Fazer um método 'assíncrono' impedirá que a sessão de depuração continue. + + malformed malformado @@ -620,11 +685,6 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Caractere de controle ausente This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - A modificação de '{0}' contendo uma expressão de switch impedirá a continuação da sessão de depuração. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. A modificação do corpo de '{0}' impedirá que a sessão de depuração continue porque o corpo tem muitas instruções. @@ -682,7 +742,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Operators - Operators + Operadores @@ -1867,12 +1927,12 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Remove unnecessary casts - Remove unnecessary casts + Remover conversões desnecessárias Remove unused variables - Remove unused variables + Remover variáveis não utilizadas @@ -1892,7 +1952,7 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Sort accessibility modifiers - Sort accessibility modifiers + Classificar modificadores de acessibilidade @@ -1917,12 +1977,12 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas TODO: free unmanaged resources (unmanaged objects) and override finalizer - Tarefa pendente: liberar recursos não gerenciados (objetos não gerenciados) e substituir o finalizador + TODO: free unmanaged resources (unmanaged objects) and override finalizer TODO: override finalizer only if '{0}' has code to free unmanaged resources - Tarefa pendente: substituir o finalizador somente se '{0}' tiver o código para liberar recursos não gerenciados + TODO: override finalizer only if '{0}' has code to free unmanaged resources @@ -2122,7 +2182,7 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Você pode usar a barra de navegação para mudar de contexto. @@ -2313,12 +2373,12 @@ Embora seja possível exibir os dez milésimos de um componente de segundo de um If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - O especificador de formato personalizado "f" representa o dígito mais significativo da fração de segundos; ou seja, ele representa os décimos de segundo em um valor de data e hora. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Se o especificador de formato "f" for usado sem outros especificadores de formato, ele será interpretado como o especificador de formato padrão de data e hora "f". +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -Quando você usa especificadores de formato "f" como parte de uma cadeia de formato fornecida para o método ParseExact, TryParseExact, ParseExact ou TryParseExact, o número de especificadores de formato "f" indica o número de dígitos significativos da fração de segundos que devem estar presentes para analisar a cadeia de caracteres com êxito. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + código @@ -2527,6 +2592,16 @@ O especificador de formato padrão "f" representa uma combinação de padrões d na Origem (atributo) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date data completa @@ -2549,7 +2624,7 @@ O especificador de formato padrão "f" representa uma combinação de padrões d {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + A exclusão de {0} impedirá que a sessão de depuração continue. @@ -3311,7 +3386,7 @@ 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. + A remoção de {0} que contém uma instrução ativa impedirá que a sessão de depuração continue. @@ -3437,7 +3512,7 @@ Deseja continuar? Not Available ⚠ - Não Disponível + Não Disponível ⚠ @@ -3532,12 +3607,12 @@ Deseja continuar? TODO: dispose managed state (managed objects) - Tarefa pendente: descartar o estado gerenciado (objetos gerenciados) + TODO: dispose managed state (managed objects) TODO: set large fields to null - Tarefa pendente: definir campos grandes como nulos + TODO: set large fields to null @@ -3707,7 +3782,7 @@ Quando esse especificador de formato padrão é usado, a operação de análise static constructor - static constructor + construtor estático diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 274ccab78e18b..f29021df5743f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (сокращенно) @@ -92,7 +97,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 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. + Добавление "{0}" в универсальный тип сделает продолжение сеанса отладки невозможным. @@ -105,6 +110,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Добавление "{0}" в интерфейс сделает продолжение сеанса отладки невозможным. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Добавление позиционного параметра в запись остановит сеанс отладки. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. При добавлении метода с явным спецификатором интерфейса вы не сможете продолжить сеанс отладки. @@ -132,17 +142,17 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Apply file header preferences - Apply file header preferences + Применить параметры заголовка файла Apply object/collection initialization preferences - Apply object/collection initialization preferences + Применять предпочтения для инициализации объекта или коллекции 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. + Активный оператор был удален из исходного метода. Чтобы продолжить выполнение сеанса отладки, необходимо отменить изменения или перезапустить сеанс. @@ -187,7 +197,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma TODO - Список задач + TODO "TODO" is an indication that there is work still to be done. @@ -227,7 +237,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + Изменение видимости {0} сделает продолжение сеанса отладки невозможным. @@ -275,6 +285,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Преобразовать в запись + + Convert to record struct + Convert to record struct + + Convert to struct Преобразовать в структуру @@ -305,6 +320,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Создать и назначить оставшиеся как свойства + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Удаление позиционного параметра из записи остановит сеанс отладки. + + Do not change this code. Put cleanup code in '{0}' method Не изменяйте этот код. Разместите код очистки в методе "{0}". @@ -335,6 +355,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Изменения, внесенные в проект "{0}", препятствуют продолжению сеанса отладки: {1} + + Edit and continue is not supported by the runtime. + Параметр "изменить и продолжить" не поддерживается средой выполнения. + + Error while reading file '{0}': {1} Ошибка при чтении файла "{0}": {1} @@ -360,6 +385,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Примеры: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Явно реализованные методы записей должны иметь имена параметров, соответствующие компилятору, созданному эквивалентом '{0}' + + Extract base class... Извлечь базовый класс... @@ -397,7 +427,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Format document - Format document + Форматировать документ @@ -495,6 +525,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Реализовать через "{0}" + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Реализация позиционного параметра записи "{0}" только для чтения остановит сеанс отладки. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Реализация позиционного параметра записи "{0}" с помощью метода доступа set остановит сеанс отладки. + + Incomplete \p{X} character escape Незавершенная escape-последовательность \p{X} @@ -550,6 +590,21 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Добавить локальный оператор + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Добавить переменную запроса @@ -575,6 +630,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Инвертировать условный оператор + + Making a method an iterator will prevent the debug session from continuing. + Если сделать метод итератором, продолжение сеанса отладки будет невозможным. + + + + Making a method 'async' will prevent the debug session from continuing. + Если сделать метод асинхронным, продолжение сеанса отладки будет невозможным. + + malformed Ошибка в регулярном выражении @@ -620,11 +685,6 @@ 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: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Изменение "{0}", которое содержит выражение switch, сделает продолжение сеанса отладки невозможным. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. Изменение тела "{0}" помешает продолжению сеанса отладки из-за того, что тело содержит слишком много операторов. @@ -682,7 +742,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Operators - Operators + Операторы @@ -1867,12 +1927,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Remove unnecessary casts - Remove unnecessary casts + Удалить ненужные приведения Remove unused variables - Remove unused variables + Удалить неиспользуемые переменные @@ -1892,7 +1952,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort accessibility modifiers - Sort accessibility modifiers + Сортировать модификаторы доступности @@ -2122,7 +2182,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Для переключения контекстов можно использовать панель навигации. @@ -2313,12 +2373,12 @@ Although it's possible to display the ten thousandths of a second component of a If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - Описатель пользовательского формата "f" представляет наиболее значащую цифру в дробной части секунды, то есть представляет десятые доли секунды в значении даты и времени. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -Если описатель формата "f" используется без других описателей формата, он интерпретируется как описатель стандартного формата даты и времени "f". +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -При использовании описателей формата "f" в качестве части строки формата, передаваемой методу ParseExact, TryParseExact, ParseExact или TryParseExact, число описателей формата "f" указывает количество наиболее значащих цифр в дробной части секунды, которые должны присутствовать для успешного анализа строки. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ If the "H" format specifier is used without other custom format specifiers, it's Описатель пользовательского формата "HH" (плюс любое число дополнительных описателей "H") представляет час в виде числа от 00 до 23, то есть час представлен в начинающемся с нуля 24-часовом формате, отсчитывающем часы с полуночи. Значение часа из одной цифры форматируется с начальным нулем. + + and update call sites directly + and update call sites directly + + code - code + код @@ -2527,6 +2592,16 @@ The "f" standard format specifier represents a combination of the long date ("D" в источнике (атрибут) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date Длинный формат даты @@ -2549,7 +2624,7 @@ The "f" standard format specifier represents a combination of the long date ("D" {0} '{1}' - {0} '{1}' + {0} "{1}" e.g. "method 'M'" @@ -3211,7 +3286,7 @@ If the "g" format specifier is used without other custom format specifiers, it's Deleting {0} will prevent the debug session from continuing. - Удаление "{0}" сделает продолжение сеанса отладки невозможным. + Удаление {0} сделает продолжение сеанса отладки невозможным. @@ -3311,7 +3386,7 @@ 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}", содержащего активный оператор, сделает продолжение сеанса отладки невозможным. + Удаление "{0}", содержащего активный оператор, сделает продолжение сеанса отладки невозможным. @@ -3437,7 +3512,7 @@ Do you want to continue? Not Available ⚠ - Нет данных + Недоступно ⚠ @@ -3707,7 +3782,7 @@ When this standard format specifier is used, the formatting or parsing operation static constructor - static constructor + статический конструктор diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index a99efc879c47c..35b12bd806b28 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (kısa) @@ -92,7 +97,7 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be 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. + Genel bir türe '{0}' ekleme, hata ayıklama oturumunun devam etmesini engelleyecek. @@ -105,6 +110,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Arabirime '{0}' eklenmesi, hata ayıklama oturumunun devam etmesini engeller. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Bir kayda konumsal bir parametre eklenmesi hata ayıklama oturumunun devam etmesini engeller. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Bir yöntem ile bir açık arabirim belirleyici ekleme hata ayıklama oturumu devam etmesini engeller. @@ -132,17 +142,17 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Apply file header preferences - Apply file header preferences + Dosya üst bilgisi tercihlerini uygula Apply object/collection initialization preferences - Apply object/collection initialization preferences + Nesne/koleksiyon başlatma tercihlerini uygula 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. + 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. @@ -227,7 +237,7 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + {0} öğesinin görünürlüğünü değiştirmek, hata ayıklama oturumunun devam etmesini engeller. @@ -275,6 +285,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Kayda dönüştür + + Convert to record struct + Convert to record struct + + Convert to struct Yapıya dönüştür @@ -305,6 +320,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Kalanları özellik olarak oluştur ve ata + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Bir konumsal parametrenin bir kayıttan silinmesi hata ayıklama oturumunun devam etmesini engeller. + + Do not change this code. Put cleanup code in '{0}' method Bu kodu değiştirmeyin. Temizleme kodunu '{0}' metodunun içine yerleştirin. @@ -335,6 +355,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' projesinde yapılan değişiklikler hata ayıklama oturumunun devam etmesini engelleyecek: {1} + + Edit and continue is not supported by the runtime. + Düzenleme ve devam etme işlemleri çalışma zamanı tarafından desteklenmiyor. + + Error while reading file '{0}': {1} '{0}' dosyası okunurken hata: {1} @@ -360,6 +385,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Örnekler: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Açık olarak uygulanan kayıt yöntemlerinin derleyici tarafından oluşturulan '{0}' eşdeğeri ile eşleşen parametre adlarına sahip olması gerekir + + Extract base class... Temel sınıfı ayıkla... @@ -397,7 +427,7 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Format document - Format document + Belgeyi biçimlendir @@ -495,6 +525,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' aracılığıyla uygula + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + '{0}' kayıt konumsal parametresinin salt okunur olarak uygulanması hata ayıklama oturumunun devam etmesini engeller. + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Küme erişimcisi içeren '{0}' kayıt konumsal parametresinin uygulanması hata ayıklama oturumunun devam etmesini engeller. + + Incomplete \p{X} character escape Tamamlanmamış \p{X} karakter kaçış @@ -550,6 +590,21 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Yerel ekle + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable Sorgu değişkeni ekle @@ -575,6 +630,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Koşullu öğeyi ters çevir + + Making a method an iterator will prevent the debug session from continuing. + Metodun yineleyici yapılması, hata ayıklama oturumunun devam etmesini engeller. + + + + Making a method 'async' will prevent the debug session from continuing. + Metodun 'async' yapılması, hata ayıklama oturumunun devam etmesini engeller. + + malformed Hatalı biçimlendirilmiş @@ -620,11 +685,6 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Eksik denetim karakteri This is an error message shown to the user when they write an invalid Regular Expression. Example: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - Switch ifadesi içeren '{0}' öğesinin değiştirilmesi hata ayıklama oturumunun devam etmesini engeller. - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. '{0}' gövdesinin değiştirilmesi, gövdede çok fazla deyim olduğundan hata ayıklama oturumunun devam etmesini engeller. @@ -682,7 +742,7 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Operators - Operators + Operatörler @@ -1867,12 +1927,12 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri Remove unnecessary casts - Remove unnecessary casts + Gereksiz atamaları kaldır Remove unused variables - Remove unused variables + Kullanılmayan değişkenleri kaldır @@ -1892,7 +1952,7 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri Sort accessibility modifiers - Sort accessibility modifiers + Erişilebilirlik değiştiricilerini sırala @@ -2122,7 +2182,7 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + Bağlamlarda geçiş yapmak için gezinti çubuğunu kullanabilirsiniz. @@ -2313,12 +2373,12 @@ Bir saat değerinin saniyenin on binde birlik bileşenini görüntülemek mümk If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - "f" özel biçim belirticisi, saniye kesirinin en anlamlı basamağını; yani bir tarih ve saat değerinde saniyenin onda birini temsil eder. + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -"f" biçim belirticisi başka biçim belirticileri olmadan kullanılırsa, standart tarih ve saat biçim belirticisi "f" olarak yorumlanır. +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -"f" biçim belirticisi Parse, TryParse, ParseExact veya TryParseExact yöntemine geçilen bir biçim dizesinde kullanıldığında, "f" biçim belirticilerinin sayısı, dizenin başarıyla ayrıştırılması için saniye kesirinde bulunması gereken anlamlı basamak sayısını gösterir. - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ 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. + + and update call sites directly + and update call sites directly + + code - code + kod @@ -2527,6 +2592,16 @@ The "f" standard format specifier represents a combination of the long date ("D" Kaynakta (öznitelik) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date uzun tarih @@ -2549,7 +2624,7 @@ The "f" standard format specifier represents a combination of the long date ("D" {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ 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. + {0} öğesini silme, hata ayıklama oturumunun devam etmesini engelleyecek. @@ -3311,7 +3386,7 @@ 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. + Etkin bir deyim içeren {0} öğesini kaldırma, hata ayıklama oturumunun devam etmesini engelleyecek. @@ -3437,7 +3512,7 @@ Devam etmek istiyor musunuz? Not Available ⚠ - Kullanılamıyor + Kullanılabilir Değil ⚠ @@ -3707,7 +3782,7 @@ Bu standart biçim belirticisi kullanıldığında, biçimlendirme veya ayrışt static constructor - static constructor + statik oluşturucu diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index a8f054401a3d2..da1729d1caf2b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM(缩写) @@ -72,12 +77,12 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Add project reference to '{0}'. - 将参数引用添加到“{0}”。 + 添加到“{0}”的项目引用。 Add reference to '{0}'. - 将引用添加到“{0}”。 + 添加到“{0}”的引用。 @@ -92,7 +97,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 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. + 将“{0}”添加到泛型类型会中止调试会话。 @@ -105,6 +110,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 将 "{0}" 添加进接口将阻止调试会话继续。 + + Adding a positional parameter to a record will prevent the debug session from continuing. + 将位置参数添加到记录将阻止调试会话继续。 + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 添加具有显式接口说明符的方法将阻止调试会话继续。 @@ -132,17 +142,17 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Apply file header preferences - Apply file header preferences + 应用文件头首选项 Apply object/collection initialization preferences - Apply object/collection initialization preferences + 应用对象/集合初始化首选项 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. + 活动语句已从其初始方法中删除。必须还原更改才能继续或重新启动调试会话。 @@ -197,7 +207,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Change namespace to '{0}' - 将名称空间更改为“{0}” + 将命名空间更改为“{0}” @@ -227,7 +237,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + 更改 {0} 的可见性将会中止调试会话。 @@ -267,7 +277,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Convert to LINQ (call form) - 转换为 LINQ (调用表单) + 转换为 LINQ (调用形式) @@ -275,6 +285,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 转换为记录 + + Convert to record struct + Convert to record struct + + Convert to struct 转换为结构 @@ -287,22 +302,27 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Create and assign field '{0}' - 创建并分配字段 "{0}" + 创建字段 "{0}" 并赋值 Create and assign property '{0}' - 创建并分配属性 "{0}" + 创建属性 "{0}" 并赋值 Create and assign remaining as fields - 创建并分配其余字段 + 创建其余部分并赋值为字段 Create and assign remaining as properties - 创建并分配其余属性 + 创建其余部分并赋值为属性 + + + + Deleting a positional parameter from a record will prevent the debug session from continuing. + 从记录中删除位置参数将阻止调试会话继续。 @@ -335,6 +355,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 在项目“{0}”中所作的更改将阻止调试会话继续: {1} + + Edit and continue is not supported by the runtime. + 运行时不支持“编辑并继续”。 + + Error while reading file '{0}': {1} 读取文件“{0}”时出错: {1} @@ -360,6 +385,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 示例: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + 记录的显示实施方法参数名必须匹配编辑器生成的等效“{0}” + + Extract base class... 提取基类... @@ -367,7 +397,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Extract interface... - 提取接口… + 提取接口... @@ -397,7 +427,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Format document - Format document + 设置文档的格式 @@ -495,6 +525,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 通过“{0}”实现 + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + 将记录位置参数“{0}”实施为只读将阻止调试会话继续, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + 使用设置访问器实施记录位置参数“{0}”将阻止调试会话继续。 + + Incomplete \p{X} character escape \p{X} 字符转义不完整 @@ -550,6 +590,21 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 引入局部 + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable 引入查询变量 @@ -575,6 +630,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 反转条件 + + Making a method an iterator will prevent the debug session from continuing. + 创造方法迭代器将阻止调试会话继续。 + + + + Making a method 'async' will prevent the debug session from continuing. + 创建方法“异步”将阻止调试会话继续。 + + malformed 格式错误 @@ -607,7 +672,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Merge with previous '{0}' statement - 与以前的 "{0}" 语句合并 + 与上一个 "{0}" 语句合并 @@ -620,11 +685,6 @@ 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: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - 修改包含 switch 表达式的“{0}”将阻止调试会话继续执行。 - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. 修改“{0}”的主体将阻止调试会话继续,因为主体包含的语句过多。 @@ -682,7 +742,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Operators - Operators + 运算符 @@ -702,7 +762,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Pull members up to base type... - 将成员拉到基本类型..。 + 将成员拉到基类... @@ -1867,12 +1927,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Remove unnecessary casts - Remove unnecessary casts + 删除不必要的转换 Remove unused variables - Remove unused variables + 删除未使用的变量 @@ -1892,7 +1952,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort accessibility modifiers - Sort accessibility modifiers + 对可访问性修饰符排序 @@ -1912,12 +1972,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Suppress {0} - 取消 {0} + 抑制 {0} TODO: free unmanaged resources (unmanaged objects) and override finalizer - TODO: 释放未托管的资源(未托管的对象)并替代终结器 + TODO: 释放未托管的资源(未托管的对象)并重写终结器 @@ -2122,12 +2182,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + 可使用导航栏切换上下文。 '{0}' cannot be null or empty. - “{0}”不能为 Null 或空。 + “{0}”不能为 null 或空。 @@ -2313,12 +2373,12 @@ Although it's possible to display the ten thousandths of a second component of a If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - "f" 自定义格式说明符表示秒部分的最高有效位;即表示日期和时间值中的十分之一秒数。 + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -如果使用 "f" 格式说明符而没有其他自定义格式说明符,该说明符将被解释为 "f" 标准日期和时间格式说明符。 +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -将 "f" 格式说明符用作提供给 ParseExact、TryParseExact、ParseExact 或 TryParseExact 方法的格式字符串的一部分时,"f" 格式说明符的数目指示能够成功分析的字符串的秒部分的最高有效位的最大数目。 - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" 自定义格式说明符(另加任意数量的 "H" 说明符)将小时表示为从 00 至 23 的数字,即通过从零开始的 24 小时制表示小时,自午夜开始对小时计数。一位数字的小时数设置为带前导零的格式。 + + and update call sites directly + and update call sites directly + + code - code + 代码 @@ -2527,6 +2592,16 @@ The "f" standard format specifier represents a combination of the long date ("D" 在源(属性)中 + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date 长日期 @@ -2549,7 +2624,7 @@ The "f" standard format specifier represents a combination of the long date ("D" {0} '{1}' - {0} '{1}' + {0}“{1}” e.g. "method 'M'" @@ -3211,7 +3286,7 @@ If the "g" format specifier is used without other custom format specifiers, it's Deleting {0} will prevent the debug session from continuing. - 删除“{0}”将阻止调试会话继续。 + 删除 {0} 将会中止调试会话。 @@ -3311,7 +3386,7 @@ 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}”将阻止调试会话继续。 + 删除包含活动语句的 {0} 将会中止调试会话。 @@ -3437,7 +3512,7 @@ Do you want to continue? Not Available ⚠ - 不可用 + 不可用 ⚠ @@ -3707,7 +3782,7 @@ When this standard format specifier is used, the formatting or parsing operation static constructor - static constructor + 静态构造函数 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 141787394ee24..9fbc11b0add62 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -1,7 +1,12 @@ - + + + #{0} directive + #{0} directive + + AM/PM (abbreviated) AM/PM (縮寫) @@ -92,7 +97,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 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. + 將 '{0}' 新增至泛型型別會造成偵錯工作階段無法繼續。 @@ -105,6 +110,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 將 '{0}' 新增至介面,會造成偵錯工作階段無法繼續。 + + Adding a positional parameter to a record will prevent the debug session from continuing. + 若將位置參數新增至記錄,將會使偵錯工作階段無法繼續。 + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 新增具有明確介面指定名稱的方法會導致偵錯工作階段無法繼續。 @@ -132,17 +142,17 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Apply file header preferences - Apply file header preferences + 套用檔案標題的喜好設定 Apply object/collection initialization preferences - Apply object/collection initialization preferences + 套用物件/集合初始設定喜好設定 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. + 現用陳述式已從其原始方法中移除。您必須還原您的變更才能繼續,或是重新啟動偵錯工作階段。 @@ -227,7 +237,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Changing visibility of {0} will prevent the debug session from continuing. - Changing visibility of {0} will prevent the debug session from continuing. + 變更 {0} 的可見度會造成偵錯工作階段無法繼續。 @@ -275,6 +285,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 轉換為記錄 + + Convert to record struct + Convert to record struct + + Convert to struct 轉換為結構 @@ -305,6 +320,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 建立其餘項目並將其指派為屬性 + + Deleting a positional parameter from a record will prevent the debug session from continuing. + 若從記錄刪除位置參數,將會使偵錯工作階段無法繼續。 + + Do not change this code. Put cleanup code in '{0}' method 請勿變更此程式碼。請將清除程式碼放入 '{0}' 方法 @@ -335,6 +355,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 在專案 '{0}' 中所做的變更將使偵錯工作階段無法繼續: {1} + + Edit and continue is not supported by the runtime. + 執行階段不支援編輯後繼續。 + + Error while reading file '{0}': {1} 讀取檔案 '{0}' 時發生錯誤: {1} @@ -360,6 +385,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 範例: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + 記錄的明確實作方法,必須有參數名稱符合編譯器產生的相等 '{0}' + + Extract base class... 擷取基底類別... @@ -397,7 +427,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Format document - Format document + 格式化文件 @@ -495,6 +525,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 透過 '{0}' 實作 + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + 若將記錄位置參數 '{0}' 實作為唯讀,將會使偵錯工作階段無法繼續。 + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + 若使用 set 存取子實作記錄位置參數 '{0}',將會使偵錯工作階段無法繼續。 + + Incomplete \p{X} character escape 不完整的 \p{X} 字元逸出 @@ -550,6 +590,21 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 引進區域函式 + + Introduce parameter + Introduce parameter + + + + Introduce parameter for '{0}' + Introduce parameter for '{0}' + + + + Introduce parameter for all occurrences of '{0}' + Introduce parameter for all occurrences of '{0}' + + Introduce query variable 引進查詢變數 @@ -575,6 +630,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 反轉條件 + + Making a method an iterator will prevent the debug session from continuing. + 將方法設為迭代器會造成偵錯工作階段無法繼續。 + + + + Making a method 'async' will prevent the debug session from continuing. + 將方法設為「非同步」會造成偵錯工作階段無法繼續。 + + malformed 語式錯誤 @@ -620,11 +685,6 @@ 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: \c - - Modifying '{0}' which contains a switch expression will prevent the debug session from continuing. - 修改包含參數運算式的 '{0}' 會讓偵錯工作階段無法繼續。 - - Modifying the body of '{0}' will prevent the debug session from continuing because the body has too many statements. 因為 '{0}' 的主體有過多陳述式,所以修改該主體將導致偵錯工作階段無法繼續進行。 @@ -682,7 +742,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Operators - Operators + 運算子 @@ -1867,12 +1927,12 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Remove unnecessary casts - Remove unnecessary casts + 移除不必要的 Cast Remove unused variables - Remove unused variables + 移除未使用的變數 @@ -1892,7 +1952,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort accessibility modifiers - Sort accessibility modifiers + 排序協助工具修飾元 @@ -2122,7 +2182,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of You can use the navigation bar to switch contexts. - You can use the navigation bar to switch contexts. + 您可以使用導覽列切換內容。 @@ -2313,12 +2373,12 @@ Although it's possible to display the ten thousandths of a second component of a If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. - "f" 自訂格式規範代表秒小數部分的最大有效位數; 換句話說,其代表日期與時間值中的十分之一秒。 + The "f" custom format specifier represents the most significant digit of the seconds fraction; that is, it represents the tenths of a second in a date and time value. -如果在使用 "f" 格式規範時沒有其他格式規範,則會將其解譯為 "f" 標準日期與時間格式規範。 +If the "f" format specifier is used without other format specifiers, it's interpreted as the "f" standard date and time format specifier. -對 ParseExact、TryParseExact、ParseExact 或 TryParseExact 方法所提供的格式字串包含 "f" 格式規範時,"f" 格式規範的數字,代表成功剖析字串所必須出現的秒小數部分最大有效位數。 - {Locked="ParseExact"}{Locked="TryParseExact"}{Locked="f"} +When you use "f" format specifiers as part of a format string supplied to the ParseExact or TryParseExact method, the number of "f" format specifiers indicates the number of most significant digits of the seconds fraction that must be present to successfully parse the string. + {Locked="ParseExact"}{Locked="TryParseExact"}{Locked=""f""} 10ths of a second (non-zero) @@ -2386,9 +2446,14 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" 自訂格式規範 (加上任意數目的其他 "H" 規範) 代表小時,以數字 00 到 23 表示; 換句話說,小時即為自午夜起所經過的時數,是以零開始的 24 小時制。單一數字的小時格式,開頭不會出現零。 + + and update call sites directly + and update call sites directly + + code - code + 代碼 @@ -2527,6 +2592,16 @@ The "f" standard format specifier represents a combination of the long date ("D" 在來源中 (屬性) + + into extracted method to invoke at call sites + into extracted method to invoke at call sites + + + + into new overload + into new overload + + long date 完整日期 @@ -2549,7 +2624,7 @@ The "f" standard format specifier represents a combination of the long date ("D" {0} '{1}' - {0} '{1}' + {0} '{1}' e.g. "method 'M'" @@ -3211,7 +3286,7 @@ If the "g" format specifier is used without other custom format specifiers, it's Deleting {0} will prevent the debug session from continuing. - 刪除 '{0}' 會造成偵錯工作階段無法繼續。 + 刪除 {0} 會造成偵錯工作階段無法繼續。 @@ -3311,7 +3386,7 @@ 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}',會造成偵錯工作階段無法繼續。 + 移除包含作用中陳述式的 {0} 會造成偵錯工作階段無法繼續。 @@ -3437,7 +3512,7 @@ Do you want to continue? Not Available ⚠ - 無法使用 + 無法使用 ⚠ @@ -3707,7 +3782,7 @@ When this standard format specifier is used, the formatting or parsing operation static constructor - static constructor + 靜態建構函式 diff --git a/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.cs b/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.cs new file mode 100644 index 0000000000000..95cc255a83d45 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.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 System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + [Export(typeof(ILanguageServerFactory)), Shared] + internal class CSharpVisualBasicLanguageServerFactory : ILanguageServerFactory + { + public const string UserVisibleName = "C#/Visual Basic Language Server Client"; + + private readonly CSharpVisualBasicRequestDispatcherFactory _dispatcherFactory; + private readonly IAsynchronousOperationListenerProvider _listenerProvider; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpVisualBasicLanguageServerFactory(CSharpVisualBasicRequestDispatcherFactory dispatcherFactory, + IAsynchronousOperationListenerProvider listenerProvider) + { + _dispatcherFactory = dispatcherFactory; + _listenerProvider = listenerProvider; + } + + public ILanguageServerTarget Create( + JsonRpc jsonRpc, + ICapabilitiesProvider capabilitiesProvider, + ILspWorkspaceRegistrationService workspaceRegistrationService, + ILspLogger logger) + { + return new LanguageServerTarget( + _dispatcherFactory, + jsonRpc, + capabilitiesProvider, + workspaceRegistrationService, + _listenerProvider, + logger, + clientName: null, + userVisibleServerName: UserVisibleName, + telemetryServerTypeName: this.GetType().Name); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs index db212888c6c45..b607e5ca7927c 100644 --- a/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs @@ -47,6 +47,18 @@ internal class FindUsagesLSPContext : FindUsagesContext /// private readonly Dictionary _definitionsWithoutReference = new(); + /// + /// Set of the locations we've found references at. We may end up with multiple references + /// being reported for the same location. For example, this can happen in multi-targetting + /// scenarios when there are symbols in files linked into multiple projects. Those symbols + /// may have references that themselves are in linked locations, leading to multiple references + /// found at different virtual locations that the user considers at the same physical location. + /// For now we filter out these duplicates to not clutter the UI. If LSP supports the ability + /// to override an already reported VSReferenceItem, we could also reissue the item with the + /// additional information about all the projects it is found in. + /// + private readonly HashSet<(string? filePath, TextSpan span)> _referenceLocations = new(); + /// /// We report the results in chunks. A batch, if it contains results, is reported every 0.5s. /// @@ -55,8 +67,6 @@ internal class FindUsagesLSPContext : FindUsagesContext // Unique identifier given to each definition and reference. private int _id = 0; - public override CancellationToken CancellationToken { get; } - public FindUsagesLSPContext( IProgress progress, Document document, @@ -70,17 +80,15 @@ public FindUsagesLSPContext( _metadataAsSourceFileService = metadataAsSourceFileService; _workQueue = new AsyncBatchingWorkQueue( TimeSpan.FromMilliseconds(500), ReportReferencesAsync, cancellationToken); - - CancellationToken = cancellationToken; } // After all definitions/references have been found, wait here until all results have been reported. - public override async ValueTask OnCompletedAsync() + public override async ValueTask OnCompletedAsync(CancellationToken cancellationToken) => await _workQueue.WaitUntilCurrentBatchCompletesAsync().ConfigureAwait(false); - public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition) + public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { - using (await _semaphore.DisposableWaitAsync(CancellationToken).ConfigureAwait(false)) + using (await _semaphore.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { if (_definitionToId.ContainsKey(definition)) { @@ -95,7 +103,7 @@ public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition var definitionItem = await GenerateVSReferenceItemAsync( _id, definitionId: _id, _document, _position, definition.SourceSpans.FirstOrDefault(), definition.DisplayableProperties, _metadataAsSourceFileService, definition.GetClassifiedText(), - definition.Tags.GetFirstGlyph(), symbolUsageInfo: null, isWrittenTo: false, CancellationToken).ConfigureAwait(false); + definition.Tags.GetFirstGlyph(), symbolUsageInfo: null, isWrittenTo: false, cancellationToken).ConfigureAwait(false); if (definitionItem != null) { @@ -113,16 +121,19 @@ public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition } } - public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem reference) + public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) { - using (await _semaphore.DisposableWaitAsync(CancellationToken).ConfigureAwait(false)) + using (await _semaphore.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { // Each reference should be associated with a definition. If this somehow isn't the // case, we bail out early. if (!_definitionToId.TryGetValue(reference.Definition, out var definitionId)) - { return; - } + + // If this is reference to the same physical location we've already reported, just + // filter this out. it will clutter the UI to show the same places. + if (!_referenceLocations.Add((reference.SourceSpan.Document.FilePath, reference.SourceSpan.SourceSpan))) + return; // If the definition hasn't been reported yet, add it to our list of references to report. if (_definitionsWithoutReference.TryGetValue(definitionId, out var definition)) @@ -137,7 +148,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere var referenceItem = await GenerateVSReferenceItemAsync( _id, definitionId, _document, _position, reference.SourceSpan, reference.AdditionalProperties, _metadataAsSourceFileService, definitionText: null, - definitionGlyph: Glyph.None, reference.SymbolUsageInfo, reference.IsWrittenTo, CancellationToken).ConfigureAwait(false); + definitionGlyph: Glyph.None, reference.SymbolUsageInfo, reference.IsWrittenTo, cancellationToken).ConfigureAwait(false); if (referenceItem != null) { diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs new file mode 100644 index 0000000000000..a46dd4b5cb463 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + [Export(typeof(DefaultCapabilitiesProvider)), Shared] + internal class DefaultCapabilitiesProvider : ICapabilitiesProvider + { + private readonly ImmutableArray> _completionProviders; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DefaultCapabilitiesProvider( + [ImportMany] IEnumerable> completionProviders) + { + _completionProviders = completionProviders + .Where(lz => lz.Metadata.Language == LanguageNames.CSharp || lz.Metadata.Language == LanguageNames.VisualBasic) + .ToImmutableArray(); + } + + public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) + { + var capabilities = new ServerCapabilities(); + if (clientCapabilities is VSClientCapabilities vsClientCapabilities && vsClientCapabilities.SupportsVisualStudioExtensions) + { + capabilities = GetVSServerCapabilities(); + } + + var commitCharacters = CompletionRules.Default.DefaultCommitCharacters.Select(c => c.ToString()).ToArray(); + var triggerCharacters = _completionProviders.SelectMany( + lz => CompletionHandler.GetTriggerCharacters(lz.Value)).Distinct().Select(c => c.ToString()).ToArray(); + + capabilities.DefinitionProvider = true; + capabilities.RenameProvider = true; + capabilities.ImplementationProvider = true; + capabilities.CodeActionProvider = new CodeActionOptions { CodeActionKinds = new[] { CodeActionKind.QuickFix, CodeActionKind.Refactor } }; + capabilities.CompletionProvider = new VisualStudio.LanguageServer.Protocol.CompletionOptions + { + ResolveProvider = true, + AllCommitCharacters = commitCharacters, + TriggerCharacters = triggerCharacters, + }; + + capabilities.SignatureHelpProvider = new SignatureHelpOptions { TriggerCharacters = new[] { "(", "," } }; + capabilities.DocumentSymbolProvider = true; + capabilities.WorkspaceSymbolProvider = true; + capabilities.DocumentFormattingProvider = true; + capabilities.DocumentRangeFormattingProvider = true; + capabilities.DocumentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions { FirstTriggerCharacter = "}", MoreTriggerCharacter = new[] { ";", "\n" } }; + capabilities.ReferencesProvider = true; + capabilities.FoldingRangeProvider = true; + capabilities.ExecuteCommandProvider = new ExecuteCommandOptions(); + capabilities.TextDocumentSync = new TextDocumentSyncOptions + { + Change = TextDocumentSyncKind.Incremental, + OpenClose = true + }; + + capabilities.HoverProvider = true; + + return capabilities; + } + + private static VSServerCapabilities GetVSServerCapabilities() + { + var vsServerCapabilities = new VSServerCapabilities(); + vsServerCapabilities.CodeActionsResolveProvider = true; + vsServerCapabilities.OnAutoInsertProvider = new DocumentOnAutoInsertOptions { TriggerCharacters = new[] { "'", "/", "\n" } }; + vsServerCapabilities.DocumentHighlightProvider = true; + vsServerCapabilities.ProjectContextProvider = true; + vsServerCapabilities.SemanticTokensOptions = new SemanticTokensOptions + { + DocumentProvider = new SemanticTokensDocumentProviderOptions { Edits = true }, + RangeProvider = true, + Legend = new SemanticTokensLegend + { + TokenTypes = SemanticTokenTypes.AllTypes.Concat(SemanticTokensHelpers.RoslynCustomTokenTypes).ToArray(), + TokenModifiers = new string[] { SemanticTokenModifiers.Static } + } + }; + + // Diagnostic requests are only supported from PullDiagnosticsInProcLanguageClient. + vsServerCapabilities.SupportsDiagnosticRequests = false; + return vsServerCapabilities; + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 683996b2edf1d..e0092066fe773 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -129,6 +130,26 @@ public static bool HasVisualStudioLspCapability(this ClientCapabilities clientCa return false; } + public static bool HasCompletionListDataCapability(this ClientCapabilities clientCapabilities) + { + if (!TryGetVSCompletionListSetting(clientCapabilities, out var completionListSetting)) + { + return false; + } + + return completionListSetting.Data; + } + + public static bool HasCompletionListCommitCharactersCapability(this ClientCapabilities clientCapabilities) + { + if (!TryGetVSCompletionListSetting(clientCapabilities, out var completionListSetting)) + { + return false; + } + + return completionListSetting.CommitCharacters; + } + public static string GetMarkdownLanguageName(this Document document) { switch (document.Project.Language) @@ -156,5 +177,35 @@ public static bool IsRazorDocument(this Document document) var spanMapper = document.Services.GetService(); return spanMapper != null; } + + private static bool TryGetVSCompletionListSetting(ClientCapabilities clientCapabilities, [NotNullWhen(returnValue: true)] out VSCompletionListSetting? completionListSetting) + { + if (clientCapabilities is not VSClientCapabilities vsClientCapabilities) + { + completionListSetting = null; + return false; + } + + var textDocumentCapability = vsClientCapabilities.TextDocument; + if (textDocumentCapability == null) + { + completionListSetting = null; + return false; + } + + if (textDocumentCapability.Completion is not VSCompletionSetting vsCompletionSetting) + { + completionListSetting = null; + return false; + } + + completionListSetting = vsCompletionSetting.CompletionList; + if (completionListSetting == null) + { + return false; + } + + return true; + } } } diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index f80cce294bf70..0b463c186d80b 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -6,12 +6,15 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Tags; using Microsoft.CodeAnalysis.Text; @@ -25,6 +28,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer { internal static class ProtocolConversions { + private const string CSharpMarkdownLanguageName = "csharp"; + private const string VisualBasicMarkdownLanguageName = "vb"; + + private static readonly Regex s_markdownEscapeRegex = new(@"([\\`\*_\{\}\[\]\(\)#+\-\.!])", RegexOptions.Compiled); + // NOTE: While the spec allows it, don't use Function and Method, as both VS and VS Code display them the same way // which can confuse users public static readonly Dictionary RoslynTagToCompletionItemKind = new Dictionary() @@ -196,11 +204,13 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text) } public static Task DocumentSpanToLocationAsync(DocumentSpan documentSpan, CancellationToken cancellationToken) - => TextSpanToLocationAsync(documentSpan.Document, documentSpan.SourceSpan, cancellationToken); + => TextSpanToLocationAsync(documentSpan.Document, documentSpan.SourceSpan, isStale: false, cancellationToken); - public static async Task DocumentSpanToLocationWithTextAsync(DocumentSpan documentSpan, ClassifiedTextElement text, CancellationToken cancellationToken) + public static async Task DocumentSpanToLocationWithTextAsync( + DocumentSpan documentSpan, ClassifiedTextElement text, CancellationToken cancellationToken) { - var location = await TextSpanToLocationAsync(documentSpan.Document, documentSpan.SourceSpan, cancellationToken).ConfigureAwait(false); + var location = await TextSpanToLocationAsync( + documentSpan.Document, documentSpan.SourceSpan, isStale: false, cancellationToken).ConfigureAwait(false); return location == null ? null : new LSP.LocationWithText { @@ -279,18 +289,22 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text) return documentEdits; } - public static async Task TextSpanToLocationAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + public static async Task TextSpanToLocationAsync( + Document document, + TextSpan textSpan, + bool isStale, + CancellationToken cancellationToken) { var result = await GetMappedSpanResultAsync(document, ImmutableArray.Create(textSpan), cancellationToken).ConfigureAwait(false); if (result == null) { - return await ConvertTextSpanToLocation(document, textSpan, cancellationToken).ConfigureAwait(false); + return await ConvertTextSpanToLocation(document, textSpan, isStale, cancellationToken).ConfigureAwait(false); } var mappedSpan = result.Value.Single(); if (mappedSpan.IsDefault) { - return await ConvertTextSpanToLocation(document, textSpan, cancellationToken).ConfigureAwait(false); + return await ConvertTextSpanToLocation(document, textSpan, isStale, cancellationToken).ConfigureAwait(false); } return new LSP.Location @@ -299,10 +313,24 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text) Range = MappedSpanResultToRange(mappedSpan) }; - static async Task ConvertTextSpanToLocation(Document document, TextSpan span, CancellationToken cancellationToken) + static async Task ConvertTextSpanToLocation( + Document document, + TextSpan span, + bool isStale, + CancellationToken cancellationToken) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (isStale) + { + // in the case of a stale item, the span may be out of bounds of the document. Cap + // us to the end of the document as that's where we're going to navigate the user + // to. + span = TextSpan.FromBounds( + Math.Min(text.Length, span.Start), + Math.Min(text.Length, span.End)); + } + return ConvertTextSpanWithTextToLocation(span, text, document.GetURI()); } @@ -596,6 +624,103 @@ public static ProjectId ProjectContextToProjectId(ProjectContext projectContext) debugName: projectContext.Id.Substring(delimiter + 1)); } + public static async Task FormattingOptionsToDocumentOptionsAsync( + LSP.FormattingOptions options, + Document document, + CancellationToken cancellationToken) + { + var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + // LSP doesn't currently support indent size as an option. However, except in special + // circumstances, indent size is usually equivalent to tab size, so we'll just set it. + var updatedOptions = documentOptions + .WithChangedOption(Formatting.FormattingOptions.UseTabs, !options.InsertSpaces) + .WithChangedOption(Formatting.FormattingOptions.TabSize, options.TabSize) + .WithChangedOption(Formatting.FormattingOptions.IndentationSize, options.TabSize); + return updatedOptions; + } + + public static LSP.MarkupContent GetDocumentationMarkupContent(ImmutableArray tags, Document document, bool featureSupportsMarkdown) + { + if (!featureSupportsMarkdown) + { + return new LSP.MarkupContent + { + Kind = LSP.MarkupKind.PlainText, + Value = tags.GetFullText(), + }; + } + + var builder = new StringBuilder(); + var isInCodeBlock = false; + foreach (var taggedText in tags) + { + switch (taggedText.Tag) + { + case TextTags.CodeBlockStart: + var codeBlockLanguageName = GetCodeBlockLanguageName(document); + builder.Append($"```{codeBlockLanguageName}{Environment.NewLine}"); + builder.Append(taggedText.Text); + isInCodeBlock = true; + break; + case TextTags.CodeBlockEnd: + builder.Append($"{Environment.NewLine}```{Environment.NewLine}"); + builder.Append(taggedText.Text); + isInCodeBlock = false; + break; + case TextTags.LineBreak: + // A line ending with double space and a new line indicates to markdown + // to render a single-spaced line break. + builder.Append(" "); + builder.Append(Environment.NewLine); + break; + default: + var styledText = GetStyledText(taggedText, isInCodeBlock); + builder.Append(styledText); + break; + } + } + + return new LSP.MarkupContent + { + Kind = LSP.MarkupKind.Markdown, + Value = builder.ToString(), + }; + + static string GetCodeBlockLanguageName(Document document) + { + return document.Project.Language switch + { + (LanguageNames.CSharp) => CSharpMarkdownLanguageName, + (LanguageNames.VisualBasic) => VisualBasicMarkdownLanguageName, + _ => throw new InvalidOperationException($"{document.Project.Language} is not supported"), + }; + } + + static string GetStyledText(TaggedText taggedText, bool isInCodeBlock) + { + var text = isInCodeBlock ? taggedText.Text : s_markdownEscapeRegex.Replace(taggedText.Text, @"\$1"); + + // For non-cref links, the URI is present in both the hint and target. + if (!string.IsNullOrEmpty(taggedText.NavigationHint) && taggedText.NavigationHint == taggedText.NavigationTarget) + return $"[{text}]({taggedText.NavigationHint})"; + + // Markdown ignores spaces at the start of lines outside of code blocks, + // so to get indented lines we replace the spaces with these. + if (!isInCodeBlock) + text = text.Replace(" ", " "); + + return taggedText.Style switch + { + TaggedTextStyle.None => text, + TaggedTextStyle.Strong => $"**{text}**", + TaggedTextStyle.Emphasis => $"_{text}_", + TaggedTextStyle.Underline => $"{text}", + TaggedTextStyle.Code => $"`{text}`", + _ => text, + }; + } + } + private static async Task?> GetMappedSpanResultAsync(TextDocument textDocument, ImmutableArray textSpans, CancellationToken cancellationToken) { if (textDocument is not Document document) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs index 6e20c8632c6d4..b3e60c33b6d1d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -39,12 +39,10 @@ public static async Task GetVSCodeActionsAsync( { var actionSets = await GetActionSetsAsync( document, codeFixService, codeRefactoringService, request.Range, cancellationToken).ConfigureAwait(false); - if (!actionSets.HasValue) - { + if (actionSets.IsDefaultOrEmpty) return Array.Empty(); - } - await codeActionsCache.UpdateActionSetsAsync(document, request.Range, actionSets.Value, cancellationToken).ConfigureAwait(false); + await codeActionsCache.UpdateActionSetsAsync(document, request.Range, actionSets, cancellationToken).ConfigureAwait(false); var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // Each suggested action set should have a unique set number, which is used for grouping code actions together. @@ -166,18 +164,10 @@ public static async Task> GetCodeActionsAsync( { var actionSets = await GetActionSetsAsync( document, codeFixService, codeRefactoringService, selection, cancellationToken).ConfigureAwait(false); - if (!actionSets.HasValue) - { - actionSets = await GetActionSetsAsync( - document, codeFixService, codeRefactoringService, selection, cancellationToken).ConfigureAwait(false); + if (actionSets.IsDefaultOrEmpty) + return ImmutableArray.Empty; - if (!actionSets.HasValue) - { - return ImmutableArray.Empty; - } - - await codeActionsCache.UpdateActionSetsAsync(document, selection, actionSets.Value, cancellationToken).ConfigureAwait(false); - } + await codeActionsCache.UpdateActionSetsAsync(document, selection, actionSets, cancellationToken).ConfigureAwait(false); var _ = ArrayBuilder.GetInstance(out var codeActions); foreach (var set in actionSets) @@ -221,7 +211,7 @@ private static CodeAction GetNestedActionsFromActionSet(IUnifiedSuggestedAction codeAction.Title, nestedActions.ToImmutable(), codeAction.IsInlinable, codeAction.Priority); } - private static async Task?> GetActionSetsAsync( + private static async ValueTask> GetActionSetsAsync( Document document, ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, @@ -232,7 +222,7 @@ private static CodeAction GetNestedActionsFromActionSet(IUnifiedSuggestedAction var textSpan = ProtocolConversions.RangeToTextSpan(selection, text); var codeFixes = await UnifiedSuggestedActionsSource.GetFilterAndOrderCodeFixesAsync( - document.Project.Solution.Workspace, codeFixService, document, textSpan, includeSuppressionFixes: true, + document.Project.Solution.Workspace, codeFixService, document, textSpan, isBlocking: false, addOperationScope: _ => null, cancellationToken).ConfigureAwait(false); var codeRefactorings = await UnifiedSuggestedActionsSource.GetFilterAndOrderCodeRefactoringsAsync( diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs index 10a202ba3f941..b0f60cdf056b1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// of the returned VSCodeActions blank, as these properties should be populated by the /// CodeActionsResolveHandler only when the user requests them. /// - internal class CodeActionsHandler : IRequestHandler + internal class CodeActionsHandler : IRequestHandler { private readonly CodeActionsCache _codeActionsCache; private readonly ICodeFixService _codeFixService; @@ -43,7 +43,7 @@ public CodeActionsHandler( public TextDocumentIdentifier? GetTextDocumentIdentifier(CodeActionParams request) => request.TextDocument; - public async Task HandleRequestAsync(LSP.CodeActionParams request, RequestContext context, CancellationToken cancellationToken) + public async Task HandleRequestAsync(LSP.CodeActionParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; if (document == null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs index 9ee3208d19b0b..d7273addb85c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; @@ -88,7 +89,7 @@ public CompletionHandler( var lspVSClientCapability = context.ClientCapabilities.HasVisualStudioLspCapability() == true; var snippetsSupported = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false; - var commitCharactersRuleCache = new Dictionary, ImmutableArray>(); + var commitCharactersRuleCache = new Dictionary, string[]>(CommitCharacterArrayComparer.Instance); // Cache the completion list so we can avoid recomputation in the resolve handler var resultId = _completionListCache.UpdateCache(request.TextDocument, list); @@ -120,22 +121,38 @@ public CompletionHandler( defaultRange = ProtocolConversions.TextSpanToRange(defaultSpan.Value, documentText); } + var supportsCompletionListData = context.ClientCapabilities.HasCompletionListDataCapability(); + var completionResolveData = new CompletionResolveData() + { + ResultId = resultId, + }; var stringBuilder = new StringBuilder(); using var _ = ArrayBuilder.GetInstance(out var lspCompletionItems); foreach (var item in list.Items) { + var completionItemResolveData = supportsCompletionListData ? null : completionResolveData; var lspCompletionItem = await CreateLSPCompletionItemAsync( - request, document, item, resultId, lspVSClientCapability, completionTrigger, commitCharactersRuleCache, + request, document, item, completionItemResolveData, lspVSClientCapability, completionTrigger, commitCharactersRuleCache, completionService, context.ClientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); lspCompletionItems.Add(lspCompletionItem); } - var completionList = new LSP.VSCompletionList { Items = lspCompletionItems.ToArray(), SuggestionMode = list.SuggestionModeItem != null, }; + + if (supportsCompletionListData) + { + completionList.Data = completionResolveData; + } + + if (context.ClientCapabilities.HasCompletionListCommitCharactersCapability()) + { + PromoteCommonCommitCharactersOntoList(completionList); + } + var optimizedCompletionList = new LSP.OptimizedVSCompletionList(completionList); return optimizedCompletionList; @@ -160,10 +177,10 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter LSP.CompletionParams request, Document document, CompletionItem item, - long? resultId, - bool useVSCompletionItem, + CompletionResolveData? completionResolveData, + bool supportsVSExtensions, CompletionTrigger completionTrigger, - Dictionary, ImmutableArray> commitCharacterRulesCache, + Dictionary, string[]> commitCharacterRulesCache, CompletionService completionService, string? clientName, bool returnTextEdits, @@ -174,10 +191,10 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter LSP.Range? defaultRange, CancellationToken cancellationToken) { - if (useVSCompletionItem) + if (supportsVSExtensions) { var vsCompletionItem = await CreateCompletionItemAsync( - request, document, item, resultId, completionTrigger, commitCharacterRulesCache, + request, document, item, completionResolveData, supportsVSExtensions, completionTrigger, commitCharacterRulesCache, completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId()); @@ -186,7 +203,7 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter else { var roslynCompletionItem = await CreateCompletionItemAsync( - request, document, item, resultId, completionTrigger, commitCharacterRulesCache, + request, document, item, completionResolveData, supportsVSExtensions, completionTrigger, commitCharacterRulesCache, completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); return roslynCompletionItem; @@ -197,9 +214,10 @@ static async Task CreateCompletionItemAsync( LSP.CompletionParams request, Document document, CompletionItem item, - long? resultId, + CompletionResolveData? completionResolveData, + bool supportsVSExtensions, CompletionTrigger completionTrigger, - Dictionary, ImmutableArray> commitCharacterRulesCache, + Dictionary, string[]> commitCharacterRulesCache, CompletionService completionService, string? clientName, bool returnTextEdits, @@ -223,10 +241,7 @@ static async Task CreateCompletionItemAsync( SortText = item.SortText, FilterText = item.FilterText, Kind = GetCompletionKind(item.Tags), - Data = new CompletionResolveData - { - ResultId = resultId, - }, + Data = completionResolveData, Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, }; @@ -254,7 +269,7 @@ static async Task CreateCompletionItemAsync( completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText; } - var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache); + var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache, supportsVSExtensions); if (commitCharacters != null) { completionItem.CommitCharacters = commitCharacters; @@ -291,19 +306,33 @@ static async Task CreateCompletionItemAsync( } } - static string[]? GetCommitCharacters(CompletionItem item, Dictionary, ImmutableArray> currentRuleCache) + static string[]? GetCommitCharacters( + CompletionItem item, + Dictionary, string[]> currentRuleCache, + bool supportsVSExtensions) { + // VSCode does not have the concept of soft selection, the list is always hard selected. + // In order to emulate soft selection behavior for things like argument completion, regex completion, datetime completion, etc + // we create a completion item without any specific commit characters. This means only tab / enter will commit. + // VS supports soft selection, so we only do this for non-VS clients. + if (!supportsVSExtensions && item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.SoftSelection) + { + return Array.Empty(); + } + var commitCharacterRules = item.Rules.CommitCharacterRules; - // If the item doesn't have any special rules, just use the default commit characters. - if (commitCharacterRules.IsEmpty) + // VS will use the default commit characters if no items are specified on the completion item. + // However, other clients like VSCode do not support this behavior so we must specify + // commit characters on every completion item - https://github.com/microsoft/vscode/issues/90987 + if (supportsVSExtensions && commitCharacterRules.IsEmpty) { return null; } - if (currentRuleCache.TryGetValue(commitCharacterRules, out var currentRules)) + if (currentRuleCache.TryGetValue(commitCharacterRules, out var cachedCommitCharacters)) { - return currentRules.ToArray(); + return cachedCommitCharacters; } using var _ = PooledHashSet.GetInstance(out var commitCharacters); @@ -325,9 +354,50 @@ static async Task CreateCompletionItemAsync( } } - var commitCharacterSet = commitCharacters.Select(c => c.ToString()).ToImmutableArray(); - currentRuleCache.Add(item.Rules.CommitCharacterRules, commitCharacterSet); - return commitCharacterSet.ToArray(); + var lspCommitCharacters = commitCharacters.Select(c => c.ToString()).ToArray(); + currentRuleCache.Add(item.Rules.CommitCharacterRules, lspCommitCharacters); + return lspCommitCharacters; + } + + static void PromoteCommonCommitCharactersOntoList(LSP.VSCompletionList completionList) + { + var commitCharacterReferences = new Dictionary(); + var mostUsedCount = 0; + string[]? mostUsedCommitCharacters = null; + for (var i = 0; i < completionList.Items.Length; i++) + { + var completionItem = completionList.Items[i]; + var commitCharacters = completionItem.CommitCharacters; + if (commitCharacters == null) + { + continue; + } + + commitCharacterReferences.TryGetValue(commitCharacters, out var existingCount); + existingCount++; + + if (existingCount > mostUsedCount) + { + // Capture the most used commit character counts so we don't need to re-iterate the array later + mostUsedCommitCharacters = commitCharacters; + mostUsedCount = existingCount; + } + + commitCharacterReferences[commitCharacters] = existingCount; + } + + Contract.ThrowIfNull(mostUsedCommitCharacters); + + // Promoted the most used commit characters onto the list and then remove these from child items. + completionList.CommitCharacters = mostUsedCommitCharacters; + for (var i = 0; i < completionList.Items.Length; i++) + { + var completionItem = completionList.Items[i]; + if (completionItem.CommitCharacters == mostUsedCommitCharacters) + { + completionItem.CommitCharacters = null; + } + } } } @@ -355,8 +425,7 @@ internal static async Task GetCompletionOptionsAsync(Document documen var completionOptions = documentOptions .WithChangedOption(CompletionOptions.SnippetsBehavior, SnippetsRule.NeverInclude) .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, false) - .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, false) - .WithChangedOption(CompletionServiceOptions.DisallowAddingImports, true); + .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, false); return completionOptions; } @@ -386,5 +455,50 @@ public TestAccessor(CompletionHandler completionHandler) public CompletionListCache GetCache() => _completionHandler._completionListCache; } + + private class CommitCharacterArrayComparer : IEqualityComparer> + { + public static readonly CommitCharacterArrayComparer Instance = new(); + + private CommitCharacterArrayComparer() + { + } + + public bool Equals([AllowNull] ImmutableArray x, [AllowNull] ImmutableArray y) + { + for (var i = 0; i < x.Length; i++) + { + var xKind = x[i].Kind; + var yKind = y[i].Kind; + if (xKind != yKind) + { + return false; + } + + var xCharacters = x[i].Characters; + var yCharacters = y[i].Characters; + if (xCharacters.Length != yCharacters.Length) + { + return false; + } + + for (var j = 0; j < xCharacters.Length; j++) + { + if (xCharacters[j] != yCharacters[j]) + { + return false; + } + } + } + + return true; + } + + public int GetHashCode([DisallowNull] ImmutableArray obj) + { + var combinedHash = Hash.CombineValues(obj); + return combinedHash; + } + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs index 081454ca5fe07..001f20c589150 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs @@ -4,6 +4,7 @@ using System; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; @@ -65,11 +66,18 @@ public CompletionResolveHandler(CompletionListCache completionListCache) var description = await completionService.GetDescriptionAsync(document, selectedItem, cancellationToken).ConfigureAwait(false); - if (completionItem is LSP.VSCompletionItem vsCompletionItem) + var supportsVSExtensions = context.ClientCapabilities.HasVisualStudioLspCapability(); + if (supportsVSExtensions) { + var vsCompletionItem = (LSP.VSCompletionItem)completionItem; vsCompletionItem.Description = new ClassifiedTextElement(description.TaggedParts .Select(tp => new ClassifiedTextRun(tp.Tag.ToClassificationTypeName(), tp.Text))); } + else + { + var clientSupportsMarkdown = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.DocumentationFormat.Contains(LSP.MarkupKind.Markdown) == true; + completionItem.Documentation = ProtocolConversions.GetDocumentationMarkupContent(description.TaggedParts, document, clientSupportsMarkdown); + } // We compute the TextEdit resolves for complex text edits (e.g. override and partial // method completions) here. Lazily resolving TextEdits is technically a violation of @@ -87,7 +95,6 @@ public CompletionResolveHandler(CompletionListCache completionListCache) document, completionService, selectedItem, snippetsSupported, cancellationToken).ConfigureAwait(false); } - completionItem.Detail = description.TaggedParts.GetFullText(); return completionItem; } @@ -98,18 +105,19 @@ private static bool MatchesLSPCompletionItem(LSP.CompletionItem lspCompletionIte return false; } - if (!lspCompletionItem.Label.EndsWith(completionItem.DisplayTextSuffix, StringComparison.Ordinal)) + // The prefix matches, consume the matching prefix from the lsp completion item label. + var displayTextWithSuffix = lspCompletionItem.Label.Substring(completionItem.DisplayTextPrefix.Length, lspCompletionItem.Label.Length - completionItem.DisplayTextPrefix.Length); + if (!displayTextWithSuffix.EndsWith(completionItem.DisplayTextSuffix, StringComparison.Ordinal)) { return false; } - if (string.Compare(lspCompletionItem.Label, completionItem.DisplayTextPrefix.Length, completionItem.DisplayText, 0, completionItem.DisplayText.Length, StringComparison.Ordinal) != 0) - { - return false; - } + // The suffix matches, consume the matching suffix from the lsp completion item label. + var originalDisplayText = displayTextWithSuffix.Substring(0, displayTextWithSuffix.Length - completionItem.DisplayTextSuffix.Length); - // All parts of the LSP completion item match the provided completion item. - return true; + // Now we're left with what should be the original display text for the lsp completion item. + // Check to make sure it matches the cached completion item label. + return string.Equals(originalDisplayText, completionItem.DisplayText); } // Internal for testing diff --git a/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs index 0759e9f573eca..75b06a9728f85 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs @@ -53,7 +53,8 @@ public AbstractGoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSour continue; } - var location = await ProtocolConversions.TextSpanToLocationAsync(definition.Document, definition.SourceSpan, cancellationToken).ConfigureAwait(false); + var location = await ProtocolConversions.TextSpanToLocationAsync( + definition.Document, definition.SourceSpan, definition.IsStale, cancellationToken).ConfigureAwait(false); locations.AddIfNotNull(location); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs index 2da56947d1faf..13209b266520c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -2,11 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; @@ -18,14 +19,18 @@ internal abstract class AbstractFormatDocumentHandlerBase false; public override bool RequiresLSPSolution => true; - protected async Task GetTextEditsAsync(RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) + protected async Task GetTextEditsAsync( + RequestContext context, + LSP.FormattingOptions options, + CancellationToken cancellationToken, + LSP.Range? range = null) { var edits = new ArrayBuilder(); var document = context.Document; if (document != null) { - var formattingService = document.Project.LanguageServices.GetRequiredService(); + var formattingService = document.Project.LanguageServices.GetRequiredService(); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); TextSpan? textSpan = null; if (range != null) @@ -33,14 +38,23 @@ internal abstract class AbstractFormatDocumentHandlerBase ProtocolConversions.TextChangeToTextEdit(change, text))); } return edits.ToArrayAndFree(); } - protected virtual Task> GetFormattingChangesAsync(IEditorFormattingService formattingService, Document document, TextSpan? textSpan, CancellationToken cancellationToken) - => formattingService.GetFormattingChangesAsync(document, textSpan, cancellationToken); + protected virtual Task> GetFormattingChangesAsync( + IFormattingInteractionService formattingService, + Document document, + TextSpan? textSpan, + DocumentOptionSet documentOptions, + CancellationToken cancellationToken) + => formattingService.GetFormattingChangesAsync(document, textSpan, documentOptions, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs index e8b624e0045e3..c13b515123c5b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs @@ -25,7 +25,10 @@ public FormatDocumentHandler() public override LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.DocumentFormattingParams request) => request.TextDocument; - public override Task HandleRequestAsync(LSP.DocumentFormattingParams request, RequestContext context, CancellationToken cancellationToken) - => GetTextEditsAsync(context, cancellationToken); + public override Task HandleRequestAsync( + LSP.DocumentFormattingParams request, + RequestContext context, + CancellationToken cancellationToken) + => GetTextEditsAsync(context, request.Options, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs index 05c7de8ea4055..b83fa2182197e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -9,8 +9,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -34,13 +35,16 @@ public FormatDocumentOnTypeHandler() public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentOnTypeFormattingParams request) => request.TextDocument; - public override async Task HandleRequestAsync(DocumentOnTypeFormattingParams request, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync( + DocumentOnTypeFormattingParams request, + RequestContext context, + CancellationToken cancellationToken) { var edits = new ArrayBuilder(); var document = context.Document; if (document != null) { - var formattingService = document.Project.LanguageServices.GetRequiredService(); + var formattingService = document.Project.LanguageServices.GetRequiredService(); var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(request.Character)) @@ -48,14 +52,20 @@ public override async Task HandleRequestAsync(DocumentOnTypeFormatti return edits.ToArrayAndFree(); } + // We should use the options passed in by LSP instead of the document's options. + var documentOptions = await ProtocolConversions.FormattingOptionsToDocumentOptionsAsync( + request.Options, document, cancellationToken).ConfigureAwait(false); + IList? textChanges; if (SyntaxFacts.IsNewLine(request.Character[0])) { - textChanges = await GetFormattingChangesOnReturnAsync(formattingService, document, position, cancellationToken).ConfigureAwait(false); + textChanges = await GetFormattingChangesOnReturnAsync( + formattingService, document, position, documentOptions, cancellationToken).ConfigureAwait(false); } else { - textChanges = await GetFormattingChangesAsync(formattingService, document, request.Character[0], position, cancellationToken).ConfigureAwait(false); + textChanges = await GetFormattingChangesAsync( + formattingService, document, request.Character[0], position, documentOptions, cancellationToken).ConfigureAwait(false); } var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -68,10 +78,21 @@ public override async Task HandleRequestAsync(DocumentOnTypeFormatti return edits.ToArrayAndFree(); } - protected virtual Task?> GetFormattingChangesOnReturnAsync(IEditorFormattingService formattingService, Document document, int position, CancellationToken cancellationToken) - => formattingService.GetFormattingChangesOnReturnAsync(document, position, cancellationToken); + protected virtual async Task?> GetFormattingChangesOnReturnAsync( + IFormattingInteractionService formattingService, + Document document, + int position, + DocumentOptionSet documentOptions, + CancellationToken cancellationToken) + => await formattingService.GetFormattingChangesOnReturnAsync(document, position, documentOptions, cancellationToken).ConfigureAwait(false); - protected virtual Task?> GetFormattingChangesAsync(IEditorFormattingService formattingService, Document document, char typedChar, int position, CancellationToken cancellationToken) - => formattingService.GetFormattingChangesAsync(document, typedChar, position, cancellationToken); + protected virtual async Task?> GetFormattingChangesAsync( + IFormattingInteractionService formattingService, + Document document, + char typedChar, + int position, + DocumentOptionSet documentOptions, + CancellationToken cancellationToken) + => await formattingService.GetFormattingChangesAsync(document, typedChar, position, documentOptions, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs index ba6a8f0ae58eb..495c46ef2b4f2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs @@ -25,7 +25,10 @@ public FormatDocumentRangeHandler() public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentRangeFormattingParams request) => request.TextDocument; - public override Task HandleRequestAsync(DocumentRangeFormattingParams request, RequestContext context, CancellationToken cancellationToken) - => GetTextEditsAsync(context, cancellationToken, range: request.Range); + public override Task HandleRequestAsync( + DocumentRangeFormattingParams request, + RequestContext context, + CancellationToken cancellationToken) + => GetTextEditsAsync(context, request.Options, cancellationToken, range: request.Range); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs index 556b4e4ba5b85..260d849f4c042 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs @@ -3,12 +3,16 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler @@ -47,17 +51,45 @@ public HoverHandler() return null; } - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var hover = await GetHoverAsync(info, document, context.ClientCapabilities, cancellationToken).ConfigureAwait(false); + return hover; - // TODO - Switch to markup content once it supports classifications. - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138 - return new VSHover + static async Task GetHoverAsync(QuickInfoItem info, Document document, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) { - Range = ProtocolConversions.TextSpanToRange(info.Span, text), - Contents = new SumType, SumType[], MarkupContent>(string.Empty), - // Build the classified text without navigation actions - they are not serializable. - RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, document, cancellationToken).ConfigureAwait(false) - }; + var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability(); + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (supportsVSExtensions) + { + return new VSHover + { + Range = ProtocolConversions.TextSpanToRange(info.Span, text), + Contents = new SumType, SumType[], MarkupContent>(string.Empty), + // Build the classified text without navigation actions - they are not serializable. + // TODO - Switch to markup content once it supports classifications. + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138 + RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, document, cancellationToken).ConfigureAwait(false) + }; + } + else + { + return new Hover + { + Range = ProtocolConversions.TextSpanToRange(info.Span, text), + Contents = GetContents(info, document, clientCapabilities), + }; + } + } + + static MarkupContent GetContents(QuickInfoItem info, Document document, ClientCapabilities clientCapabilities) + { + var clientSupportsMarkdown = clientCapabilities?.TextDocument?.Hover?.ContentFormat.Contains(MarkupKind.Markdown) == true; + // Insert line breaks in between sections to ensure we get double spacing between sections. + var tags = info.Sections + .SelectMany(section => section.TaggedParts.Add(new TaggedText(TextTags.LineBreak, Environment.NewLine))) + .ToImmutableArray(); + return ProtocolConversions.GetDocumentationMarkupContent(tags, document, clientSupportsMarkdown); + } } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs index 35b26e03d937e..2df5956fc1226 100644 --- a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -46,7 +46,10 @@ public OnAutoInsertHandler( public override LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.DocumentOnAutoInsertParams request) => request.TextDocument; - public override async Task HandleRequestAsync(LSP.DocumentOnAutoInsertParams autoInsertParams, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync( + LSP.DocumentOnAutoInsertParams request, + RequestContext context, + CancellationToken cancellationToken) { var document = context.Document; if (document == null) @@ -57,16 +60,14 @@ public OnAutoInsertHandler( var service = document.GetRequiredLanguageService(); // We should use the options passed in by LSP instead of the document's options. - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var updatedOptions = options - .WithChangedOption(FormattingOptions.UseTabs, !autoInsertParams.Options.InsertSpaces) - .WithChangedOption(FormattingOptions.TabSize, autoInsertParams.Options.TabSize); + var documentOptions = await ProtocolConversions.FormattingOptionsToDocumentOptionsAsync( + request.Options, document, cancellationToken).ConfigureAwait(false); // The editor calls this handler for C# and VB comment characters, but we only need to process the one for the language that matches the document - if (autoInsertParams.Character == "\n" || autoInsertParams.Character == service.DocumentationCommentCharacter) + if (request.Character == "\n" || request.Character == service.DocumentationCommentCharacter) { var documentationCommentResponse = await GetDocumentationCommentResponseAsync( - autoInsertParams, document, service, updatedOptions, cancellationToken).ConfigureAwait(false); + request, document, service, documentOptions, cancellationToken).ConfigureAwait(false); if (documentationCommentResponse != null) { return documentationCommentResponse; @@ -76,10 +77,10 @@ public OnAutoInsertHandler( // Only support this for razor as LSP doesn't support overtype yet. // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1165179/ // Once LSP supports overtype we can move all of brace completion to LSP. - if (autoInsertParams.Character == "\n" && context.ClientName == document.Services.GetService()?.DiagnosticsLspClientName) + if (request.Character == "\n" && context.ClientName == document.Services.GetService()?.DiagnosticsLspClientName) { var braceCompletionAfterReturnResponse = await GetBraceCompletionAfterReturnResponseAsync( - autoInsertParams, document, updatedOptions, cancellationToken).ConfigureAwait(false); + request, document, documentOptions, cancellationToken).ConfigureAwait(false); if (braceCompletionAfterReturnResponse != null) { return braceCompletionAfterReturnResponse; diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs index c4e93f42cf263..a1d6b8f3ee1e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { [ExportLspRequestHandlerProvider, Shared] [ProvidesMethod(LSP.Methods.TextDocumentReferencesName)] - internal class FindAllReferencesHandler : AbstractStatelessRequestHandler + internal class FindAllReferencesHandler : AbstractStatelessRequestHandler { private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; @@ -37,7 +37,7 @@ public FindAllReferencesHandler(IMetadataAsSourceFileService metadataAsSourceFil public override TextDocumentIdentifier? GetTextDocumentIdentifier(ReferenceParams request) => request.TextDocument; - public override async Task HandleRequestAsync(ReferenceParams referenceParams, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync(ReferenceParams referenceParams, RequestContext context, CancellationToken cancellationToken) { Debug.Assert(context.ClientCapabilities.HasVisualStudioLspCapability()); @@ -57,8 +57,8 @@ public FindAllReferencesHandler(IMetadataAsSourceFileService metadataAsSourceFil progress, document, position, _metadataAsSourceFileService, cancellationToken); // Finds the references for the symbol at the specific position in the document, reporting them via streaming to the LSP client. - await findUsagesService.FindReferencesAsync(document, position, findUsagesContext).ConfigureAwait(false); - await findUsagesContext.OnCompletedAsync().ConfigureAwait(false); + await findUsagesService.FindReferencesAsync(document, position, findUsagesContext, cancellationToken).ConfigureAwait(false); + await findUsagesContext.OnCompletedAsync(cancellationToken).ConfigureAwait(false); return progress.GetValues(); } diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs index 0384707de5624..e8b8e2efac6a6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler @@ -39,12 +40,11 @@ public FindImplementationsHandler() return locations.ToArrayAndFree(); } - var findUsagesService = document.Project.LanguageServices.GetRequiredService(); + var findUsagesService = document.GetRequiredLanguageService(); var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); - var findUsagesContext = new SimpleFindUsagesContext(cancellationToken); - - await FindImplementationsAsync(findUsagesService, document, position, findUsagesContext).ConfigureAwait(false); + var findUsagesContext = new SimpleFindUsagesContext(); + await FindImplementationsAsync(findUsagesService, document, position, findUsagesContext, cancellationToken).ConfigureAwait(false); foreach (var definition in findUsagesContext.GetDefinitions()) { @@ -65,7 +65,7 @@ public FindImplementationsHandler() return locations.ToArrayAndFree(); } - protected virtual Task FindImplementationsAsync(IFindUsagesService findUsagesService, Document document, int position, SimpleFindUsagesContext context) - => findUsagesService.FindImplementationsAsync(document, position, context); + protected virtual Task FindImplementationsAsync(IFindUsagesService findUsagesService, Document document, int position, SimpleFindUsagesContext context, CancellationToken cancellationToken) + => findUsagesService.FindImplementationsAsync(document, position, context, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensEditsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensEditsHandler.cs index 3ef96058a9b2a..dc2ae512fc496 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensEditsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensEditsHandler.cs @@ -56,24 +56,31 @@ public SemanticTokensEditsHandler(SemanticTokensCache tokensCache) Contract.ThrowIfNull(newSemanticTokensData, "newSemanticTokensData is null."); - var resultId = _tokensCache.GetNextResultId(); - var newSemanticTokens = new LSP.SemanticTokens { ResultId = resultId, Data = newSemanticTokensData }; - - await _tokensCache.UpdateCacheAsync( - request.TextDocument.Uri, newSemanticTokens, cancellationToken).ConfigureAwait(false); - // Getting the cached tokens for the document. If we don't have an applicable cached token set, // we can't calculate edits, so we must return all semantic tokens instead. var oldSemanticTokensData = await _tokensCache.GetCachedTokensDataAsync( request.TextDocument.Uri, request.PreviousResultId, cancellationToken).ConfigureAwait(false); if (oldSemanticTokensData == null) { - return newSemanticTokens; + var newResultId = _tokensCache.GetNextResultId(); + return new LSP.SemanticTokens { ResultId = newResultId, Data = newSemanticTokensData }; + } + + var resultId = request.PreviousResultId; + var editArray = ComputeSemanticTokensEdits(oldSemanticTokensData, newSemanticTokensData); + + // If we have edits, generate a new ResultId. Otherwise, re-use the previous one. + if (editArray.Length != 0) + { + resultId = _tokensCache.GetNextResultId(); + var updatedTokens = new LSP.SemanticTokens { ResultId = resultId, Data = newSemanticTokensData }; + await _tokensCache.UpdateCacheAsync( + request.TextDocument.Uri, updatedTokens, cancellationToken).ConfigureAwait(false); } var edits = new SemanticTokensEdits { - Edits = ComputeSemanticTokensEdits(oldSemanticTokensData, newSemanticTokensData), + Edits = editArray, ResultId = resultId }; diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index 56661059c0687..46c9d8fdf7a47 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs @@ -292,16 +292,21 @@ private static int ComputeNextToken( while (classifiedSpans[currentClassifiedSpanIndex].TextSpan == originalTextSpan) { var classificationType = classifiedSpans[currentClassifiedSpanIndex].ClassificationType; - if (classificationType != ClassificationTypeNames.StaticSymbol) + if (classificationType == ClassificationTypeNames.StaticSymbol) { - // 4. Token type - looked up in SemanticTokensLegend.tokenTypes (language server defined mapping - // from integer to LSP token types). - tokenTypeIndex = GetTokenTypeIndex(classificationType, tokenTypesToIndex); + // 4. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers + modifierBits = TokenModifiers.Static; } - else + else if (classificationType == ClassificationTypeNames.ReassignedVariable) { // 5. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers - modifierBits = TokenModifiers.Static; + modifierBits = TokenModifiers.ReassignedVariable; + } + else + { + // 6. Token type - looked up in SemanticTokensLegend.tokenTypes (language server defined mapping + // from integer to LSP token types). + tokenTypeIndex = GetTokenTypeIndex(classificationType, tokenTypesToIndex); } // Break out of the loop if we have no more classified spans left, or if the next classified span has diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs index dbdabcb86d11a..8591b19b6f0d3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs @@ -15,5 +15,6 @@ internal enum TokenModifiers { None = 0, Static = 1, + ReassignedVariable = 2, } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs index 71fcfe8c202e1..97b24f1ed5cea 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; @@ -42,19 +42,13 @@ public override async Task HandleRequestAsync(DocumentSymbolParams req { var document = context.Document; if (document == null) - { return Array.Empty(); - } var navBarService = document.Project.LanguageServices.GetRequiredService(); var navBarItems = await navBarService.GetItemsAsync(document, supportsCodeGeneration: false, cancellationToken).ConfigureAwait(false); if (navBarItems.IsEmpty) - { return Array.Empty(); - } - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // TODO - Return more than 2 levels of symbols. @@ -62,22 +56,18 @@ public override async Task HandleRequestAsync(DocumentSymbolParams req using var _ = ArrayBuilder.GetInstance(out var symbols); if (context.ClientCapabilities?.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true) { + // only top level ones foreach (var item in navBarItems) - { - // only top level ones - symbols.AddIfNotNull(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false)); - } + symbols.AddIfNotNull(GetDocumentSymbol(item, text, cancellationToken)); } else { foreach (var item in navBarItems) { - symbols.AddIfNotNull(GetSymbolInformation(item, compilation, tree, document, text, cancellationToken, containerName: null)); + symbols.AddIfNotNull(GetSymbolInformation(item, document, text, containerName: null)); foreach (var childItem in item.ChildItems) - { - symbols.AddIfNotNull(GetSymbolInformation(childItem, compilation, tree, document, text, cancellationToken, item.Text)); - } + symbols.AddIfNotNull(GetSymbolInformation(childItem, document, text, item.Text)); } } @@ -88,128 +78,62 @@ public override async Task HandleRequestAsync(DocumentSymbolParams req /// /// Get a symbol information from a specified nav bar item. /// - private static SymbolInformation? GetSymbolInformation(RoslynNavigationBarItem item, Compilation compilation, SyntaxTree tree, Document document, - SourceText text, CancellationToken cancellationToken, string? containerName = null) + private static SymbolInformation? GetSymbolInformation( + RoslynNavigationBarItem item, Document document, SourceText text, string? containerName = null) { - if (item.Spans.IsEmpty) - { + if (item is not RoslynNavigationBarItem.SymbolItem symbolItem || symbolItem.Location.InDocumentInfo == null) return null; - } - - var location = GetLocation(item, compilation, tree, cancellationToken); - - if (location == null) - { - return Create(item, item.Spans.First(), containerName, document, text); - } - - return Create(item, location.SourceSpan, containerName, document, text); - static VSSymbolInformation Create(RoslynNavigationBarItem item, TextSpan span, string? containerName, Document document, SourceText text) + return new VSSymbolInformation { - return new VSSymbolInformation + Name = item.Text, + Location = new LSP.Location { - Name = item.Text, - Location = new LSP.Location - { - Uri = document.GetURI(), - Range = ProtocolConversions.TextSpanToRange(span, text), - }, - Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph), - ContainerName = containerName, - Icon = new ImageElement(item.Glyph.GetImageId()), - }; - } + Uri = document.GetURI(), + Range = ProtocolConversions.TextSpanToRange(symbolItem.Location.InDocumentInfo.Value.navigationSpan, text), + }, + Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph), + ContainerName = containerName, + Icon = new ImageElement(item.Glyph.GetImageId()), + }; } /// /// Get a document symbol from a specified nav bar item. /// - private static async Task GetDocumentSymbolAsync(RoslynNavigationBarItem item, Compilation compilation, SyntaxTree tree, - SourceText text, CancellationToken cancellationToken) + private static DocumentSymbol? GetDocumentSymbol( + RoslynNavigationBarItem item, SourceText text, CancellationToken cancellationToken) { - // it is actually symbol location getter. but anyway. - var location = GetLocation(item, compilation, tree, cancellationToken); - if (location == null) + if (item is not RoslynNavigationBarItem.SymbolItem symbolItem || + symbolItem.Location.InDocumentInfo == null) { return null; } - var symbol = await GetSymbolAsync(location, compilation, cancellationToken).ConfigureAwait(false); - if (symbol == null) - { + var inDocumentInfo = symbolItem.Location.InDocumentInfo.Value; + if (inDocumentInfo.spans.Length == 0) return null; - } return new DocumentSymbol { - Name = symbol.Name, + Name = symbolItem.Name, Detail = item.Text, Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph), - Deprecated = symbol.IsObsolete(), - Range = ProtocolConversions.TextSpanToRange(item.Spans.First(), text), - SelectionRange = ProtocolConversions.TextSpanToRange(location.SourceSpan, text), - Children = await GetChildrenAsync(item.ChildItems, compilation, tree, text, cancellationToken).ConfigureAwait(false), + Deprecated = symbolItem.IsObsolete, + Range = ProtocolConversions.TextSpanToRange(inDocumentInfo.spans.First(), text), + SelectionRange = ProtocolConversions.TextSpanToRange(inDocumentInfo.navigationSpan, text), + Children = GetChildren(item.ChildItems, text, cancellationToken), }; - static async Task GetChildrenAsync(IEnumerable items, Compilation compilation, SyntaxTree tree, - SourceText text, CancellationToken cancellationToken) + static DocumentSymbol[] GetChildren( + ImmutableArray items, SourceText text, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var list); foreach (var item in items) - { - list.AddIfNotNull(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false)); - } + list.AddIfNotNull(GetDocumentSymbol(item, text, cancellationToken)); return list.ToArray(); } - - static async Task GetSymbolAsync(Location location, Compilation compilation, CancellationToken cancellationToken) - { - var model = compilation.GetSemanticModel(location.SourceTree); - var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - var node = root.FindNode(location.SourceSpan); - - while (node != null) - { - var symbol = model.GetDeclaredSymbol(node, cancellationToken); - if (symbol != null) - { - return symbol; - } - - node = node.Parent; - } - - return null; - } - } - - /// - /// Get a location for a particular nav bar item. - /// - private static Location? GetLocation(RoslynNavigationBarItem item, Compilation compilation, SyntaxTree tree, CancellationToken cancellationToken) - { - if (item is not RoslynNavigationBarItem.SymbolItem symbolItem) - return null; - - var symbols = symbolItem.NavigationSymbolId.Resolve(compilation, cancellationToken: cancellationToken); - var symbol = symbols.Symbol; - - if (symbol == null) - { - if (symbolItem.NavigationSymbolIndex < symbols.CandidateSymbols.Length) - { - symbol = symbols.CandidateSymbols[symbolItem.NavigationSymbolIndex]; - } - else - { - return null; - } - } - - var location = symbol.Locations.FirstOrDefault(l => l.SourceTree?.Equals(tree) == true); - return location ?? symbol.Locations.FirstOrDefault(); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index cdcde12cbc943..387c09bc40886 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -105,7 +105,8 @@ public void ReportProgress(int current, int maximum) private async Task ReportSymbolInformationAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { - var location = await ProtocolConversions.TextSpanToLocationAsync(result.NavigableItem.Document, result.NavigableItem.SourceSpan, cancellationToken).ConfigureAwait(false); + var location = await ProtocolConversions.TextSpanToLocationAsync( + result.NavigableItem.Document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(location); _progress.Report(new VSSymbolInformation { diff --git a/src/Features/LanguageServer/Protocol/ICapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/ICapabilitiesProvider.cs new file mode 100644 index 0000000000000..c1cb95a3c9e8a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/ICapabilitiesProvider.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + internal interface ICapabilitiesProvider + { + ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities); + } +} diff --git a/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs b/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs new file mode 100644 index 0000000000000..c2e58e0c7500a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.LanguageServer.Protocol; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + internal interface ILanguageServerFactory + { + public ILanguageServerTarget Create( + JsonRpc jsonRpc, + ICapabilitiesProvider capabilitiesProvider, + ILspWorkspaceRegistrationService workspaceRegistrationService, + ILspLogger logger); + } +} diff --git a/src/Features/LanguageServer/Protocol/ILanguageServerTarget.cs b/src/Features/LanguageServer/Protocol/ILanguageServerTarget.cs new file mode 100644 index 0000000000000..1aaeefbccdbbf --- /dev/null +++ b/src/Features/LanguageServer/Protocol/ILanguageServerTarget.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + internal interface ILanguageServerTarget : IAsyncDisposable + { + } +} diff --git a/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs b/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs new file mode 100644 index 0000000000000..5b48c9e55a3e4 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs @@ -0,0 +1,416 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Roslyn.Utilities; +using StreamJsonRpc; + +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer +{ + internal class LanguageServerTarget : ILanguageServerTarget + { + private readonly ICapabilitiesProvider _capabilitiesProvider; + + protected readonly JsonRpc JsonRpc; + protected readonly RequestDispatcher RequestDispatcher; + protected readonly RequestExecutionQueue Queue; + protected readonly ILspWorkspaceRegistrationService WorkspaceRegistrationService; + protected readonly IAsynchronousOperationListener Listener; + protected readonly ILspLogger Logger; + protected readonly string? ClientName; + + /// + /// Server name used when sending error messages to the client. + /// + private readonly string _userVisibleServerName; + + /// + /// Server name used when capturing error telemetry. + /// + protected readonly string TelemetryServerName; + + // Set on first LSP initialize request. + protected ClientCapabilities? _clientCapabilities; + + // Fields used during shutdown. + private bool _shuttingDown; + private Task? _errorShutdownTask; + + internal bool HasShutdownStarted => _shuttingDown; + + internal LanguageServerTarget( + AbstractRequestDispatcherFactory requestDispatcherFactory, + JsonRpc jsonRpc, + ICapabilitiesProvider capabilitiesProvider, + ILspWorkspaceRegistrationService workspaceRegistrationService, + IAsynchronousOperationListenerProvider listenerProvider, + ILspLogger logger, + string? clientName, + string userVisibleServerName, + string telemetryServerTypeName) + { + RequestDispatcher = requestDispatcherFactory.CreateRequestDispatcher(); + + _capabilitiesProvider = capabilitiesProvider; + WorkspaceRegistrationService = workspaceRegistrationService; + Logger = logger; + + JsonRpc = jsonRpc; + JsonRpc.AddLocalRpcTarget(this); + + Listener = listenerProvider.GetListener(FeatureAttribute.LanguageServer); + ClientName = clientName; + + _userVisibleServerName = userVisibleServerName; + TelemetryServerName = telemetryServerTypeName; + + Queue = new RequestExecutionQueue(logger, workspaceRegistrationService, userVisibleServerName, TelemetryServerName); + Queue.RequestServerShutdown += RequestExecutionQueue_Errored; + } + + /// + /// Handle the LSP initialize request by storing the client capabilities and responding with the server + /// capabilities. The specification assures that the initialize request is sent only once. + /// + [JsonRpcMethod(Methods.InitializeName, UseSingleObjectParameterDeserialization = true)] + public Task InitializeAsync(InitializeParams initializeParams, CancellationToken cancellationToken) + { + try + { + Logger?.TraceStart("Initialize"); + + Contract.ThrowIfTrue(_clientCapabilities != null, $"{nameof(InitializeAsync)} called multiple times"); + _clientCapabilities = initializeParams.Capabilities; + return Task.FromResult(new InitializeResult + { + Capabilities = _capabilitiesProvider.GetCapabilities(_clientCapabilities), + }); + } + finally + { + Logger?.TraceStop("Initialize"); + } + } + + [JsonRpcMethod(Methods.InitializedName)] + public virtual Task InitializedAsync() + { + return Task.CompletedTask; + } + + [JsonRpcMethod(Methods.ShutdownName)] + public Task ShutdownAsync(CancellationToken _) + { + try + { + Logger?.TraceStart("Shutdown"); + + ShutdownImpl(); + + return Task.CompletedTask; + } + finally + { + Logger?.TraceStop("Shutdown"); + } + } + + protected virtual void ShutdownImpl() + { + Contract.ThrowIfTrue(_shuttingDown, "Shutdown has already been called."); + + _shuttingDown = true; + + ShutdownRequestQueue(); + } + + [JsonRpcMethod(Methods.ExitName)] + public Task ExitAsync(CancellationToken _) + { + try + { + Logger?.TraceStart("Exit"); + + ExitImpl(); + + return Task.CompletedTask; + } + finally + { + Logger?.TraceStop("Exit"); + } + } + + private void ExitImpl() + { + try + { + ShutdownRequestQueue(); + JsonRpc.Dispose(); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + // Swallow exceptions thrown by disposing our JsonRpc object. Disconnected events can potentially throw their own exceptions so + // we purposefully ignore all of those exceptions in an effort to shutdown gracefully. + } + } + + [JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDefinitionName, + textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentRenameName, + renameParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentReferencesName, + referencesParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentCodeActionName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentCodeActionsAsync(CodeActionParams codeActionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentCodeActionName, codeActionParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)] + public async Task> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + // Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698 + return await RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentCompletionName, + completionParams, _clientCapabilities, ClientName, cancellationToken).ConfigureAwait(false); + } + + [JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)] + public Task ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentCompletionResolveName, completionItem, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentFoldingRangeName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentFoldingRangeAsync(FoldingRangeParams textDocumentFoldingRangeParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentFoldingRangeName, textDocumentFoldingRangeParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDocumentHighlightName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentHoverName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDocumentSymbolName, documentSymbolParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentFormattingName, documentFormattingParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentOnTypeFormattingName, documentOnTypeFormattingParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentImplementationName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentRangeFormattingName, documentRangeFormattingParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.WorkspaceExecuteCommandName, UseSingleObjectParameterDeserialization = true)] + public Task ExecuteWorkspaceCommandAsync(ExecuteCommandParams executeCommandParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.WorkspaceExecuteCommandName, executeCommandParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)] + public Task GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.WorkspaceSymbolName, workspaceSymbolParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentSemanticTokensAsync(SemanticTokensParams semanticTokensParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, SemanticTokensMethods.TextDocumentSemanticTokensName, + semanticTokensParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensEditsName, UseSingleObjectParameterDeserialization = true)] + public Task> GetTextDocumentSemanticTokensEditsAsync(SemanticTokensEditsParams semanticTokensEditsParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync>(Queue, SemanticTokensMethods.TextDocumentSemanticTokensEditsName, + semanticTokensEditsParams, _clientCapabilities, ClientName, cancellationToken); + } + + // Note: Since a range request is always received in conjunction with a whole document request, we don't need to cache range results. + [JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensRangeName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentSemanticTokensRangeAsync(SemanticTokensRangeParams semanticTokensRangeParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, SemanticTokensMethods.TextDocumentSemanticTokensRangeName, + semanticTokensRangeParams, _clientCapabilities, ClientName, cancellationToken); + } + + // Temporary workaround to specify the actual public LSP method name until the LSP protocol package updates. + // Tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1249055 + [JsonRpcMethod("textDocument/semanticTokens/full", UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentSemanticTokensPublicAsync(SemanticTokensParams semanticTokensParams, CancellationToken cancellationToken) + => GetTextDocumentSemanticTokensAsync(semanticTokensParams, cancellationToken); + + // Temporary workaround to specify the actual public LSP method name until the LSP protocol package updates. + // Tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1249055 + [JsonRpcMethod("textDocument/semanticTokens/full/delta", UseSingleObjectParameterDeserialization = true)] + public Task> GetTextDocumentSemanticTokensEditsPublicAsync(SemanticTokensEditsParams semanticTokensEditsParams, CancellationToken cancellationToken) + => GetTextDocumentSemanticTokensEditsAsync(semanticTokensEditsParams, cancellationToken); + + [JsonRpcMethod(Methods.TextDocumentDidChangeName, UseSingleObjectParameterDeserialization = true)] + public Task HandleDocumentDidChangeAsync(DidChangeTextDocumentParams didChangeParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDidChangeName, + didChangeParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentDidOpenName, UseSingleObjectParameterDeserialization = true)] + public Task HandleDocumentDidOpenAsync(DidOpenTextDocumentParams didOpenParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDidOpenName, + didOpenParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(Methods.TextDocumentDidCloseName, UseSingleObjectParameterDeserialization = true)] + public Task HandleDocumentDidCloseAsync(DidCloseTextDocumentParams didCloseParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDidCloseName, + didCloseParams, _clientCapabilities, ClientName, cancellationToken); + } + + private void ShutdownRequestQueue() + { + Queue.RequestServerShutdown -= RequestExecutionQueue_Errored; + // if the queue requested shutdown via its event, it will have already shut itself down, but this + // won't cause any problems calling it again + Queue.Shutdown(); + } + + private void RequestExecutionQueue_Errored(object? sender, RequestShutdownEventArgs e) + { + // log message and shut down + Logger?.TraceWarning($"Request queue is requesting shutdown due to error: {e.Message}"); + + var message = new LogMessageParams() + { + MessageType = MessageType.Error, + Message = e.Message + }; + + var asyncToken = Listener.BeginAsyncOperation(nameof(RequestExecutionQueue_Errored)); + _errorShutdownTask = Task.Run(async () => + { + Logger?.TraceInformation("Shutting down language server."); + + await JsonRpc.NotifyWithParameterObjectAsync(Methods.WindowLogMessageName, message).ConfigureAwait(false); + + ShutdownImpl(); + ExitImpl(); + }).CompletesAsyncOperation(asyncToken); + } + + public async ValueTask DisposeAsync() + { + // if the server shut down due to error, we might not have finished cleaning up + if (_errorShutdownTask is not null) + await _errorShutdownTask.ConfigureAwait(false); + + if (Logger is IDisposable disposableLogger) + disposableLogger.Dispose(); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 4e1e771e06dc1..3364bab9cae2e 100644 --- a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -36,6 +36,8 @@ + + diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index c7b3100e3c9ef..7073c2029d2ab 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -47,7 +47,7 @@ void M() caretLocation, customTags: new[] { PredefinedCodeRefactoringProviderNames.UseImplicitType }), priority: PriorityLevel.Low, - groupName: "Roslyn1", + groupName: "Roslyn4", applicableRange: new LSP.Range { Start = new Position { Line = 4, Character = 8 }, End = new Position { Line = 4, Character = 11 } }, diagnostics: null); @@ -204,9 +204,9 @@ private static void AssertRangeAndDocEqual( LSP.Location caret, LSP.ClientCapabilities clientCapabilities = null) { - var result = await testLspServer.ExecuteRequestAsync( + var result = await testLspServer.ExecuteRequestAsync( LSP.Methods.TextDocumentCodeActionName, CreateCodeActionParams(caret), clientCapabilities, null, CancellationToken.None); - return result; + return result.Cast().ToArray(); } internal static LSP.CodeActionParams CreateCodeActionParams(LSP.Location caret) @@ -246,7 +246,7 @@ internal static LSP.VSCodeAction CreateCodeAction( private static CodeActionsCache GetCodeActionsCache(TestLspServer testLspServer) { var dispatchAccessor = testLspServer.GetDispatcherAccessor(); - var handler = (CodeActionsHandler)dispatchAccessor.GetHandler(LSP.Methods.TextDocumentCodeActionName); + var handler = (CodeActionsHandler)dispatchAccessor.GetHandler(LSP.Methods.TextDocumentCodeActionName); Assert.NotNull(handler); var cache = handler.GetTestAccessor().GetCache(); return Assert.IsType(cache); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs index f7abd5fb03813..65e3fd640b1f9 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -24,7 +25,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Completion public class CompletionResolveTests : AbstractLanguageServerProtocolTests { [Fact] - public async Task TestResolveCompletionItemAsync() + public async Task TestResolveCompletionItemFromListAsync() { var markup = @"class A @@ -35,19 +36,27 @@ 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 completionList = await RunGetCompletionsAsync(testLspServer, completionParams); - var serverCompletionItem = completionList.Items.FirstOrDefault(item => item.Label == "A"); - var completionResultId = ((CompletionResolveData)serverCompletionItem.Data).ResultId.Value; - var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); - var clientCompletionItem = ConvertToClientCompletionItem(serverCompletionItem); + var clientCapabilities = new LSP.VSClientCapabilities + { + SupportsVisualStudioExtensions = true, + TextDocument = new TextDocumentClientCapabilities() + { + Completion = new VSCompletionSetting() + { + CompletionList = new VSCompletionListSetting() + { + Data = true, + } + } + } + }; + var clientCompletionItem = await GetCompletionItemToResolveAsync( + testLspServer, + locations, + label: "A", + clientCapabilities).ConfigureAwait(false); var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); - var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; - var expected = CreateResolvedCompletionItem(clientCompletionItem, description, "class A", null); var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( @@ -55,6 +64,28 @@ void M() AssertJsonEquals(expected, results); } + [Fact] + public async Task TestResolveCompletionItemAsync() + { + var markup = +@"class A +{ + void M() + { + {|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var clientCompletionItem = await GetCompletionItemToResolveAsync(testLspServer, locations, label: "A").ConfigureAwait(false); + + var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); + var expected = CreateResolvedCompletionItem(clientCompletionItem, description, "class A", null); + + var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( + testLspServer, clientCompletionItem).ConfigureAwait(false); + AssertJsonEquals(expected, results); + } + [Fact] [WorkItem(51125, "https://github.com/dotnet/roslyn/issues/51125")] public async Task TestResolveOverridesCompletionItemAsync() @@ -70,18 +101,9 @@ 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 completionList = await RunGetCompletionsAsync(testLspServer, completionParams); - var serverCompletionItem = completionList.Items.FirstOrDefault(item => item.Label == "M()"); - var completionResultId = ((CompletionResolveData)serverCompletionItem.Data).ResultId.Value; - var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); - var clientCompletionItem = ConvertToClientCompletionItem(serverCompletionItem); - var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; - + var clientCompletionItem = await GetCompletionItemToResolveAsync(testLspServer, locations, label: "M()").ConfigureAwait(false); var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( - testLspServer, clientCompletionItem, clientCapabilities).ConfigureAwait(false); + testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.NotNull(results.TextEdit); Assert.Null(results.InsertText); @@ -106,15 +128,6 @@ 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 completionList = await RunGetCompletionsAsync(testLspServer, completionParams); - var serverCompletionItem = completionList.Items.FirstOrDefault(item => item.Label == "M()"); - var completionResultId = ((CompletionResolveData)serverCompletionItem.Data).ResultId.Value; - var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); - var clientCompletionItem = ConvertToClientCompletionItem(serverCompletionItem); - // 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 { @@ -130,6 +143,11 @@ class B : A } } }; + var clientCompletionItem = await GetCompletionItemToResolveAsync( + testLspServer, + locations, + label: "M()", + clientCapabilities).ConfigureAwait(false); var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem, clientCapabilities).ConfigureAwait(false); @@ -157,7 +175,6 @@ class B : A override {|caret:|} }"; using var testLspServer = CreateTestLspServer(markup, out _); - var tags = new string[] { "Method", "Public" }; var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); @@ -171,8 +188,169 @@ class B : A }", textEdit.NewText); } - private static async Task RunResolveCompletionItemAsync(TestLspServer testLspServer, LSP.CompletionItem completionItem, LSP.ClientCapabilities clientCapabilities = null) + [Fact] + public async Task TestResolveCompletionItemWithMarkupContentAsync() { + var markup = +@" +class A +{ + /// + /// A cref + ///
+ /// strong text + ///
+ /// italic text + ///
+ /// underline text + /// + /// + /// + /// Item 1. + /// + /// + /// Item 2. + /// + /// + /// link text + /// + ///
+ void AMethod(int i) + { + } + + void M() + { + AMet{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var clientCompletionItem = await GetCompletionItemToResolveAsync( + testLspServer, + locations, + label: "AMethod", + new ClientCapabilities()).ConfigureAwait(false); + Assert.True(clientCompletionItem is not VSCompletionItem); + + var expected = @"```csharp +void A.AMethod(int i) +``` + +A cref A\.AMethod\(int\) +**strong text** +_italic text_ +underline text + +• Item 1\. +• Item 2\. + +[link text](https://google.com)"; + + var results = await RunResolveCompletionItemAsync( + testLspServer, + clientCompletionItem, + new ClientCapabilities + { + TextDocument = new TextDocumentClientCapabilities + { + Completion = new CompletionSetting + { + CompletionItem = new CompletionItemSetting + { + DocumentationFormat = new MarkupKind[] { MarkupKind.Markdown } + } + } + } + }).ConfigureAwait(false); + Assert.Equal(expected, results.Documentation.Value.Second.Value); + } + + [Fact] + public async Task TestResolveCompletionItemWithPlainTextAsync() + { + var markup = +@" +class A +{ + /// + /// A cref + ///
+ /// strong text + ///
+ /// italic text + ///
+ /// underline text + /// + /// + /// + /// Item 1. + /// + /// + /// Item 2. + /// + /// + /// link text + /// + ///
+ void AMethod(int i) + { + } + + void M() + { + AMet{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var clientCompletionItem = await GetCompletionItemToResolveAsync( + testLspServer, + locations, + label: "AMethod", + new ClientCapabilities()).ConfigureAwait(false); + Assert.True(clientCompletionItem is not VSCompletionItem); + + var expected = @"void A.AMethod(int i) +A cref A.AMethod(int) +strong text +italic text +underline text + +• Item 1. +• Item 2. + +link text"; + + var results = await RunResolveCompletionItemAsync( + testLspServer, + clientCompletionItem, + new ClientCapabilities()).ConfigureAwait(false); + Assert.Equal(expected, results.Documentation.Value.Second.Value); + } + + [Fact] + public async Task TestResolveCompletionItemWithPrefixSuffixAsync() + { + var markup = +@"class A +{ + void M() + { + var a = 10; + a.{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var clientCompletionItem = await GetCompletionItemToResolveAsync(testLspServer, locations, label: "(byte)").ConfigureAwait(false); + + var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( + testLspServer, clientCompletionItem).ConfigureAwait(false); + Assert.Equal("(byte)", results.Label); + Assert.NotNull(results.Description); + } + + private static async Task RunResolveCompletionItemAsync(TestLspServer testLspServer, LSP.CompletionItem completionItem, LSP.ClientCapabilities clientCapabilities = null) + { + clientCapabilities ??= new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionResolveName, completionItem, clientCapabilities, null, CancellationToken.None); } @@ -205,17 +383,56 @@ private static ClassifiedTextRun[] CreateClassifiedTextRunForClass(string classN new ClassifiedTextRun("class name", className) }; - private static async Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) + private static async Task GetCompletionItemToResolveAsync( + TestLspServer testLspServer, + Dictionary> locations, + string label, + LSP.ClientCapabilities clientCapabilities = null) where T : LSP.CompletionItem + { + var completionParams = CreateCompletionParams( + locations["caret"].Single(), LSP.VSCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); + + clientCapabilities ??= new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; + var completionList = await RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities); + + if (clientCapabilities.HasCompletionListDataCapability()) + { + var vsCompletionList = Assert.IsAssignableFrom(completionList); + Assert.NotNull(vsCompletionList.Data); + } + + var serverCompletionItem = completionList.Items.FirstOrDefault(item => item.Label == label); + var clientCompletionItem = ConvertToClientCompletionItem((T)serverCompletionItem); + return clientCompletionItem; + } + + private static async Task RunGetCompletionsAsync( + TestLspServer testLspServer, + LSP.CompletionParams completionParams, + LSP.ClientCapabilities clientCapabilities) { - var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; - return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, + var completionList = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, completionParams, clientCapabilities, null, CancellationToken.None); + + // Emulate client behavior of promoting "Data" completion list properties onto completion items. + if (clientCapabilities.HasCompletionListDataCapability() && + completionList is VSCompletionList vsCompletionList && + vsCompletionList.Data != null) + { + foreach (var completionItem in completionList.Items) + { + Assert.Null(completionItem.Data); + completionItem.Data = vsCompletionList.Data; + } + } + + return completionList; } - private static LSP.VSCompletionItem ConvertToClientCompletionItem(LSP.CompletionItem serverCompletionItem) + private static T ConvertToClientCompletionItem(T serverCompletionItem) where T : LSP.CompletionItem { var serializedItem = JsonConvert.SerializeObject(serverCompletionItem); - var clientCompletionItem = JsonConvert.DeserializeObject(serializedItem); + var clientCompletionItem = JsonConvert.DeserializeObject(serializedItem); return clientCompletionItem; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index a93ccbb3301a4..176087b2ee5d7 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -19,6 +19,54 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Completion { public class CompletionTests : AbstractLanguageServerProtocolTests { + + [Fact] + public async Task TestGetCompletionsAsync_PromotesCommitCharactersToListAsync() + { + var clientCapabilities = new LSP.VSClientCapabilities + { + SupportsVisualStudioExtensions = true, + TextDocument = new LSP.TextDocumentClientCapabilities() + { + Completion = new LSP.VSCompletionSetting() + { + CompletionList = new LSP.VSCompletionListSetting() + { + CommitCharacters = true, + } + } + } + }; + var markup = +@"class A +{ + void M() + { + {|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 expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: new string[] { "Class", "Internal" }, + request: completionParams, document: document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters, insertText: "A").ConfigureAwait(false); + var expectedCommitCharacters = expected.CommitCharacters; + + // Null out the commit characters since we're expecting the commit characters will be lifted onto the completion list. + expected.CommitCharacters = null; + + var results = await RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities).ConfigureAwait(false); + AssertJsonEquals(expected, results.Items.First()); + var vsCompletionList = Assert.IsAssignableFrom(results); + Assert.Equal(expectedCommitCharacters, vsCompletionList.CommitCharacters.Value.First); + } + [Fact] public async Task TestGetCompletionsAsync() { @@ -442,9 +490,71 @@ partial class C Assert.Null(results.Items.First().InsertText); } - private static async Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) + [Fact] + public async Task TestAlwaysHasCommitCharactersWithoutVSCapabilityAsync() + { + var markup = +@"using System; +class A +{ + void M() + { + {|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, new LSP.VSClientCapabilities()).ConfigureAwait(false); + Assert.NotNull(results); + Assert.NotEmpty(results.Items); + Assert.All(results.Items, (item) => Assert.NotNull(item.CommitCharacters)); + } + + [Fact] + public async Task TestSoftSelectedItemsHaveNoCommitCharactersWithoutVSCapabilityAsync() + { + 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.Typing, + triggerCharacter: "[", + triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams, new LSP.VSClientCapabilities()).ConfigureAwait(false); + Assert.NotNull(results); + Assert.NotEmpty(results.Items); + Assert.All(results.Items, (item) => Assert.True(item.CommitCharacters.Length == 0)); + } + + private static Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; + return RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities); + } + + private static async Task RunGetCompletionsAsync( + TestLspServer testLspServer, + LSP.CompletionParams completionParams, + LSP.VSClientCapabilities clientCapabilities) + { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, completionParams, clientCapabilities, null, CancellationToken.None); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs index 0fc478b82ee71..828c26902b33d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs @@ -46,13 +46,54 @@ void M() Assert.Equal(expected, actualText); } - private static async Task RunFormatDocumentOnTypeAsync(TestLspServer testLspServer, string characterTyped, LSP.Location locationTyped) + [Fact] + public async Task TestFormatDocumentOnType_UseTabsAsync() + { + var markup = +@"class A +{ + void M() + { + if (true) + {{|type:|} + } +}"; + var expected = +@"class A +{ + void M() + { + if (true) + { + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var characterTyped = ";"; + var locationTyped = locations["type"].Single(); + var documentText = await testLspServer.GetCurrentSolution().GetDocuments(locationTyped.Uri).Single().GetTextAsync(); + + var results = await RunFormatDocumentOnTypeAsync(testLspServer, characterTyped, locationTyped, insertSpaces: false, tabSize: 4); + var actualText = ApplyTextEdits(results, documentText); + Assert.Equal(expected, actualText); + } + + private static async Task RunFormatDocumentOnTypeAsync( + TestLspServer testLspServer, + string characterTyped, + LSP.Location locationTyped, + bool insertSpaces = true, + int tabSize = 4) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentOnTypeFormattingName, - CreateDocumentOnTypeFormattingParams(characterTyped, locationTyped), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateDocumentOnTypeFormattingParams( + characterTyped, locationTyped, insertSpaces, tabSize), new LSP.ClientCapabilities(), null, CancellationToken.None); } - private static LSP.DocumentOnTypeFormattingParams CreateDocumentOnTypeFormattingParams(string characterTyped, LSP.Location locationTyped) + private static LSP.DocumentOnTypeFormattingParams CreateDocumentOnTypeFormattingParams( + string characterTyped, + LSP.Location locationTyped, + bool insertSpaces, + int tabSize) => new LSP.DocumentOnTypeFormattingParams() { Position = locationTyped.Range.Start, @@ -60,7 +101,8 @@ private static LSP.DocumentOnTypeFormattingParams CreateDocumentOnTypeFormatting TextDocument = CreateTextDocumentIdentifier(locationTyped.Uri), Options = new LSP.FormattingOptions() { - // TODO - Format should respect formatting options. + InsertSpaces = insertSpaces, + TabSize = tabSize, } }; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs index d1a22c557e820..31017d8a73785 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs @@ -43,20 +43,60 @@ void M() Assert.Equal(expected, actualText); } - private static async Task RunFormatDocumentRangeAsync(TestLspServer testLspServer, LSP.Location location) + [Fact] + public async Task TestFormatDocumentRange_UseTabsAsync() + { + var markup = +@"class A +{ +{|format:void|} M() +{ + int i = 1; + } +}"; + var expected = +@"class A +{ + void M() +{ + int i = 1; + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var rangeToFormat = locations["format"].Single(); + var documentText = await testLspServer.GetCurrentSolution().GetDocuments(rangeToFormat.Uri).Single().GetTextAsync(); + + var results = await RunFormatDocumentRangeAsync(testLspServer, rangeToFormat, insertSpaces: false, tabSize: 4); + var actualText = ApplyTextEdits(results, documentText); + Assert.Equal(expected, actualText); + } + + private static async Task RunFormatDocumentRangeAsync( + TestLspServer testLspServer, + LSP.Location location, + bool insertSpaces = true, + int tabSize = 4) { - return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentRangeFormattingName, - CreateDocumentRangeFormattingParams(location), new LSP.ClientCapabilities(), null, CancellationToken.None); + return await testLspServer.ExecuteRequestAsync( + LSP.Methods.TextDocumentRangeFormattingName, + CreateDocumentRangeFormattingParams(location, insertSpaces, tabSize), + new LSP.ClientCapabilities(), + clientName: null, + CancellationToken.None); } - private static LSP.DocumentRangeFormattingParams CreateDocumentRangeFormattingParams(LSP.Location location) + private static LSP.DocumentRangeFormattingParams CreateDocumentRangeFormattingParams( + LSP.Location location, + bool insertSpaces, + int tabSize) => new LSP.DocumentRangeFormattingParams() { Range = location.Range, TextDocument = CreateTextDocumentIdentifier(location.Uri), Options = new LSP.FormattingOptions() { - // TODO - Format should respect formatting options. + InsertSpaces = insertSpaces, + TabSize = tabSize } }; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs index 22fb03b017453..0eda1d3148090 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs @@ -44,19 +44,80 @@ void M() Assert.Equal(expected, actualText); } - private static async Task RunFormatDocumentAsync(TestLspServer testLspServer, Uri uri) + [Fact] + public async Task TestFormatDocument_UseTabsAsync() + { + var markup = +@"class A +{ +void M() +{ + int i = 1;{|caret:|} + } +}"; + var expected = +@"class A +{ + void M() + { + int i = 1; + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var documentURI = locations["caret"].Single().Uri; + var documentText = await testLspServer.GetCurrentSolution().GetDocuments(documentURI).Single().GetTextAsync(); + + var results = await RunFormatDocumentAsync(testLspServer, documentURI, insertSpaces: false, tabSize: 4); + var actualText = ApplyTextEdits(results, documentText); + Assert.Equal(expected, actualText); + } + + [Fact] + public async Task TestFormatDocument_ModifyTabIndentSizeAsync() + { + var markup = +@"class A +{ +void M() +{ + int i = 1;{|caret:|} + } +}"; + var expected = +@"class A +{ + void M() + { + int i = 1; + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var documentURI = locations["caret"].Single().Uri; + var documentText = await testLspServer.GetCurrentSolution().GetDocuments(documentURI).Single().GetTextAsync(); + + var results = await RunFormatDocumentAsync(testLspServer, documentURI, insertSpaces: true, tabSize: 2); + var actualText = ApplyTextEdits(results, documentText); + Assert.Equal(expected, actualText); + } + + private static async Task RunFormatDocumentAsync( + TestLspServer testLspServer, + Uri uri, + bool insertSpaces = true, + int tabSize = 4) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentFormattingName, - CreateDocumentFormattingParams(uri), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateDocumentFormattingParams(uri, insertSpaces, tabSize), new LSP.ClientCapabilities(), null, CancellationToken.None); } - private static LSP.DocumentFormattingParams CreateDocumentFormattingParams(Uri uri) + private static LSP.DocumentFormattingParams CreateDocumentFormattingParams(Uri uri, bool insertSpaces, int tabSize) => new LSP.DocumentFormattingParams() { TextDocument = CreateTextDocumentIdentifier(uri), Options = new LSP.FormattingOptions() { - // TODO - Format should respect formatting options. + InsertSpaces = insertSpaces, + TabSize = tabSize, } }; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs index 1988f9018537e..c0825777a1577 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs @@ -37,7 +37,7 @@ public async Task TestGetHoverAsync() var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); - VerifyContent(results, $"string A.Method(int i)|A great method|{FeaturesResources.Returns_colon}| |a string"); + VerifyVSContent(results, $"string A.Method(int i)|A great method|{FeaturesResources.Returns_colon}| |a string"); } [Fact] @@ -60,7 +60,7 @@ public async Task TestGetHoverAsync_WithExceptions() var expectedLocation = locations["caret"].Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); - VerifyContent(results, $"string A.Method(int i)|A great method|{FeaturesResources.Exceptions_colon}| System.NullReferenceException"); + VerifyVSContent(results, $"string A.Method(int i)|A great method|{FeaturesResources.Exceptions_colon}| System.NullReferenceException"); } [Fact] @@ -83,7 +83,7 @@ public async Task TestGetHoverAsync_WithRemarks() var expectedLocation = locations["caret"].Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); - VerifyContent(results, "string A.Method(int i)|A great method|Remarks are cool too."); + VerifyVSContent(results, "string A.Method(int i)|A great method|Remarks are cool too."); } [Fact] @@ -111,7 +111,7 @@ public async Task TestGetHoverAsync_WithList() var expectedLocation = locations["caret"].Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); - VerifyContent(results, "string A.Method(int i)|A great method|• |Item 1.|• |Item 2."); + VerifyVSContent(results, "string A.Method(int i)|A great method|• |Item 1.|• |Item 2."); } [Fact] @@ -187,19 +187,221 @@ static void Main(string[] args) var result = await RunGetHoverAsync(testLspServer, location, project.Id); var expectedConstant = project.Name == "Net472" ? "Target in net472" : "Target in netcoreapp3.1"; - VerifyContent(result, $"({FeaturesResources.constant}) string WithConstant.Target = \"{expectedConstant}\""); + VerifyVSContent(result, $"({FeaturesResources.constant}) string WithConstant.Target = \"{expectedConstant}\""); } } - private static async Task RunGetHoverAsync(TestLspServer testLspServer, LSP.Location caret, ProjectId projectContext = null) + [Fact] + public async Task TestGetHoverAsync_UsingMarkupContent() + { + var markup = +@"class A +{ + /// + /// A cref + ///
+ /// strong text + ///
+ /// italic text + ///
+ /// underline text + /// + /// + /// + /// Item 1. + /// + /// + /// Item 2. + /// + /// + /// link text + /// + ///
+ /// + /// Oh no! + /// + /// an int + /// a string + /// + /// Remarks are cool too. + /// + void {|caret:AMethod|}(int i) + { + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var expectedLocation = locations["caret"].Single(); + + var expectedMarkdown = @$"```csharp +void A.AMethod(int i) +``` + +A cref A\.AMethod\(int\) +**strong text** +_italic text_ +underline text + +• Item 1\. +• Item 2\. + +[link text](https://google.com) + +Remarks are cool too\. + +{FeaturesResources.Returns_colon} +  a string + +{FeaturesResources.Exceptions_colon} +  System\.NullReferenceException +"; + + var results = await RunGetHoverAsync( + testLspServer, + expectedLocation, + clientCapabilities: new LSP.ClientCapabilities + { + TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = new LSP.MarkupKind[] { LSP.MarkupKind.Markdown } } } + }).ConfigureAwait(false); + Assert.Equal(expectedMarkdown, results.Contents.Third.Value); + } + + [Fact] + public async Task TestGetHoverAsync_WithoutMarkdownClientSupport() + { + var markup = +@"class A +{ + /// + /// A cref + ///
+ /// strong text + ///
+ /// italic text + ///
+ /// underline text + /// + /// + /// + /// Item 1. + /// + /// + /// Item 2. + /// + /// + /// link text + /// + ///
+ /// + /// Oh no! + /// + /// an int + /// a string + /// + /// Remarks are cool too. + /// + void {|caret:AMethod|}(int i) + { + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var expectedLocation = locations["caret"].Single(); + + var expectedText = @$"void A.AMethod(int i) +A cref A.AMethod(int) +strong text +italic text +underline text + +• Item 1. +• Item 2. + +link text + +Remarks are cool too. + +{FeaturesResources.Returns_colon} + a string + +{FeaturesResources.Exceptions_colon} + System.NullReferenceException +"; + + var results = await RunGetHoverAsync( + testLspServer, + expectedLocation, + clientCapabilities: new LSP.ClientCapabilities()).ConfigureAwait(false); + Assert.Equal(expectedText, results.Contents.Third.Value); + } + + [Fact] + public async Task TestGetHoverAsync_UsingMarkupContentProperlyEscapes() + { + var markup = +@"class A +{ + /// + /// Some {curly} [braces] and (parens) + ///
+ /// #Hashtag + ///
+ /// 1 + 1 - 1 + ///
+ /// Period. + ///
+ /// Exclaim! + ///
+ /// strong\** text + ///
+ /// italic_ **text** + ///
+ /// closing] link + ///
+ void {|caret:AMethod|}(int i) + { + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var expectedLocation = locations["caret"].Single(); + + var expectedMarkdown = @"```csharp +void A.AMethod(int i) +``` + +Some \{curly\} \[braces\] and \(parens\) +\#Hashtag +1 \+ 1 \- 1 +Period\. +Exclaim\! +**strong\\\*\* text** +_italic\_ \*\*text\*\*_ +[closing\] link](https://google.com) +"; + + var results = await RunGetHoverAsync( + testLspServer, + expectedLocation, + clientCapabilities: new LSP.ClientCapabilities + { + TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = new LSP.MarkupKind[] { LSP.MarkupKind.Markdown } } } + }).ConfigureAwait(false); + Assert.Equal(expectedMarkdown, results.Contents.Third.Value); + } + + private static async Task RunGetHoverAsync( + TestLspServer testLspServer, + LSP.Location caret, + ProjectId projectContext = null, + LSP.ClientCapabilities clientCapabilities = null) { - return (LSP.VSHover)await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentHoverName, - CreateTextDocumentPositionParams(caret, projectContext), new LSP.ClientCapabilities(), null, CancellationToken.None); + clientCapabilities ??= new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; + return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentHoverName, + CreateTextDocumentPositionParams(caret, projectContext), clientCapabilities, null, CancellationToken.None); } - private void VerifyContent(LSP.VSHover result, string expectedContent) + private void VerifyVSContent(LSP.Hover hover, string expectedContent) { - var containerElement = (ContainerElement)result.RawContent; + var vsHover = Assert.IsType(hover); + var containerElement = (ContainerElement)vsHover.RawContent; using var _ = ArrayBuilder.GetInstance(out var classifiedTextElements); GetClassifiedTextElements(containerElement, classifiedTextElements); Assert.False(classifiedTextElements.SelectMany(classifiedTextElements => classifiedTextElements.Runs).Any(run => run.NavigationAction != null)); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs index 9086b729494fe..95a9e682a4929 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs @@ -221,7 +221,7 @@ void M() $0 } }"; - await VerifyMarkupAndExpected("\n", markup, expected, useTabs: true, tabSize: 4); + await VerifyMarkupAndExpected("\n", markup, expected, insertSpaces: false, tabSize: 4); } [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] @@ -304,7 +304,7 @@ void M() await VerifyNoResult("\n", markup); } - private async Task VerifyMarkupAndExpected(string characterTyped, string markup, string expected, bool useTabs = false, int tabSize = 4) + private async Task VerifyMarkupAndExpected(string characterTyped, string markup, string expected, bool insertSpaces = true, int tabSize = 4) { using var testLspServer = CreateTestLspServer(markup, out var locations); var locationTyped = locations["type"].Single(); @@ -312,7 +312,7 @@ private async Task VerifyMarkupAndExpected(string characterTyped, string markup, var document = testLspServer.GetCurrentSolution().GetDocuments(locationTyped.Uri).Single(); var documentText = await document.GetTextAsync(); - var result = await RunOnAutoInsertAsync(testLspServer, characterTyped, locationTyped, insertSpaces: !useTabs, tabSize); + var result = await RunOnAutoInsertAsync(testLspServer, characterTyped, locationTyped, insertSpaces, tabSize); AssertEx.NotNull(result); Assert.Equal(InsertTextFormat.Snippet, result.TextEditFormat); @@ -320,13 +320,13 @@ private async Task VerifyMarkupAndExpected(string characterTyped, string markup, Assert.Equal(expected, actualText); } - private async Task VerifyNoResult(string characterTyped, string markup, bool useTabs = false, int tabSize = 4) + private async Task VerifyNoResult(string characterTyped, string markup, bool insertSpaces = true, int tabSize = 4) { using var testLspServer = CreateTestLspServer(markup, out var locations); var locationTyped = locations["type"].Single(); var documentText = await testLspServer.GetCurrentSolution().GetDocuments(locationTyped.Uri).Single().GetTextAsync(); - var result = await RunOnAutoInsertAsync(testLspServer, characterTyped, locationTyped, useTabs, tabSize); + var result = await RunOnAutoInsertAsync(testLspServer, characterTyped, locationTyped, insertSpaces, tabSize); Assert.Null(result); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs index 25edd699209fa..1ce11e1d6dbfe 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.VisualStudio.Text.Adornments; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; @@ -299,8 +300,9 @@ private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret, IPr SupportsVisualStudioExtensions = true }; - return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentReferencesName, + var results = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentReferencesName, CreateReferenceParams(caret, progress), vsClientCapabilities, null, CancellationToken.None); + return results?.Cast()?.ToArray(); } private static void AssertValidDefinitionProperties(LSP.VSReferenceItem[] referenceItems, int definitionIndex, Glyph definitionGlyph) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensTests.cs index 7cc959da8fd40..38d67632d22f7 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensTests.cs @@ -116,7 +116,11 @@ static class C { } Assert.Equal(expectedEdit, ((LSP.SemanticTokensEdits)editResults).Edits.First()); Assert.Equal("3", ((LSP.SemanticTokensEdits)editResults).ResultId); - // 4. Re-request whole document handler (may happen if LSP runs into an error) + // 4. Edits handler - no changes (ResultId should remain same) + var editResultsNoChange = await RunGetSemanticTokensEditsAsync(testLspServer, caretLocation, previousResultId: "3"); + Assert.Equal("3", ((LSP.SemanticTokensEdits)editResultsNoChange).ResultId); + + // 5. Re-request whole document handler (may happen if LSP runs into an error) var wholeDocResults2 = await RunGetSemanticTokensAsync(testLspServer, caretLocation); var expectedWholeDocResults2 = new LSP.SemanticTokens { diff --git a/src/Features/VisualBasic/Portable/AddParameter/VisualBasicAddParameterCodeFixProvider.vb b/src/Features/VisualBasic/Portable/AddParameter/VisualBasicAddParameterCodeFixProvider.vb index 9c59d4073162a..81045aaf3a144 100644 --- a/src/Features/VisualBasic/Portable/AddParameter/VisualBasicAddParameterCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/AddParameter/VisualBasicAddParameterCodeFixProvider.vb @@ -7,7 +7,6 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.AddParameter Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.VisualBasic.GenerateConstructor Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -41,14 +40,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddParameter End Sub Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(Of String)( - IDEDiagnosticIds.UnboundConstructorId, BC30057, BC30272, BC30274, BC30311, BC30389, BC30512, BC32006, BC30387, BC30516, BC36582, BC36625) Protected Overrides ReadOnly Property TooManyArgumentsDiagnosticIds As ImmutableArray(Of String) = - GenerateConstructorDiagnosticIds.TooManyArgumentsDiagnosticIds + GenerateConstructorDiagnosticIds.TooManyArgumentsDiagnosticIds Protected Overrides ReadOnly Property CannotConvertDiagnosticIds As ImmutableArray(Of String) = GenerateConstructorDiagnosticIds.CannotConvertDiagnosticIds - End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb index 97de0a1874c30..f8dfca13b6c34 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb @@ -6,6 +6,7 @@ Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeCleanup +Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -49,6 +50,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase Private Async Function GetNewNodeAsync(document As Document, node As SyntaxNode, cancellationToken As CancellationToken) As Task(Of SyntaxNode) Dim newNode As SyntaxNode = Nothing + Dim trivia As SyntaxTriviaList = node.GetLeadingTrivia() + node = node.WithoutLeadingTrivia() Dim propertyStatement = TryCast(node, PropertyStatementSyntax) If propertyStatement IsNot Nothing Then @@ -61,7 +64,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase End If 'Make sure we preserve any trivia from the original node - newNode = newNode.WithTriviaFrom(node) + newNode = newNode.WithLeadingTrivia(trivia) 'We need to perform a cleanup on the node because AddModifiers doesn't adhere to the VB modifier ordering rules Dim cleanupService = document.GetLanguageService(Of ICodeCleanerService) diff --git a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb index 577fda16c59cc..09fcf69cb6a26 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb @@ -4,8 +4,8 @@ Imports System.Collections.Immutable Imports System.Composition -Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase Friend Const BC40004 As String = "BC40004" ' '{0} '{1}' overloads an overloadable member declared in the base class '{2}'. If you want to shadow the base method, this method must be declared 'Shadows'. - + Public Sub New() End Sub diff --git a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicUnboundIdentifiersDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicUnboundIdentifiersDiagnosticAnalyzer.vb index 7a855909e5846..a2f22de3d3fe8 100644 --- a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicUnboundIdentifiersDiagnosticAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicUnboundIdentifiersDiagnosticAnalyzer.vb @@ -10,23 +10,13 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.Diagnostics Friend NotInheritable Class VisualBasicUnboundIdentifiersDiagnosticAnalyzer - Inherits UnboundIdentifiersDiagnosticAnalyzerBase(Of SyntaxKind, SimpleNameSyntax, QualifiedNameSyntax, IncompleteMemberSyntax, LambdaExpressionSyntax) + Inherits UnboundIdentifiersDiagnosticAnalyzerBase(Of SyntaxKind, SimpleNameSyntax, QualifiedNameSyntax, IncompleteMemberSyntax) Private ReadOnly _messageFormat As LocalizableString = New LocalizableResourceString(NameOf(VBFeaturesResources.Type_0_is_not_defined), VBFeaturesResources.ResourceManager, GetType(VBFeaturesResources)) - Private ReadOnly _messageFormat2 As LocalizableString = New LocalizableResourceString(NameOf(VBFeaturesResources.Too_many_arguments_to_0), VBFeaturesResources.ResourceManager, GetType(VBFeaturesResources)) - Private Shared ReadOnly s_kindsOfInterest As ImmutableArray(Of SyntaxKind) = ImmutableArray.Create( - SyntaxKind.IncompleteMember, - SyntaxKind.MultiLineFunctionLambdaExpression, - SyntaxKind.MultiLineSubLambdaExpression, - SyntaxKind.SingleLineFunctionLambdaExpression, - SyntaxKind.SingleLineSubLambdaExpression) + Private Shared ReadOnly s_kindsOfInterest As ImmutableArray(Of SyntaxKind) = ImmutableArray.Create(SyntaxKind.IncompleteMember) - Protected Overrides ReadOnly Property SyntaxKindsOfInterest As ImmutableArray(Of SyntaxKind) - Get - Return s_kindsOfInterest - End Get - End Property + Protected Overrides ReadOnly Property SyntaxKindsOfInterest As ImmutableArray(Of SyntaxKind) = s_kindsOfInterest Protected Overrides ReadOnly Property DiagnosticDescriptor As DiagnosticDescriptor Get @@ -34,45 +24,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Diagnostics End Get End Property - Protected Overrides ReadOnly Property DiagnosticDescriptor2 As DiagnosticDescriptor - Get - Return GetDiagnosticDescriptor(IDEDiagnosticIds.UnboundConstructorId, _messageFormat2) - End Get - End Property - - Protected Overrides Function ConstructorDoesNotExist(node As SyntaxNode, info As SymbolInfo, semanticModel As SemanticModel) As Boolean - Dim arguments = (TryCast(node.Parent, ObjectCreationExpressionSyntax)?.ArgumentList?.Arguments) - If Not arguments.HasValue Then - Return False - End If - - Dim args = arguments.Value - - Dim constructors = TryCast(info.Symbol?.OriginalDefinition, INamedTypeSymbol)?.Constructors - If constructors Is Nothing Then - Return False - End If - - Dim count = constructors.Value _ - .WhereAsArray(Function(constructor) constructor.Parameters.Length = args.Count) _ - .WhereAsArray(Function(constructor) - For index = 0 To constructor.Parameters.Length - 1 - Dim typeInfo = semanticModel.GetTypeInfo(args(index).GetExpression) - If Not constructor.Parameters(index).Type.Equals(typeInfo.ConvertedType) Then - Return False - End If - Next - Return True - End Function) _ - .Length - - If count = 0 Then - Return True - End If - - Return False - End Function - Protected Overrides Function IsNameOf(node As SyntaxNode) As Boolean Return node.Kind() = SyntaxKind.NameOfKeyword End Function diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 265bcbeb60281..85075aaf5a143 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -4,6 +4,7 @@ Imports System.Collections.Immutable Imports System.Composition +Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices Imports System.Threading Imports Microsoft.CodeAnalysis.Differencing @@ -39,16 +40,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #Region "Syntax Analysis" - ''' - ''' for methods, constructors, operators and accessors. - ''' for auto-properties. - ''' 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 - While node IsNot rootOpt - Select Case node.Kind + Friend Overrides Function TryFindMemberDeclaration(rootOpt As SyntaxNode, node As SyntaxNode, ByRef declarations As OneOrMany(Of SyntaxNode)) As Boolean + Dim current = node + While current IsNot rootOpt + Select Case current.Kind Case SyntaxKind.SubBlock, SyntaxKind.FunctionBlock, SyntaxKind.ConstructorBlock, @@ -58,39 +53,42 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock, SyntaxKind.RaiseEventAccessorBlock - Return node + declarations = OneOrMany.Create(current) + Return True Case SyntaxKind.PropertyStatement ' Property a As Integer = 1 ' Property a As New T - If Not node.Parent.IsKind(SyntaxKind.PropertyBlock) Then - Return node + If Not current.Parent.IsKind(SyntaxKind.PropertyBlock) Then + declarations = OneOrMany.Create(current) + Return True End If Case SyntaxKind.VariableDeclarator - ' 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 + If current.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + + Dim variableDeclarator = CType(current, VariableDeclaratorSyntax) + If variableDeclarator.Names.Count = 1 Then + declarations = OneOrMany.Create(current) + Else + declarations = OneOrMany.Create(variableDeclarator.Names.SelectAsArray(Function(n) CType(n, SyntaxNode))) + End If + + Return True End If Case SyntaxKind.ModifiedIdentifier - ' 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 + If current.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + declarations = OneOrMany.Create(current) + Return True End If End Select - node = node.Parent + current = current.Parent End While - Return Nothing + declarations = Nothing + Return False End Function ''' @@ -203,6 +201,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function + Friend Overrides Function IsDeclarationWithSharedBody(declaration As SyntaxNode) As Boolean + If declaration.Kind = SyntaxKind.ModifiedIdentifier AndAlso declaration.Parent.Kind = SyntaxKind.VariableDeclarator Then + Dim variableDeclarator = CType(declaration.Parent, VariableDeclaratorSyntax) + Return variableDeclarator.Names.Count > 1 AndAlso variableDeclarator.Initializer IsNot Nothing OrElse HasAsNewClause(variableDeclarator) + End If + + Return False + End Function + Protected Overrides Function GetCapturedVariables(model As SemanticModel, memberBody As SyntaxNode) As ImmutableArray(Of ISymbol) Dim methodBlock = TryCast(memberBody, MethodBlockBaseSyntax) If methodBlock IsNot Nothing Then @@ -368,6 +375,91 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function + Friend Overrides Function GetActiveSpanEnvelope(declaration As SyntaxNode) As (envelope As TextSpan, hole As TextSpan) + Select Case declaration.Kind + Case SyntaxKind.SubBlock, + SyntaxKind.FunctionBlock, + SyntaxKind.ConstructorBlock, + SyntaxKind.OperatorBlock, + SyntaxKind.GetAccessorBlock, + SyntaxKind.SetAccessorBlock, + SyntaxKind.AddHandlerAccessorBlock, + SyntaxKind.RemoveHandlerAccessorBlock, + SyntaxKind.RaiseEventAccessorBlock + ' the body is the Statements list of the block + Return (declaration.Span, Nothing) + + Case SyntaxKind.PropertyStatement + ' Property: Attributes Modifiers [|Identifier AsClause Initializer|] ImplementsClause + ' Property: Attributes Modifiers [|Identifier$ Initializer|] ImplementsClause + Dim propertyStatement = DirectCast(declaration, PropertyStatementSyntax) + If propertyStatement.Initializer IsNot Nothing Then + Return (TextSpan.FromBounds(propertyStatement.Identifier.Span.Start, propertyStatement.Initializer.Span.End), Nothing) + End If + + If HasAsNewClause(propertyStatement) Then + Return (TextSpan.FromBounds(propertyStatement.Identifier.Span.Start, propertyStatement.AsClause.Span.End), Nothing) + End If + + Return Nothing + + Case SyntaxKind.VariableDeclarator + Dim variableDeclarator = DirectCast(declaration, VariableDeclaratorSyntax) + If Not declaration.Parent.IsKind(SyntaxKind.FieldDeclaration) OrElse variableDeclarator.Names.Count > 1 Then + Return Nothing + End If + + ' Field: Attributes Modifiers Declarators + Dim fieldDeclaration = DirectCast(declaration.Parent, FieldDeclarationSyntax) + If fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword) Then + Return Nothing + End If + + ' Dim a = initializer + If variableDeclarator.Initializer IsNot Nothing Then + Return (variableDeclarator.Span, Nothing) + End If + + ' Dim a As New C() + If HasAsNewClause(variableDeclarator) Then + Return (variableDeclarator.Span, Nothing) + End If + + ' Dim a(n) As Integer + Dim modifiedIdentifier = variableDeclarator.Names.Single() + If modifiedIdentifier.ArrayBounds IsNot Nothing Then + Return (variableDeclarator.Span, Nothing) + End If + + Return Nothing + + Case SyntaxKind.ModifiedIdentifier + If Not declaration.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + Return Nothing + End If + + ' Dim a, b As New C() + Dim variableDeclarator = DirectCast(declaration.Parent, VariableDeclaratorSyntax) + If HasAsNewClause(variableDeclarator) Then + Dim asNewClause = DirectCast(variableDeclarator.AsClause, AsNewClauseSyntax) + Return (envelope:=TextSpan.FromBounds(declaration.Span.Start, asNewClause.NewExpression.Span.End), + hole:=TextSpan.FromBounds(declaration.Span.End, asNewClause.NewExpression.Span.Start)) + End If + + ' Dim a(n) As Integer + ' Dim a(n), b(n) As Integer + Dim modifiedIdentifier = DirectCast(declaration, ModifiedIdentifierSyntax) + If modifiedIdentifier.ArrayBounds IsNot Nothing Then + Return (declaration.Span, Nothing) + End If + + Return Nothing + + Case Else + Return Nothing + End Select + End Function + Protected Overrides Function GetEncompassingAncestorImpl(bodyOrMatchRoot As SyntaxNode) As SyntaxNode ' AsNewClause is a match root for field/property As New initializer ' EqualsClause is a match root for field/property initializer @@ -522,78 +614,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return node End Function - Friend Overrides Function FindPartnerInMemberInitializer(leftModel As SemanticModel, leftType As INamedTypeSymbol, leftNode As SyntaxNode, rightType As INamedTypeSymbol, cancellationToken As CancellationToken) As SyntaxNode - Dim leftInitializer = leftNode.FirstAncestorOrSelf(Of SyntaxNode)( - Function(node) - Return node.IsKind(SyntaxKind.EqualsValue) AndAlso (node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) OrElse node.Parent.IsKind(SyntaxKind.PropertyStatement)) OrElse - node.IsKind(SyntaxKind.AsNewClause) AndAlso node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) OrElse - IsArrayBoundsArgument(node) - End Function) + Friend Overrides Function FindDeclarationBodyPartner(leftDeclaration As SyntaxNode, rightDeclaration As SyntaxNode, leftNode As SyntaxNode) As SyntaxNode + Debug.Assert(leftDeclaration.Kind = rightDeclaration.Kind) - If leftInitializer Is Nothing Then - Return Nothing - End If - - Dim rightInitializer As SyntaxNode - If leftInitializer.Parent.IsKind(SyntaxKind.PropertyStatement) Then - ' property initializer - Dim leftDeclaration = DirectCast(leftInitializer.Parent, PropertyStatementSyntax) - Dim leftSymbol = leftModel.GetDeclaredSymbol(leftDeclaration, cancellationToken) - Debug.Assert(leftSymbol IsNot Nothing) - - Dim rightProperty = rightType.GetMembers(leftSymbol.Name).Single() - Dim rightDeclaration = DirectCast(rightProperty.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), PropertyStatementSyntax) - - rightInitializer = rightDeclaration.Initializer - ElseIf leftInitializer.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then - ' field initializer or AsNewClause - Dim leftDeclarator = DirectCast(leftInitializer.Parent, VariableDeclaratorSyntax) - - Dim leftSymbol = leftModel.GetDeclaredSymbol(leftDeclarator.Names.First(), cancellationToken) - Debug.Assert(leftSymbol IsNot Nothing) - - Dim rightSymbol = rightType.GetMembers(leftSymbol.Name).Single() - Dim rightDeclarator = DirectCast(rightSymbol.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken).Parent, VariableDeclaratorSyntax) - - rightInitializer = If(leftInitializer.IsKind(SyntaxKind.EqualsValue), rightDeclarator.Initializer, DirectCast(rightDeclarator.AsClause, SyntaxNode)) - Else - ' ArrayBounds argument - Dim leftArguments = DirectCast(leftInitializer.Parent, ArgumentListSyntax) - Dim argumentIndex = GetItemIndexByPosition(leftArguments.Arguments, leftInitializer.Span.Start) - - Dim leftIdentifier = leftArguments.Parent - Debug.Assert(leftIdentifier.IsKind(SyntaxKind.ModifiedIdentifier)) - - Dim leftSymbol = leftModel.GetDeclaredSymbol(leftIdentifier, cancellationToken) - Debug.Assert(leftSymbol IsNot Nothing) - - Dim rightSymbol = rightType.GetMembers(leftSymbol.Name).Single() - Dim rightIdentifier = DirectCast(rightSymbol.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), ModifiedIdentifierSyntax) - - rightInitializer = rightIdentifier.ArrayBounds.Arguments(argumentIndex) - End If - - If rightInitializer Is Nothing Then - Return Nothing - End If - - Return FindPartner(leftInitializer, rightInitializer, leftNode) - End Function - - Friend Overrides Function FindPartner(leftRoot As SyntaxNode, rightRoot As SyntaxNode, leftNode As SyntaxNode) As SyntaxNode - Return SyntaxUtilities.FindPartner(leftRoot, rightRoot, leftNode) - End Function - - Private Shared Function IsArrayBoundsArgument(node As SyntaxNode) As Boolean - Dim argumentSyntax = TryCast(node, ArgumentSyntax) + ' Special case modified identifiers with AsNew clause - the node we are seeking can be in the AsNew clause. + If leftDeclaration.Kind = SyntaxKind.ModifiedIdentifier Then + Dim leftDeclarator = CType(leftDeclaration.Parent, VariableDeclaratorSyntax) + Dim rightDeclarator = CType(rightDeclaration.Parent, VariableDeclaratorSyntax) - If argumentSyntax IsNot Nothing Then - Debug.Assert(argumentSyntax.Parent.IsKind(SyntaxKind.ArgumentList)) - Dim identifier = argumentSyntax.Parent.Parent - Return identifier.IsKind(SyntaxKind.ModifiedIdentifier) AndAlso identifier.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) + If leftDeclarator.AsClause IsNot Nothing AndAlso leftNode.SpanStart >= leftDeclarator.AsClause.SpanStart Then + Return SyntaxUtilities.FindPartner(leftDeclarator.AsClause, rightDeclarator.AsClause, leftNode) + End If End If - Return False + Return SyntaxUtilities.FindPartner(leftDeclaration, rightDeclaration, leftNode) End Function Friend Overrides Function IsClosureScope(node As SyntaxNode) As Boolean @@ -765,6 +799,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #Region "Syntax And Semantic Utils" + Protected Overrides ReadOnly Property LineDirectiveKeyword As String + Get + Return "ExternalSource" + End Get + End Property + + Protected Overrides ReadOnly Property LineDirectiveSyntaxKind As UShort + Get + Return SyntaxKind.ExternalSourceDirectiveTrivia + End Get + End Property + Protected Overrides Function GetSyntaxSequenceEdits(oldNodes As ImmutableArray(Of SyntaxNode), newNodes As ImmutableArray(Of SyntaxNode)) As IEnumerable(Of SequenceEdit) Return SyntaxComparer.GetSequenceEdits(oldNodes, newNodes) End Function @@ -946,6 +992,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return node.IsKind(SyntaxKind.InterfaceBlock) End Function + Friend Overrides Function IsRecordDeclaration(node As SyntaxNode) As Boolean + ' No records in VB + Return False + End Function + Friend Overrides Function TryGetContainingTypeDeclaration(node As SyntaxNode) As SyntaxNode Return node.Parent.FirstAncestorOrSelf(Of TypeBlockSyntax)() ' TODO: EnbumBlock? End Function @@ -987,6 +1038,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function + Friend Overrides Function IsRecordPrimaryConstructorParameter(declaration As SyntaxNode) As Boolean + Return False + End Function + + Friend Overrides Function IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(declaration As SyntaxNode, newContainingType As INamedTypeSymbol, ByRef isFirstAccessor As Boolean) As Boolean + Return False + End Function + Private Shared Function GetInitializerExpression(equalsValue As EqualsValueSyntax, asClause As AsClauseSyntax) As ExpressionSyntax If equalsValue IsNot Nothing Then Return equalsValue.Value @@ -1072,6 +1131,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Case SyntaxKind.FunctionStatement Return If(parent.Kind = SyntaxKind.FunctionBlock, parent, syntax) + Case SyntaxKind.PropertyStatement + Return If(parent.Kind = SyntaxKind.PropertyBlock, parent, syntax) + + Case SyntaxKind.EventStatement + Return If(parent.Kind = SyntaxKind.EventBlock, parent, syntax) + ' declarations that never have a block Case SyntaxKind.ModifiedIdentifier @@ -1136,14 +1201,66 @@ 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), - ByRef isAmbiguous As Boolean, - cancellationToken As CancellationToken) As ISymbol + Protected Overrides Function GetSymbolsForEdit( + editKind As EditKind, + oldNode As SyntaxNode, + newNode As SyntaxNode, + oldModel As SemanticModel, + newModel As SemanticModel, + editMap As IReadOnlyDictionary(Of SyntaxNode, EditKind), + cancellationToken As CancellationToken) As OneOrMany(Of (oldSymbol As ISymbol, newSymbol As ISymbol)) + + Dim oldSymbols As OneOrMany(Of ISymbol) = Nothing + Dim newSymbols As OneOrMany(Of ISymbol) = Nothing + + If editKind = EditKind.Delete Then + If Not TryGetSyntaxNodesForEdit(oldNode, oldModel, oldSymbols, cancellationToken) Then + Return OneOrMany(Of (ISymbol, ISymbol)).Empty + End If + + Return oldSymbols.Select(Function(s) New ValueTuple(Of ISymbol, ISymbol)(s, Nothing)) + End If + + If editKind = EditKind.Insert Then + If Not TryGetSyntaxNodesForEdit(newNode, newModel, newSymbols, cancellationToken) Then + Return OneOrMany(Of (ISymbol, ISymbol)).Empty + End If + + Return newSymbols.Select(Function(s) New ValueTuple(Of ISymbol, ISymbol)(Nothing, s)) + End If + + If editKind = EditKind.Update Then + If Not TryGetSyntaxNodesForEdit(oldNode, oldModel, oldSymbols, cancellationToken) OrElse + Not TryGetSyntaxNodesForEdit(newNode, newModel, newSymbols, cancellationToken) Then + Return OneOrMany(Of (ISymbol, ISymbol)).Empty + End If + + If oldSymbols.Count = 1 AndAlso newSymbols.Count = 1 Then + Return OneOrMany.Create((oldSymbols(0), newSymbols(0))) + End If - isAmbiguous = False + ' This only occurs when field dentifiers are deleted/inserted/reordered from/to/within their variable declarator list, + ' or their shared initializer is updated. The particular inserted and deleted fields will be represented by separate edits, + ' but the AsNew clause of the declarator may have been updated as well, which needs to update the remaining (matching) fields. + Dim builder = ArrayBuilder(Of (ISymbol, ISymbol)).GetInstance() + For Each oldSymbol In oldSymbols + Dim newSymbol = newSymbols.FirstOrDefault(Function(s, o) CaseInsensitiveComparison.Equals(s.Name, o.Name), oldSymbol) + If newSymbol IsNot Nothing Then + builder.Add((oldSymbol, newSymbol)) + End If + Next + + Return OneOrMany.Create(builder.ToImmutableAndFree()) + End If + + Throw ExceptionUtilities.UnexpectedValue(editKind) + End Function + + Private Shared Function TryGetSyntaxNodesForEdit( + node As SyntaxNode, + model As SemanticModel, + ByRef symbols As OneOrMany(Of ISymbol), + cancellationToken As CancellationToken) As Boolean ' Avoid duplicate semantic edits - don't return symbols for statements within blocks. Select Case node.Kind() @@ -1160,59 +1277,66 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.ModuleStatement, SyntaxKind.EnumStatement, SyntaxKind.NamespaceStatement - Return Nothing + Return False Case SyntaxKind.EventStatement If node.Parent.IsKind(SyntaxKind.EventBlock) Then - Return Nothing + Return False End If Case SyntaxKind.PropertyStatement ' autoprop or interface property If node.Parent.IsKind(SyntaxKind.PropertyBlock) Then - Return Nothing + Return False End If Case SyntaxKind.SubStatement ' interface method If node.Parent.IsKind(SyntaxKind.SubBlock) Then - Return Nothing + Return False End If Case SyntaxKind.FunctionStatement ' interface method If node.Parent.IsKind(SyntaxKind.FunctionBlock) Then - Return Nothing + Return False End If Case SyntaxKind.Parameter, SyntaxKind.TypeParameter, SyntaxKind.ImportsStatement, SyntaxKind.NamespaceBlock - Return Nothing + Return False Case SyntaxKind.ModifiedIdentifier If Not node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then - Return Nothing + Return False End If Case SyntaxKind.VariableDeclarator If Not node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then - Return Nothing + Return False End If Dim variableDeclarator = CType(node, VariableDeclaratorSyntax) - isAmbiguous = variableDeclarator.Names.Count > 1 - node = variableDeclarator.Names.First + If variableDeclarator.Names.Count > 1 Then + symbols = OneOrMany.Create(variableDeclarator.Names.SelectAsArray(Function(n) model.GetDeclaredSymbol(n, cancellationToken))) + Return True + End If + node = variableDeclarator.Names(0) End Select Dim symbol = model.GetDeclaredSymbol(node, cancellationToken) + If symbol Is Nothing Then + Return False + End If ' 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 + If symbol.Kind = SymbolKind.Method AndAlso CType(symbol, IMethodSymbol).IsPartialDefinition Then + Return False End If - Return symbol + symbols = OneOrMany.Create(symbol) + Return True End Function Friend Overrides Function ContainsLambda(declaration As SyntaxNode) As Boolean diff --git a/src/Features/VisualBasic/Portable/GenerateConstructor/GenerateConstructorCodeFixProvider.vb b/src/Features/VisualBasic/Portable/GenerateConstructor/GenerateConstructorCodeFixProvider.vb index a4f522a242eb0..ce32658b465ec 100644 --- a/src/Features/VisualBasic/Portable/GenerateConstructor/GenerateConstructorCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/GenerateConstructor/GenerateConstructorCodeFixProvider.vb @@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateConstructor Friend Const BC30516 As String = NameOf(BC30516) ' error BC30516: Overload resolution failed because no accessible 'Blah' accepts this number of arguments. Friend Const BC36625 As String = NameOf(BC36625) ' error BC36625: Lambda expression cannot be converted to 'Integer' because 'Integer' is not a delegate type. - Friend Shared ReadOnly AllDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC30057, IDEDiagnosticIds.UnboundConstructorId, BC30272, BC30274, BC30389, BC30455, BC32006, BC30512, BC30387, BC30516) + Friend Shared ReadOnly AllDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC30057, BC30272, BC30274, BC30389, BC30455, BC32006, BC30512, BC30387, BC30516) Friend Shared ReadOnly TooManyArgumentsDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC30057) Friend Shared ReadOnly CannotConvertDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC30512, BC32006, BC30311, BC36625) End Class @@ -44,7 +44,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateConstructor Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) Get - Return GenerateConstructorDiagnosticIds.AllDiagnosticIds + Return GenerateConstructorDiagnosticIds.AllDiagnosticIds.Concat(GenerateConstructorDiagnosticIds.TooManyArgumentsDiagnosticIds) End Get End Property diff --git a/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb b/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb index 189cf12f13b2e..effac2a33fc76 100644 --- a/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb +++ b/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb @@ -5,6 +5,7 @@ Imports System.Composition Imports Microsoft.CodeAnalysis.GoToDefinition Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.GoToDefinition @@ -20,5 +21,142 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GoToDefinition Protected Overrides Function FindRelatedExplicitlyDeclaredSymbol(symbol As ISymbol, compilation As Compilation) As ISymbol Return symbol.FindRelatedExplicitlyDeclaredSymbol(compilation) End Function + + Protected Overrides Function GetTargetPositionIfControlFlow(semanticModel As SemanticModel, token As SyntaxToken) As Integer? + Dim node = token.GetRequiredParent() + + If token.IsKind(SyntaxKind.ReturnKeyword, SyntaxKind.YieldKeyword) Then + Return FindContainingReturnableConstruct(node).GetFirstToken().Span.Start + End If + + Dim continueTarget = TryGetContinueTarget(node) + If continueTarget IsNot Nothing Then + Return continueTarget.GetFirstToken().Span.Start + End If + + Dim exitTarget = TryGetExitTarget(node) + If exitTarget IsNot Nothing Then + Select Case node.Kind() + Case SyntaxKind.ExitSubStatement + Case SyntaxKind.ExitFunctionStatement + Case SyntaxKind.ExitPropertyStatement + Dim Symbol = semanticModel.GetDeclaredSymbol(exitTarget) + Return Symbol.Locations.FirstOrNone().SourceSpan.Start + End Select + + ' Exit Select, Exit While, Exit For, Exit ForEach, ... + Return exitTarget.GetLastToken().Span.End + End If + + Return Nothing + End Function + + Private Shared Function TryGetExitTarget(node As SyntaxNode) As SyntaxNode + Select Case node.Kind() + Case SyntaxKind.ExitSelectStatement + Return FindContainingSelect(node) + Case SyntaxKind.ExitWhileStatement + Return FindContainingWhile(node) + Case SyntaxKind.ExitForStatement + Return FindContainingFor(node) + Case SyntaxKind.ExitDoStatement + Return FindContainingDoLoop(node) + Case SyntaxKind.ExitTryStatement + Return FindContainingTry(node) + Case SyntaxKind.ExitPropertyStatement + Return FindContainingReturnableConstruct(node) + Case SyntaxKind.ExitSubStatement + Return FindContainingReturnableConstruct(node) + Case SyntaxKind.ExitFunctionStatement + Return FindContainingReturnableConstruct(node) + End Select + + Return Nothing + End Function + + Private Shared Function TryGetContinueTarget(node As SyntaxNode) As SyntaxNode + Select Case node.Kind() + Case SyntaxKind.ContinueWhileStatement + Return FindContainingWhile(node) + Case SyntaxKind.ContinueForStatement + Return FindContainingFor(node) + Case SyntaxKind.ContinueDoStatement + Return FindContainingDoLoop(node) + End Select + + Return Nothing + End Function + + Private Shared Function FindContainingSelect(node As SyntaxNode) As SyntaxNode + While node IsNot Nothing AndAlso Not node.IsKind(SyntaxKind.SelectBlock) + node = node.Parent + + If node.IsReturnableConstruct() Then + Return Nothing + End If + End While + + Return node + End Function + + Private Shared Function FindContainingWhile(node As SyntaxNode) As SyntaxNode + While node IsNot Nothing AndAlso Not node.IsKind(SyntaxKind.WhileBlock) + node = node.Parent + + If node.IsReturnableConstruct() Then + Return Nothing + End If + End While + + Return node + End Function + + Private Shared Function FindContainingFor(node As SyntaxNode) As SyntaxNode + While node IsNot Nothing AndAlso Not node.IsKind(SyntaxKind.ForBlock, SyntaxKind.ForEachBlock) + node = node.Parent + + If node.IsReturnableConstruct() Then + Return Nothing + End If + End While + + Return node + End Function + + Private Shared Function FindContainingDoLoop(node As SyntaxNode) As SyntaxNode + While node IsNot Nothing AndAlso Not node.IsKind(SyntaxKind.DoLoopUntilBlock, SyntaxKind.DoLoopWhileBlock, SyntaxKind.DoUntilLoopBlock, SyntaxKind.DoWhileLoopBlock) + node = node.Parent + + If node.IsReturnableConstruct() Then + Return Nothing + End If + End While + + Return node + End Function + + Private Shared Function FindContainingTry(node As SyntaxNode) As SyntaxNode + While node IsNot Nothing AndAlso Not node.IsKind(SyntaxKind.TryBlock) + node = node.Parent + + If node.IsReturnableConstruct() Then + Return Nothing + End If + End While + + Return node + End Function + + Private Shared Function FindContainingReturnableConstruct(node As SyntaxNode) As SyntaxNode + While node IsNot Nothing AndAlso Not node.IsReturnableConstruct() + node = node.Parent + + If node.IsKind(SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock) Then + Return Nothing + End If + End While + + Return node + End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb new file mode 100644 index 0000000000000..0c7f03ca1fa12 --- /dev/null +++ b/src/Features/VisualBasic/Portable/IntroduceVariable/VisualBasicIntroduceParameterCodeRefactoringProvider.vb @@ -0,0 +1,38 @@ +' 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.Composition +Imports System.Diagnostics.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.IntroduceVariable +Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable + + Friend Class VisualBasicIntroduceParameterCodeRefactoringProvider + Inherits AbstractIntroduceParameterService(Of ExpressionSyntax, InvocationExpressionSyntax, ObjectCreationExpressionSyntax, IdentifierNameSyntax) + + + + Public Sub New() + End Sub + + Protected Overrides Function GenerateExpressionFromOptionalParameter(parameterSymbol As IParameterSymbol) As SyntaxNode + Return GenerateExpression(parameterSymbol.Type, parameterSymbol.ExplicitDefaultValue, canUseFieldReference:=True) + End Function + + Protected Overrides Function GetLocalDeclarationFromDeclarator(variableDecl As SyntaxNode) As SyntaxNode + Return TryCast(variableDecl.Parent, LocalDeclarationStatementSyntax) + End Function + + Protected Overrides Function UpdateArgumentListSyntax(node As SyntaxNode, arguments As SeparatedSyntaxList(Of SyntaxNode)) As SyntaxNode + Return DirectCast(node, ArgumentListSyntax).WithArguments(arguments) + End Function + + Protected Overrides Function IsDestructor(methodSymbol As IMethodSymbol) As Boolean + Return methodSymbol.Name.Equals(WellKnownMemberNames.DestructorName) + End Function + End Class +End Namespace diff --git a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb index 7f6753a278bf7..c6c4cc2710b2a 100644 --- a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb +++ b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb @@ -66,6 +66,13 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices Space()) End Sub + Protected Overrides Sub AddEnumUnderlyingTypeSeparator() + AddToGroup(SymbolDescriptionGroups.MainDescription, + Space(), + Keyword("As"), + Space()) + End Sub + Protected Overrides Function GetInitializerSourcePartsAsync(symbol As ISymbol) As Task(Of ImmutableArray(Of SymbolDisplayPart)) If TypeOf symbol Is IParameterSymbol Then Return GetInitializerSourcePartsAsync(DirectCast(symbol, IParameterSymbol)) diff --git a/src/Features/VisualBasic/Portable/MakeTypeAbstract/VisualBasicMakeTypeAbstractCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeTypeAbstract/VisualBasicMakeTypeAbstractCodeFixProvider.vb index 2873cd09e6f27..293cca71685c9 100644 --- a/src/Features/VisualBasic/Portable/MakeTypeAbstract/VisualBasicMakeTypeAbstractCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeTypeAbstract/VisualBasicMakeTypeAbstractCodeFixProvider.vb @@ -12,7 +12,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.MakeTypeAbstract Friend NotInheritable Class VisualBasicMakeTypeAbstractCodeFixProvider - Inherits AbstractMakeTypeAbstractCodeFixProvider(Of ClassStatementSyntax) + Inherits AbstractMakeTypeAbstractCodeFixProvider(Of ClassBlockSyntax) @@ -20,18 +20,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeTypeAbstract End Sub Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = - ImmutableArray.Create( - "BC31411" - ) + ImmutableArray.Create("BC31411") - Protected Overrides Function IsValidRefactoringContext(node As SyntaxNode, ByRef typeDeclaration As ClassStatementSyntax) As Boolean - If node Is Nothing OrElse Not (node.IsKind(SyntaxKind.ClassStatement)) Then + Protected Overrides Function IsValidRefactoringContext(node As SyntaxNode, ByRef typeDeclaration As ClassBlockSyntax) As Boolean + Dim classStatement = TryCast(node, ClassStatementSyntax) + If classStatement Is Nothing Then Return False End If - typeDeclaration = CType(node, ClassStatementSyntax) + If classStatement.Modifiers.Any(SyntaxKind.MustInheritKeyword) OrElse + classStatement.Modifiers.Any(SyntaxKind.StaticKeyword) Then + Return False + End If - Return Not (typeDeclaration.Modifiers.Any(SyntaxKind.MustInheritKeyword) OrElse typeDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)) + typeDeclaration = TryCast(classStatement.Parent, ClassBlockSyntax) + Return typeDeclaration IsNot Nothing End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/NavigationBar/VisualBasicNavigationBarItemService.vb b/src/Features/VisualBasic/Portable/NavigationBar/VisualBasicNavigationBarItemService.vb index 9a71722c8dced..f3d89443fe212 100644 --- a/src/Features/VisualBasic/Portable/NavigationBar/VisualBasicNavigationBarItemService.vb +++ b/src/Features/VisualBasic/Portable/NavigationBar/VisualBasicNavigationBarItemService.vb @@ -9,11 +9,13 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.ErrorReporting Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.LanguageServices +Imports Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem +Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.NavigationBar @@ -44,14 +46,13 @@ Namespace Microsoft.CodeAnalysis.NavigationBar Dim typesAndDeclarations = GetTypesAndDeclarationsInFile(semanticModel, cancellationToken) Dim typeItems = ImmutableArray.CreateBuilder(Of RoslynNavigationBarItem) - Dim typeSymbolIndexProvider As New NavigationBarSymbolIdIndexProvider(caseSensitive:=False) - Dim symbolDeclarationService = document.GetLanguageService(Of ISymbolDeclarationService) For Each typeAndDeclaration In typesAndDeclarations Dim type = typeAndDeclaration.Item1 Dim position = typeAndDeclaration.Item2.SpanStart - typeItems.AddRange(CreateItemsForType(type, position, typeSymbolIndexProvider.GetIndexForSymbolId(type.GetSymbolKey(cancellationToken)), semanticModel, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) + typeItems.AddRange(CreateItemsForType( + document.Project.Solution, type, position, semanticModel, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) Next Return typeItems.ToImmutable() @@ -98,22 +99,23 @@ Namespace Microsoft.CodeAnalysis.NavigationBar End Function Private Function CreateItemsForType( + solution As Solution, type As INamedTypeSymbol, position As Integer, - typeSymbolIdIndex As Integer, semanticModel As SemanticModel, workspaceSupportsDocumentChanges As Boolean, symbolDeclarationService As ISymbolDeclarationService, cancellationToken As CancellationToken) As ImmutableArray(Of RoslynNavigationBarItem) - Dim items = ImmutableArray.CreateBuilder(Of RoslynNavigationBarItem) + Dim items = ArrayBuilder(Of RoslynNavigationBarItem).GetInstance() If type.TypeKind = TypeKind.Enum Then - items.Add(CreateItemForEnum(type, typeSymbolIdIndex, semanticModel.SyntaxTree, symbolDeclarationService, cancellationToken)) + items.AddIfNotNull(CreateItemForEnum(solution, type, semanticModel.SyntaxTree, symbolDeclarationService)) Else - items.Add(CreatePrimaryItemForType(type, typeSymbolIdIndex, semanticModel.SyntaxTree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) + items.AddIfNotNull(CreatePrimaryItemForType(solution, type, semanticModel.SyntaxTree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) If type.TypeKind <> TypeKind.Interface Then Dim typeEvents = CreateItemForEvents( + solution, type, position, type, @@ -132,7 +134,8 @@ Namespace Microsoft.CodeAnalysis.NavigationBar ' If this is a WithEvents property, then we should also add items for it Dim propertySymbol = TryCast(member, IPropertySymbol) If propertySymbol IsNot Nothing AndAlso propertySymbol.IsWithEvents Then - items.Add(CreateItemForEvents( + items.AddIfNotNull(CreateItemForEvents( + solution, type, position, propertySymbol.Type, @@ -146,42 +149,57 @@ Namespace Microsoft.CodeAnalysis.NavigationBar End If End If - Return items.ToImmutable() + Return items.ToImmutableAndFree() End Function Private Shared Function CreateItemForEnum( + solution As Solution, type As INamedTypeSymbol, - typeSymbolIdIndex As Integer, tree As SyntaxTree, - symbolDeclarationService As ISymbolDeclarationService, - cancellationToken As CancellationToken) As RoslynNavigationBarItem + symbolDeclarationService As ISymbolDeclarationService) As RoslynNavigationBarItem - Dim symbolIndexProvider As New NavigationBarSymbolIdIndexProvider(caseSensitive:=False) - - Dim members = Aggregate member In type.GetMembers() - Where member.IsShared AndAlso member.Kind = SymbolKind.Field + Dim members = From member In type.GetMembers() + Where member.IsShared AndAlso member.Kind = Global.Microsoft.CodeAnalysis.SymbolKind.Field Order By member.Name - Select DirectCast(New SymbolItem( - member.Name, - member.GetGlyph(), - GetSpansInDocument(member, tree, symbolDeclarationService, cancellationToken), - member.GetSymbolKey(cancellationToken), - symbolIndexProvider.GetIndexForSymbolId(member.GetSymbolKey(cancellationToken))), RoslynNavigationBarItem) - Into ToImmutableArray() + Select CreateSymbolItem(solution, member, tree, symbolDeclarationService) + + Dim location = GetSymbolLocation(solution, type, tree, symbolDeclarationService) + If location Is Nothing Then + Return Nothing + End If Return New SymbolItem( + type.Name, type.Name, type.GetGlyph(), - GetSpansInDocument(type, tree, symbolDeclarationService, cancellationToken), - type.GetSymbolKey(cancellationToken), - typeSymbolIdIndex, - members, + type.IsObsolete, + location.Value, + ImmutableArray(Of RoslynNavigationBarItem).CastUp(members.WhereNotNull().ToImmutableArray()), bolded:=True) End Function + Private Shared Function CreateSymbolItem( + solution As Solution, + member As ISymbol, + tree As SyntaxTree, + symbolDeclarationService As ISymbolDeclarationService) As SymbolItem + + Dim location = GetSymbolLocation(solution, member, tree, symbolDeclarationService) + If location Is Nothing Then + Return Nothing + End If + + Return New SymbolItem( + member.Name, + member.Name, + member.GetGlyph(), + member.IsObsolete, + location.Value) + End Function + Private Function CreatePrimaryItemForType( + solution As Solution, type As INamedTypeSymbol, - typeSymbolIdIndex As Integer, tree As SyntaxTree, workspaceSupportsDocumentChanges As Boolean, symbolDeclarationService As ISymbolDeclarationService, @@ -198,7 +216,7 @@ Namespace Microsoft.CodeAnalysis.NavigationBar childItems.Add(New GenerateDefaultConstructor("New", type.GetSymbolKey(cancellationToken))) End If Else - childItems.AddRange(CreateItemsForMemberGroup(constructors, tree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) + childItems.AddRange(CreateItemsForMemberGroup(solution, constructors, tree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) End If ' Get any of the methods named "Finalize" in this class, and list them first. The legacy @@ -211,7 +229,7 @@ Namespace Microsoft.CodeAnalysis.NavigationBar childItems.Add(New GenerateFinalizer(WellKnownMemberNames.DestructorName, type.GetSymbolKey(cancellationToken))) End If Else - childItems.AddRange(CreateItemsForMemberGroup(finalizeMethods, tree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) + childItems.AddRange(CreateItemsForMemberGroup(solution, finalizeMethods, tree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) End If ' And now, methods and properties @@ -222,7 +240,7 @@ Namespace Microsoft.CodeAnalysis.NavigationBar For Each memberGroup In memberGroups If Not CaseInsensitiveComparison.Equals(memberGroup.Key, WellKnownMemberNames.DestructorName) Then - childItems.AddRange(CreateItemsForMemberGroup(memberGroup, tree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) + childItems.AddRange(CreateItemsForMemberGroup(solution, memberGroup, tree, workspaceSupportsDocumentChanges, symbolDeclarationService, cancellationToken)) End If Next End If @@ -233,12 +251,17 @@ Namespace Microsoft.CodeAnalysis.NavigationBar name &= " (" & type.ContainingType.ToDisplayString() & ")" End If + Dim location = GetSymbolLocation(solution, type, tree, symbolDeclarationService) + If location Is Nothing Then + Return Nothing + End If + Return New SymbolItem( + type.Name, name, type.GetGlyph(), - spans:=GetSpansInDocument(type, tree, symbolDeclarationService, cancellationToken), - navigationSymbolId:=type.GetSymbolKey(cancellationToken), - navigationSymbolIndex:=typeSymbolIdIndex, + type.IsObsolete, + location.Value, childItems:=childItems.ToImmutableArray(), bolded:=True) End Function @@ -284,6 +307,7 @@ Namespace Microsoft.CodeAnalysis.NavigationBar ''' If this is an entry for a WithEvents member, the WithEvents ''' property itself. Private Shared Function CreateItemForEvents( + solution As Solution, containingType As INamedTypeSymbol, position As Integer, eventType As ITypeSymbol, @@ -315,28 +339,19 @@ Namespace Microsoft.CodeAnalysis.NavigationBar Next Next - ' The spans of the left item will encompass all event handler spans - Dim allMethodSpans As New List(Of TextSpan) - ' Generate an item for each event For Each e In accessibleEvents If eventToImplementingMethods.ContainsKey(e) Then - Dim methodSpans = GetSpansInDocument(eventToImplementingMethods(e), semanticModel.SyntaxTree, symbolDeclarationService) - - ' Dev11 arbitrarily will navigate to the last method that implements the event - ' if more than one exists - Dim navigationSymbolId = eventToImplementingMethods(e).Last.GetSymbolKey(cancellationToken) - - rightHandMemberItems.Add( - New SymbolItem( + Dim methodLocation = GetSymbolLocation(solution, eventToImplementingMethods(e).First(), semanticModel.SyntaxTree, symbolDeclarationService) + If methodLocation IsNot Nothing Then + rightHandMemberItems.Add(New SymbolItem( + e.Name, e.Name, e.GetGlyph(), - methodSpans, - navigationSymbolId, - navigationSymbolIndex:=0, + e.IsObsolete, + methodLocation.Value, bolded:=True)) - - allMethodSpans.AddRange(methodSpans) + End If Else If workspaceSupportsDocumentChanges AndAlso e.Type IsNot Nothing AndAlso @@ -361,38 +376,23 @@ Namespace Microsoft.CodeAnalysis.NavigationBar eventContainer.Name, eventContainer.GetGlyph(), indent:=1, - spans:=allMethodSpans.ToImmutableArray(), childItems:=rightHandMemberItems.ToImmutableArray()) Else Return New ActionlessItem( String.Format(VBFeaturesResources._0_Events, containingType.Name), Glyph.EventPublic, indent:=1, - spans:=allMethodSpans.ToImmutableArray(), childItems:=rightHandMemberItems.ToImmutableArray()) End If End Function - Private Shared Function GetSpansInDocument(symbol As ISymbol, tree As SyntaxTree, symbolDeclarationService As ISymbolDeclarationService, cancellationToken As CancellationToken) As ImmutableArray(Of TextSpan) - If cancellationToken.IsCancellationRequested Then - Return ImmutableArray(Of TextSpan).Empty - End If - - Return GetSpansInDocument(SpecializedCollections.SingletonEnumerable(symbol), tree, symbolDeclarationService) - End Function - - Private Shared Function GetSpansInDocument(list As IEnumerable(Of ISymbol), tree As SyntaxTree, symbolDeclarationService As ISymbolDeclarationService) As ImmutableArray(Of TextSpan) - Return list.SelectMany(AddressOf symbolDeclarationService.GetDeclarations) _ - .Where(Function(r) r.SyntaxTree.Equals(tree)) _ - .Select(Function(r) r.GetSyntax().FullSpan) _ - .ToImmutableArray() - End Function - - Private Function CreateItemsForMemberGroup(members As IEnumerable(Of ISymbol), - tree As SyntaxTree, - workspaceSupportsDocumentChanges As Boolean, - symbolDeclarationService As ISymbolDeclarationService, - cancellationToken As CancellationToken) As IEnumerable(Of RoslynNavigationBarItem) + Private Function CreateItemsForMemberGroup( + solution As Solution, + members As IEnumerable(Of ISymbol), + tree As SyntaxTree, + workspaceSupportsDocumentChanges As Boolean, + symbolDeclarationService As ISymbolDeclarationService, + cancellationToken As CancellationToken) As IEnumerable(Of RoslynNavigationBarItem) Dim firstMember = members.First() ' If there is exactly one member that has no type arguments, we will skip showing the @@ -405,41 +405,42 @@ Namespace Microsoft.CodeAnalysis.NavigationBar End If Dim items As New List(Of RoslynNavigationBarItem) - Dim symbolIdIndexProvider As New NavigationBarSymbolIdIndexProvider(caseSensitive:=False) - For Each member In members - Dim spans = GetSpansInDocument(member, tree, symbolDeclarationService, cancellationToken) - ' If this is a partial method, we'll care about the implementation part if one ' exists Dim method = TryCast(member, IMethodSymbol) If method IsNot Nothing AndAlso method.PartialImplementationPart IsNot Nothing Then method = method.PartialImplementationPart - items.Add(New SymbolItem( - method.ToDisplayString(displayFormat), - method.GetGlyph(), - spans, - method.GetSymbolKey(cancellationToken), - symbolIdIndexProvider.GetIndexForSymbolId(method.GetSymbolKey(cancellationToken)), - bolded:=spans.Count > 0, - grayed:=spans.Count = 0)) + + Dim location = GetSymbolLocation(solution, method, tree, symbolDeclarationService) + If location IsNot Nothing Then + items.Add(New SymbolItem( + method.Name, + method.ToDisplayString(displayFormat), + method.GetGlyph(), + method.IsObsolete, + location.Value, + bolded:=location.Value.InDocumentInfo IsNot Nothing)) + End If ElseIf method IsNot Nothing AndAlso IsUnimplementedPartial(method) Then If workspaceSupportsDocumentChanges Then items.Add(New GenerateMethod( - member.ToDisplayString(displayFormat), - member.GetGlyph(), - member.ContainingType.GetSymbolKey(cancellationToken), - member.GetSymbolKey(cancellationToken))) + member.ToDisplayString(displayFormat), + member.GetGlyph(), + member.ContainingType.GetSymbolKey(cancellationToken), + member.GetSymbolKey(cancellationToken))) End If Else - items.Add(New SymbolItem( - member.ToDisplayString(displayFormat), - member.GetGlyph(), - spans, - member.GetSymbolKey(cancellationToken), - symbolIdIndexProvider.GetIndexForSymbolId(member.GetSymbolKey(cancellationToken)), - bolded:=spans.Count > 0, - grayed:=spans.Count = 0)) + Dim location = GetSymbolLocation(solution, member, tree, symbolDeclarationService) + If location IsNot Nothing Then + items.Add(New SymbolItem( + member.Name, + member.ToDisplayString(displayFormat), + member.GetGlyph(), + member.IsObsolete, + location.Value, + bolded:=location.Value.InDocumentInfo IsNot Nothing)) + End If End If Next diff --git a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb index 04a8463f960ac..2d902d5d5d603 100644 --- a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb +++ b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb @@ -23,7 +23,32 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo End Sub Protected Overrides Async Function BuildQuickInfoAsync( - document As Document, + context As QuickInfoContext, + token As SyntaxToken) As Task(Of QuickInfoItem) + Dim semanticModel = Await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(False) + Dim info = Await BuildQuickInfoAsync(context.Document.Project.Solution.Workspace, + semanticModel, token, context.CancellationToken).ConfigureAwait(False) + If info IsNot Nothing Then + Return info + End If + + Return Await MyBase.BuildQuickInfoAsync(context, token).ConfigureAwait(False) + End Function + + Protected Overrides Async Function BuildQuickInfoAsync( + context As CommonQuickInfoContext, + token As SyntaxToken) As Task(Of QuickInfoItem) + Dim info = Await BuildQuickInfoAsync(context.Workspace, context.SemanticModel, token, context.CancellationToken).ConfigureAwait(False) + If info IsNot Nothing Then + Return info + End If + + Return Await MyBase.BuildQuickInfoAsync(context, token).ConfigureAwait(False) + End Function + + Private Overloads Shared Async Function BuildQuickInfoAsync( + workspace As Workspace, + semanticModel As SemanticModel, token As SyntaxToken, cancellationToken As CancellationToken) As Task(Of QuickInfoItem) @@ -31,70 +56,69 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo Dim predefinedCastExpression = TryCast(parent, PredefinedCastExpressionSyntax) If predefinedCastExpression IsNot Nothing AndAlso token = predefinedCastExpression.Keyword Then - Dim compilation = Await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(False) - Dim documentation = New PredefinedCastExpressionDocumentation(predefinedCastExpression.Keyword.Kind, compilation) - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, documentation, Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Dim documentation = New PredefinedCastExpressionDocumentation(predefinedCastExpression.Keyword.Kind, semanticModel.Compilation) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, documentation, Glyph.MethodPublic, cancellationToken) End If Select Case token.Kind Case SyntaxKind.AddHandlerKeyword If TypeOf parent Is AddRemoveHandlerStatementSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New AddHandlerStatementDocumentation(), Glyph.Keyword, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New AddHandlerStatementDocumentation(), Glyph.Keyword, cancellationToken) End If Case SyntaxKind.DimKeyword If TypeOf parent Is FieldDeclarationSyntax Then - Return Await BuildContentAsync(document, token, DirectCast(parent, FieldDeclarationSyntax).Declarators, cancellationToken).ConfigureAwait(False) + Return Await BuildContentAsync(workspace, semanticModel, token, DirectCast(parent, FieldDeclarationSyntax).Declarators, cancellationToken).ConfigureAwait(False) ElseIf TypeOf parent Is LocalDeclarationStatementSyntax Then - Return Await BuildContentAsync(document, token, DirectCast(parent, LocalDeclarationStatementSyntax).Declarators, cancellationToken).ConfigureAwait(False) + Return Await BuildContentAsync(workspace, semanticModel, token, DirectCast(parent, LocalDeclarationStatementSyntax).Declarators, cancellationToken).ConfigureAwait(False) End If Case SyntaxKind.CTypeKeyword If TypeOf parent Is CTypeExpressionSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New CTypeCastExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New CTypeCastExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) End If Case SyntaxKind.DirectCastKeyword If TypeOf parent Is DirectCastExpressionSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New DirectCastExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New DirectCastExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) End If Case SyntaxKind.GetTypeKeyword If TypeOf parent Is GetTypeExpressionSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New GetTypeExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New GetTypeExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) End If Case SyntaxKind.GetXmlNamespaceKeyword If TypeOf parent Is GetXmlNamespaceExpressionSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New GetXmlNamespaceExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New GetXmlNamespaceExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) End If Case SyntaxKind.IfKeyword If parent.Kind = SyntaxKind.BinaryConditionalExpression Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New BinaryConditionalExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New BinaryConditionalExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) ElseIf parent.Kind = SyntaxKind.TernaryConditionalExpression Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New TernaryConditionalExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New TernaryConditionalExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) End If Case SyntaxKind.RemoveHandlerKeyword If TypeOf parent Is AddRemoveHandlerStatementSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New RemoveHandlerStatementDocumentation(), Glyph.Keyword, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New RemoveHandlerStatementDocumentation(), Glyph.Keyword, cancellationToken) End If Case SyntaxKind.TryCastKeyword If TypeOf parent Is TryCastExpressionSyntax Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New TryCastExpressionDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New TryCastExpressionDocumentation(), Glyph.MethodPublic, cancellationToken) End If Case SyntaxKind.IdentifierToken If SyntaxFacts.GetContextualKeywordKind(token.ToString()) = SyntaxKind.MidKeyword Then If parent.Kind = SyntaxKind.MidExpression Then - Return Await BuildContentForIntrinsicOperatorAsync(document, token, parent, New MidAssignmentDocumentation(), Glyph.MethodPublic, cancellationToken).ConfigureAwait(False) + Return BuildContentForIntrinsicOperator(semanticModel, token, parent, New MidAssignmentDocumentation(), Glyph.MethodPublic, cancellationToken) End If End If End Select - Return Await MyBase.BuildQuickInfoAsync(document, token, cancellationToken).ConfigureAwait(False) + Return Nothing End Function ''' @@ -122,7 +146,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo End Function Private Overloads Shared Async Function BuildContentAsync( - document As Document, + workspace As Workspace, + semanticModel As SemanticModel, token As SyntaxToken, declarators As SeparatedSyntaxList(Of VariableDeclaratorSyntax), cancellationToken As CancellationToken) As Task(Of QuickInfoItem) @@ -131,11 +156,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo Return Nothing End If - Dim semantics = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) - Dim types = declarators.SelectMany(Function(d) d.Names).Select( Function(n) As ISymbol - Dim symbol = semantics.GetDeclaredSymbol(n, cancellationToken) + Dim symbol = semanticModel.GetDeclaredSymbol(n, cancellationToken) If symbol Is Nothing Then Return Nothing End If @@ -157,21 +180,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo Return QuickInfoItem.Create(token.Span, sections:=ImmutableArray.Create(QuickInfoSection.Create(QuickInfoSectionKinds.Description, ImmutableArray.Create(New TaggedText(TextTags.Text, VBFeaturesResources.Multiple_Types))))) End If - Return Await CreateContentAsync(document.Project.Solution.Workspace, token, semantics, New TokenInformation(types), supportedPlatforms:=Nothing, cancellationToken:=cancellationToken).ConfigureAwait(False) + Return Await CreateContentAsync(workspace, semanticModel, token, New TokenInformation(types), supportedPlatforms:=Nothing, cancellationToken:=cancellationToken).ConfigureAwait(False) End Function - Private Shared Async Function BuildContentForIntrinsicOperatorAsync(document As Document, - token As SyntaxToken, - expression As SyntaxNode, - documentation As AbstractIntrinsicOperatorDocumentation, - glyph As Glyph, - cancellationToken As CancellationToken) As Task(Of QuickInfoItem) + Private Shared Function BuildContentForIntrinsicOperator( + semanticModel As SemanticModel, + token As SyntaxToken, + expression As SyntaxNode, + documentation As AbstractIntrinsicOperatorDocumentation, + glyph As Glyph, + cancellationToken As CancellationToken) As QuickInfoItem Dim builder = New List(Of SymbolDisplayPart) builder.AddRange(documentation.PrefixParts) - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) - Dim position = expression.SpanStart For i = 0 To documentation.ParameterCount - 1 diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf index 4bde1eaa7dfd7..2e37443237550 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Přidat chybějící Importy + Přidat chybějící Importy {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Použít předvolby pro umístění direktiv Import {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Použít předvolby pro kvalifikaci Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Pokud je to možné, nastavit privátní pole jako ReadOnly {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Uspořádat direktivy Import {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Sdílený konstruktor @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + (Počet událostí: {0}) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Odebrat kvalifikaci Me + Odebrat kvalifikaci Me {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf index b2a04670ca4e8..21b10618decd0 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Fehlende Importe hinzufügen + Fehlende Importe hinzufügen {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Platzierungseinstellungen für Import-Direktiven anwenden {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Me-Qualifizierungseinstellungen anwenden {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Privates Feld nach Möglichkeit als "ReadOnly" festlegen {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Import-Direktiven organisieren {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Freigegebener Konstruktor @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} Ereignisse) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Qualifikation "Me" entfernen + Qualifikation "Me" entfernen {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf index 2f6cb27d42556..8aec5d2f4af3e 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Agregar instancias de Import que faltan + Agregar instancias de Import que faltan {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Aplicar las preferencias de ubicación de directivas Import {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Aplicar las preferencias de calificación Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Hacer que el campo privado sea ReadOnly cuando sea posible {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Organizar Importaciones {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Constructor compartido @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} eventos) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Quitar calificación "Me" + Quitar calificación "Me" {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf index 75664311797ba..c079d57e9c857 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Ajouter les Import manquants + Ajouter les Import manquants {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Appliquer les préférences de placement de la directive Imports {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Appliquer les préférences de qualification pour Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Rendre le champ privé ReadOnly quand cela est possible {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Organiser les Imports {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Constructeur partagé @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} événements) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Supprimer la qualification 'Me' + Supprimer la qualification 'Me' {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf index 4226b015dea93..e2659985552a3 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Aggiungi Import mancanti + Aggiungi Import mancanti {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Applica le preferenze di posizionamento per le direttive Import {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Applica le preferenze relative alla qualificazione Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Imposta il campo privato come ReadOnly quando possibile {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Organizza direttive Import {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Costruttore condiviso @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + (Eventi {0}) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Rimuovi qualificazione 'Me' + Rimuovi qualificazione 'Me' {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf index 4c3eee942a069..9dcc48bb4a88f 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - 欠落している Import の追加 + 欠落している Import の追加 {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Import ディレクティブの配置設定を適用する {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Me 修飾設定を適用する {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + 可能な場合、プライベート フィールドを ReadOnly にする {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Import の整理 {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + 共有コンストラクター @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} イベント) @@ -249,7 +249,7 @@ Remove 'Me' qualification - 修飾子 'Me' を削除します + 修飾子 'Me' を削除します {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf index a239c92b10927..e341bfa2c49da 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - 누락된 Imports 추가 + 누락된 Imports 추가 {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Import 지시문 배치 기본 설정 적용 {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Me 한정 기본 설정 적용 {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + 가능한 경우 프라이빗 필드를 ReadOnly로 만들기 {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Import 구성 {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + 공유 생성자 @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} 이벤트) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Me' 한정자 제거 + Me' 한정자 제거 {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf index 497268b33fe44..87db8a619e2ec 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Dodaj brakujące instrukcje Imports + Dodaj brakujące instrukcje Imports {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Zastosuj preferencje umieszczania dyrektyw Import {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Zastosuj preferencje kwalifikacji Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Ustaw pole prywatne jako ReadOnly, gdy to możliwe {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Organizuj dyrektywy Import {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Udostępniony konstruktor @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + (Zdarzenia: {0}) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Usuń kwalifikację „Me” + Usuń kwalifikację „Me” {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf index 7a5690a97626f..6d6399f53cc24 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Adicionar Importações ausentes + Adicionar Importações ausentes {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Aplicar as preferências de posicionamento de diretiva de Imports {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Aplicar as preferências de qualificação de Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Fazer com que o campo privado seja ReadOnly quando possível {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Organizar as Imports {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Construtor compartilhado @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} Eventos) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Remover qualificação 'Me' + Remover qualificação 'Me' {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf index 6b283b9afb2cc..cfe7315b26ee2 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Добавить отсутствующий оператор Imports + Добавить отсутствующий оператор Imports {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Применить параметры размещения директивы Import {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Применить параметры квалификации Me {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Сделать закрытое поле доступным для чтения (ReadOnly), если это возможно {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Организовать импорты (Import) {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Общий конструктор @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} События) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Удаление квалификации Me + Удаление квалификации Me {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf index a4d6bbcd351c0..f627bf735a0d5 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - Eksik Import'ları ekle + Eksik Import'ları ekle {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + Import yönerge yerleştirme tercihlerini uygula {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + Me yeterlilik tercihlerini uygula {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + Mümkün olduğunda özel alanı ReadOnly yap {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + Import'ları Düzenle {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + Paylaşılan oluşturucu @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} Olay) @@ -249,7 +249,7 @@ Remove 'Me' qualification - Me' nitelemesini kaldır + Me' nitelemesini kaldır {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf index 927a9eaad093d..591e1bd1549f3 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - 添加缺少的 Import + 添加缺少的 Import {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + 应用 Import 指令放置首选项 {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + 应用 Me 资格首选项 {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + 尽可能将私有字段设为 ReadOnly {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + 整理 Import {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + 共享构造函数 @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} 事件) @@ -249,7 +249,7 @@ Remove 'Me' qualification - 删除 "Me" 资格 + 删除 "Me" 资格 {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf index 3810ced0ccf9b..fd3f478081751 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Add missing Imports - 新增缺少的 Import + 新增缺少的 Import {Locked="Import"} @@ -29,12 +29,12 @@ Apply Imports directive placement preferences - Apply Imports directive placement preferences + 套用 Import 指示詞放置喜好設定 {Locked="Import"} "Import" is a VB keyword and should not be localized. Apply Me qualification preferences - Apply Me qualification preferences + 套用 Me 資格喜好設定 {Locked="Me"} "Me" is a VB keyword and should not be localized. @@ -94,7 +94,7 @@ Make private field ReadOnly when possible - Make private field ReadOnly when possible + 盡可能地將私人欄位設為 ReadOnly {Locked="ReadOnly"} "ReadOnly" is a VB keyword and should not be localized. @@ -114,7 +114,7 @@ Organize Imports - Organize Imports + 整理 Import {Locked="Import"} @@ -124,7 +124,7 @@ Shared constructor - Shared constructor + 共用建構函式 @@ -139,7 +139,7 @@ ({0} Events) - ({0} Events) + ({0} 個事件) @@ -249,7 +249,7 @@ Remove 'Me' qualification - 移除 'Me' 限定性條件 + 移除 'Me' 限定性條件 {Locked="Me"} "Me" is a VB keyword and should not be localized. diff --git a/src/Interactive/Directory.Build.props b/src/Interactive/Directory.Build.props deleted file mode 100644 index 6eef643958f56..0000000000000 --- a/src/Interactive/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs index 1bb5b525bc595..a7d291b934226 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs @@ -307,6 +307,7 @@ private static JsonRpc CreateRpc(Stream stream, object? incomingCallTarget) var rpc = new JsonRpc(new HeaderDelimitedMessageHandler(stream, jsonFormatter)) { CancelLocallyInvokedMethodsWhenConnectionIsClosed = true, + ExceptionStrategy = ExceptionProcessing.ISerializable, }; if (incomingCallTarget != null) diff --git a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj index a46066dcffd67..c65aa9464f392 100644 --- a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj +++ b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj @@ -11,6 +11,7 @@ true + true .NET Compiler Platform ("Roslyn") interactive host implementation. diff --git a/src/Interactive/HostProcess/InteractiveHost32.csproj b/src/Interactive/HostProcess/InteractiveHost32.csproj index 5d501a10f47b7..672d10ba27bcc 100644 --- a/src/Interactive/HostProcess/InteractiveHost32.csproj +++ b/src/Interactive/HostProcess/InteractiveHost32.csproj @@ -7,6 +7,7 @@ Exe net472 true + true diff --git a/src/Interactive/HostProcess/InteractiveHost64.csproj b/src/Interactive/HostProcess/InteractiveHost64.csproj index 44e834da6daa8..512b9b448b8db 100644 --- a/src/Interactive/HostProcess/InteractiveHost64.csproj +++ b/src/Interactive/HostProcess/InteractiveHost64.csproj @@ -8,6 +8,7 @@ net472;net5.0-windows7.0 win10-x64 true + true true diff --git a/src/Interactive/HostTest/InteractiveHostCoreTests.cs b/src/Interactive/HostTest/InteractiveHostCoreTests.cs index 9bba46ad35788..9f24ee2705e09 100644 --- a/src/Interactive/HostTest/InteractiveHostCoreTests.cs +++ b/src/Interactive/HostTest/InteractiveHostCoreTests.cs @@ -27,7 +27,7 @@ public sealed class InteractiveHostCoreTests : AbstractInteractiveHostTests internal override InteractiveHostPlatform DefaultPlatform => InteractiveHostPlatform.Core; internal override bool UseDefaultInitializationFile => false; - [Fact] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/53392")] public async Task StackOverflow() { var process = Host.TryGetProcess(); diff --git a/src/Interactive/HostTest/InteractiveHostDesktopTests.cs b/src/Interactive/HostTest/InteractiveHostDesktopTests.cs index b0abf594026ea..924ea37b7f080 100644 --- a/src/Interactive/HostTest/InteractiveHostDesktopTests.cs +++ b/src/Interactive/HostTest/InteractiveHostDesktopTests.cs @@ -63,7 +63,7 @@ public async Task OutputRedirection2() Assert.Equal("4\r\n", error); } - [Fact] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/46414")] public async Task StackOverflow() { var process = Host.TryGetProcess(); diff --git a/src/NuGet/Directory.Build.props b/src/NuGet/Directory.Build.props deleted file mode 100644 index 6eef643958f56..0000000000000 --- a/src/NuGet/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - diff --git a/src/NuGet/Microsoft.CodeAnalysis.Compilers.Package.csproj b/src/NuGet/Microsoft.CodeAnalysis.Compilers.Package.csproj index 52345caaeaf45..170261e3458f9 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Compilers.Package.csproj +++ b/src/NuGet/Microsoft.CodeAnalysis.Compilers.Package.csproj @@ -5,6 +5,7 @@ true + true Microsoft.CodeAnalysis.Compilers false diff --git a/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Package.csproj b/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Package.csproj index cd24961a5e944..263ad10478feb 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Package.csproj +++ b/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Package.csproj @@ -5,6 +5,7 @@ true + true Microsoft.CodeAnalysis.EditorFeatures false diff --git a/src/NuGet/Microsoft.CodeAnalysis.Scripting.Package.csproj b/src/NuGet/Microsoft.CodeAnalysis.Scripting.Package.csproj index fb7cae312921d..ed08cbcff6b67 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Scripting.Package.csproj +++ b/src/NuGet/Microsoft.CodeAnalysis.Scripting.Package.csproj @@ -5,6 +5,7 @@ true + true Microsoft.CodeAnalysis.Scripting false diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets b/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets index 8c658ea13609d..0d5edd7862c01 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets @@ -29,8 +29,8 @@ - - + + diff --git a/src/NuGet/Microsoft.Net.Compilers/Microsoft.Net.Compilers.Package.csproj b/src/NuGet/Microsoft.Net.Compilers/Microsoft.Net.Compilers.Package.csproj index cfeda97c7a3ff..05f74e26ce61a 100644 --- a/src/NuGet/Microsoft.Net.Compilers/Microsoft.Net.Compilers.Package.csproj +++ b/src/NuGet/Microsoft.Net.Compilers/Microsoft.Net.Compilers.Package.csproj @@ -4,6 +4,7 @@ net472 true + true Microsoft.Net.Compilers false true diff --git a/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs b/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs index ca9b3085201d0..eb62f635ebb37 100644 --- a/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs +++ b/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.UnitTests { public class GlobalAssemblyCacheTests { - [MonoOnlyFact("https://github.com/dotnet/roslyn/issues/6179")] + [Fact] public void GetAssemblyIdentities() { var gac = GlobalAssemblyCache.Instance; @@ -85,7 +85,7 @@ public void GetAssemblyIdentities() Assert.Equal(0, names.Length); } - [MonoOnlyFact("https://github.com/dotnet/roslyn/pull/39369")] + [Fact] public void GetFacadeAssemblyIdentities() { var gac = GlobalAssemblyCache.Instance; diff --git a/src/Scripting/CoreTestUtilities/Microsoft.CodeAnalysis.Scripting.TestUtilities.csproj b/src/Scripting/CoreTestUtilities/Microsoft.CodeAnalysis.Scripting.TestUtilities.csproj index ed2b9244ce07b..318e666190c3c 100644 --- a/src/Scripting/CoreTestUtilities/Microsoft.CodeAnalysis.Scripting.TestUtilities.csproj +++ b/src/Scripting/CoreTestUtilities/Microsoft.CodeAnalysis.Scripting.TestUtilities.csproj @@ -7,6 +7,7 @@ netstandard2.0 true false + true diff --git a/src/Scripting/Directory.Build.props b/src/Scripting/Directory.Build.props deleted file mode 100644 index 6eef643958f56..0000000000000 --- a/src/Scripting/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - diff --git a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj index a9afc00ae5bb4..82bb66a741dc3 100644 --- a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj +++ b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj @@ -18,7 +18,7 @@ Library partial - + @@ -31,7 +31,7 @@ <_OptimizedDependenciesDir>$(ArtifactsTmpDir)OptimizedDependencies\ - @@ -71,9 +71,9 @@ - <_Dependency Include="@(ReferencePath->'%(NuGetPackageId)')" - Condition="'%(ReferencePath.FrameworkFile)' != 'true' and - '%(ReferencePath.NuGetPackageId)' != '' and + <_Dependency Include="@(ReferencePath->'%(NuGetPackageId)')" + Condition="'%(ReferencePath.FrameworkFile)' != 'true' and + '%(ReferencePath.NuGetPackageId)' != '' and '%(ReferencePath.ReferenceSourceTarget)' != 'ProjectReference'" /> @@ -111,9 +111,9 @@ <_AssemblyVersionVariable Include="Roslyn" Version="$(AssemblyVersion)"/> - @@ -173,14 +173,14 @@ <_EffectiveOptimizeAssemblies Condition="'$(EnableNgenOptimization)' == 'true' and '$(ApplyNgenOptimization)' != ''">%(ExpectedDependency.OptimizeAssemblies) - + <_OptimizeAssembliesSplit Include="%(ExpectedDependency._EffectiveOptimizeAssemblies)" DependencyName="%(ExpectedDependency.Identity)" /> <_UnsignAssembliesSplit Include="%(ExpectedDependency.UnsignAssemblies)" DependencyName="%(ExpectedDependency.Identity)"/> @@ -224,10 +224,10 @@ - - - @@ -259,7 +259,7 @@ <_OptimizedNuGetPackageVersionSuffix Condition="'$(OfficialBuild)' != 'true'">vs-ci <_OptimizedNuGetPackageVersionSuffix Condition="'$(OfficialBuild)' == 'true'">vs-$(VersionSuffixDateStamp)-$(VersionSuffixBuildOfTheDayPadded) - + diff --git a/src/Setup/DevDivVsix/CompilersPackage/Microsoft.CodeAnalysis.Compilers.Setup.csproj b/src/Setup/DevDivVsix/CompilersPackage/Microsoft.CodeAnalysis.Compilers.Setup.csproj index 9797ff225ce2b..a399a5f34dac6 100644 --- a/src/Setup/DevDivVsix/CompilersPackage/Microsoft.CodeAnalysis.Compilers.Setup.csproj +++ b/src/Setup/DevDivVsix/CompilersPackage/Microsoft.CodeAnalysis.Compilers.Setup.csproj @@ -1,3 +1,4 @@ + @@ -23,7 +24,7 @@ - - - - - + diff --git a/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj b/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj index a07dad65823cb..27715abc143ec 100644 --- a/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj +++ b/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.csproj @@ -38,6 +38,7 @@ package name=$(MSBuildProjectName) version=$(VsixVersion) + vs.package.productArch=neutral folder InstallDir:\Common7\ServiceHub\Services\RoslynCodeAnalysisService @(_FileEntries, '%0d%0a ') diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index 20066d89d0f9c..a48e954bc07bc 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -84,8 +84,20 @@ internal static ImmutableArray GetTelemetryInfos(ImmutableArray + + + + diff --git a/src/Tools/BuildValidator/BuildValidatorResources.resx b/src/Tools/BuildValidator/BuildValidatorResources.resx new file mode 100644 index 0000000000000..f7a4362c71708 --- /dev/null +++ b/src/Tools/BuildValidator/BuildValidatorResources.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Assemblies to be excluded (substring match) + + + Do not output log information to console + + + Output debug info when rebuild is not equal to the original + + + Output verbose log information + + + Path to assemblies to rebuild (can be specified one or more times) + + + 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. + + + Path to referenced assemblies (can be specified zero or more times) + + + Path to sources to use in rebuild + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/CompilationDiff.cs b/src/Tools/BuildValidator/CompilationDiff.cs index 30cfc631cbb98..cf16dcada36af 100644 --- a/src/Tools/BuildValidator/CompilationDiff.cs +++ b/src/Tools/BuildValidator/CompilationDiff.cs @@ -134,14 +134,13 @@ public static unsafe CompilationDiff CreateMissingReferences( public static unsafe CompilationDiff Create( AssemblyInfo assemblyInfo, CompilationFactory compilationFactory, - ImmutableArray syntaxTrees, - ImmutableArray metadataReferences, + RebuildArtifactResolver artifactResolver, ILogger logger) { using var rebuildPeStream = new MemoryStream(); var hasEmbeddedPdb = compilationFactory.OptionsReader.HasEmbeddedPdb; var rebuildPdbStream = hasEmbeddedPdb ? null : new MemoryStream(); - var rebuildCompilation = compilationFactory.CreateCompilation(syntaxTrees, metadataReferences); + var rebuildCompilation = compilationFactory.CreateCompilation(artifactResolver); var emitResult = compilationFactory.Emit( rebuildPeStream, rebuildPdbStream, @@ -246,14 +245,14 @@ void writeMissingReferences() using var writer = new StreamWriter(Path.Combine(debugPath, "references.txt"), append: false); foreach (var info in _references) { - if (_localReferenceResolver.TryGetCachedAssemblyInfo(info.Mvid, out var assemblyInfo)) + if (_localReferenceResolver.TryGetCachedAssemblyInfo(info.ModuleVersionId, out var assemblyInfo)) { - writer.WriteLine($"Found: {info.Mvid} {info.Name} at {assemblyInfo.FilePath}"); + writer.WriteLine($"Found: {info.ModuleVersionId} {info.FileName} at {assemblyInfo.FilePath}"); } else { - writer.WriteLine($"Missing: {info.Mvid} {info.Name}"); - foreach (var cachedInfo in _localReferenceResolver.GetCachedAssemblyInfos(info.Name)) + writer.WriteLine($"Missing: {info.ModuleVersionId} {info.FileName}"); + foreach (var cachedInfo in _localReferenceResolver.GetCachedAssemblyInfos(info.FileName)) { writer.WriteLine($"\t{cachedInfo.Mvid} {cachedInfo.FilePath}"); } @@ -407,13 +406,12 @@ 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) + foreach (var info in optionsReader.GetEmbeddedSourceTextInfo()) { - if (info.EmbeddedCompressedHash is { } hash) + if (!info.CompressedHash.IsDefaultOrEmpty) { - var hashString = BitConverter.ToString(hash).Replace("-", ""); - writer.WriteLine($@"\t""{Path.GetFileName(info.SourceFilePath)}"" - {hashString}"); + var hashString = BitConverter.ToString(info.CompressedHash.ToArray()).Replace("-", ""); + writer.WriteLine($@"\t""{Path.GetFileName(info.SourceTextInfo.OriginalSourceFilePath)}"" - {hashString}"); } } } diff --git a/src/Tools/BuildValidator/IldasmUtilities.cs b/src/Tools/BuildValidator/IldasmUtilities.cs index 8204473a95087..b617c37ff759b 100644 --- a/src/Tools/BuildValidator/IldasmUtilities.cs +++ b/src/Tools/BuildValidator/IldasmUtilities.cs @@ -31,7 +31,7 @@ private static string GetIldasmPath() } else { - throw new PlatformNotSupportedException("Runtime platform not supported for testing"); + throw new PlatformNotSupportedException(); } return Path.Combine(directory, "runtimes", ridName, "native", ildasmExeName); diff --git a/src/Tools/BuildValidator/LocalReferenceResolver.cs b/src/Tools/BuildValidator/LocalReferenceResolver.cs index bceb95b9e34a3..3c93b7cb02796 100644 --- a/src/Tools/BuildValidator/LocalReferenceResolver.cs +++ b/src/Tools/BuildValidator/LocalReferenceResolver.cs @@ -77,7 +77,7 @@ public IEnumerable GetCachedAssemblyInfos(string fileName) => _nam public string GetCachedReferencePath(MetadataReferenceInfo referenceInfo) { - if (_mvidMap.TryGetValue(referenceInfo.Mvid, out var value)) + if (_mvidMap.TryGetValue(referenceInfo.ModuleVersionId, out var value)) { return value.FilePath; } @@ -85,102 +85,79 @@ public string GetCachedReferencePath(MetadataReferenceInfo referenceInfo) throw new Exception($"Could not find referenced assembly {referenceInfo}"); } - public bool TryResolveReferences(ImmutableArray references, out ImmutableArray results) + public bool TryResolveReferences(MetadataReferenceInfo metadataReferenceInfo, [NotNullWhen(true)] out MetadataReference? metadataReference) { - if (!CacheNames(references)) + if (!TryGetAssemblyInfo(metadataReferenceInfo, out var assemblyInfo)) { - results = default; + metadataReference = null; return false; } - var builder = ImmutableArray.CreateBuilder(references.Length); - foreach (var reference in references) + // This is deliberately using an ordinal comparison here. The name of the assembly is written out + // into the PDB. Rebuild will only succeed if the provided reference has the same name with the + // same casing + var filePath = assemblyInfo.FilePath; + if (Path.GetFileName(filePath) != metadataReferenceInfo.FileName) { - var filePath = GetCachedReferencePath(reference); - using var fileStream = File.OpenRead(filePath); - - // This is deliberately using an ordinal comparison here. The name of the assembly is written out - // into the PDB. Rebuild will only succeed if the provided reference has the same name with the - // same casing - if (Path.GetFileName(filePath) != reference.Name) - { - filePath = Path.Combine(Path.GetDirectoryName(filePath)!, reference.Name); - } - builder.Add(MetadataReference.CreateFromStream( - fileStream, - filePath: filePath, - properties: new MetadataReferenceProperties( - kind: MetadataImageKind.Assembly, - aliases: reference.ExternAlias is null ? ImmutableArray.Empty : ImmutableArray.Create(reference.ExternAlias), - embedInteropTypes: reference.EmbedInteropTypes))); + filePath = Path.Combine(Path.GetDirectoryName(filePath)!, metadataReferenceInfo.FileName); } - results = builder.MoveToImmutable(); + metadataReference = MetadataReference.CreateFromStream( + File.OpenRead(assemblyInfo.FilePath), + filePath: filePath, + properties: new MetadataReferenceProperties( + kind: MetadataImageKind.Assembly, + aliases: metadataReferenceInfo.ExternAlias is null ? ImmutableArray.Empty : ImmutableArray.Create(metadataReferenceInfo.ExternAlias), + embedInteropTypes: metadataReferenceInfo.EmbedInteropTypes)); return true; } - public bool CacheNames(ImmutableArray references) + public bool TryGetAssemblyInfo(MetadataReferenceInfo metadataReferenceInfo, [NotNullWhen(true)] out AssemblyInfo? assemblyInfo) { - if (references.All(r => _mvidMap.ContainsKey(r.Mvid))) + if (_mvidMap.TryGetValue(metadataReferenceInfo.ModuleVersionId, out assemblyInfo)) { - // All references have already been cached, no reason to look in the file system return true; } + if (_nameMap.TryGetValue(metadataReferenceInfo.FileName, out var _)) + { + // The file name of this reference has already been searched for and none of them + // had the correct MVID (else the _mvidMap lookup would succeed). No reason to do + // more work here. + return false; + } + + var list = new List(); + foreach (var directory in _indexDirectories) { - foreach (var file in directory.GetFiles("*.*", SearchOption.AllDirectories)) + foreach (var fileInfo in directory.EnumerateFiles(metadataReferenceInfo.FileName, SearchOption.AllDirectories)) { - // A single file name can have multiple MVID, so compare by name first then - // open the files to check the MVID - foreach (var reference in references) + if (Util.GetPortableExecutableInfo(fileInfo.FullName) is not { } peInfo) { - if (!FileNameEqualityComparer.StringComparer.Equals(reference.FileInfo.Name, file.Name)) - { - continue; - } - - if (Util.GetPortableExecutableInfo(file.FullName) is not { } peInfo) - { - _logger.LogWarning($@"Could not read MVID from ""{file.FullName}"""); - continue; - } - - if (peInfo.IsReadyToRun) - { - _logger.LogInformation($@"Skipping ReadyToRun image ""{file.FullName}"""); - continue; - } - - var assemblyInfo = new AssemblyInfo(file.FullName, peInfo.Mvid); - if (!_nameMap.TryGetValue(assemblyInfo.FileName, out var list)) - { - list = new List(); - _nameMap[assemblyInfo.FileName] = list; - } - list.Add(assemblyInfo); - - if (!_mvidMap.ContainsKey(peInfo.Mvid)) - { - _logger.LogTrace($"Caching [{peInfo.Mvid}, {file.FullName}]"); - _mvidMap[peInfo.Mvid] = assemblyInfo; - } + _logger.LogWarning($@"Could not read MVID from ""{fileInfo.FullName}"""); + continue; } - } - } - var uncached = references.Where(m => !_mvidMap.ContainsKey(m.Mvid)).ToArray(); - if (uncached.Any()) - { - using var _ = _logger.BeginScope($"Missing {uncached.Length} metadata references:"); - foreach (var missingReference in uncached) - { - _logger.LogError($@"{missingReference.Name} - {missingReference.Mvid}"); + if (peInfo.IsReadyToRun) + { + _logger.LogInformation($@"Skipping ReadyToRun image ""{fileInfo.FullName}"""); + continue; + } + + var currentInfo = new AssemblyInfo(fileInfo.FullName, peInfo.Mvid); + list.Add(currentInfo); + + if (!_mvidMap.ContainsKey(peInfo.Mvid)) + { + _logger.LogTrace($"Caching [{peInfo.Mvid}, {fileInfo.FullName}]"); + _mvidMap[peInfo.Mvid] = currentInfo; + } } - return false; } - return true; + _nameMap[metadataReferenceInfo.FileName] = list; + return _mvidMap.TryGetValue(metadataReferenceInfo.ModuleVersionId, out assemblyInfo); } } } diff --git a/src/Tools/BuildValidator/LocalSourceResolver.cs b/src/Tools/BuildValidator/LocalSourceResolver.cs index 17d4b5d58b212..46bf580a2052a 100644 --- a/src/Tools/BuildValidator/LocalSourceResolver.cs +++ b/src/Tools/BuildValidator/LocalSourceResolver.cs @@ -16,77 +16,45 @@ namespace BuildValidator { internal class LocalSourceResolver { - private readonly Options _options; - private readonly ILogger _logger; + internal Options Options { get; } + internal ImmutableArray SourceLinkEntries { get; } + internal ILogger Logger { get; } - public LocalSourceResolver(Options options, ILoggerFactory loggerFactory) + public LocalSourceResolver(Options options, ImmutableArray sourceLinkEntries, ILogger logger) { - _options = options; - _logger = loggerFactory.CreateLogger(); + Options = options; + SourceLinkEntries = sourceLinkEntries; + Logger = logger; } - public ResolvedSource ResolveSource(SourceFileInfo sourceFileInfo, ImmutableArray sourceLinks, Encoding encoding) + public SourceText ResolveSource(SourceTextInfo sourceTextInfo) { - var pdbDocumentPath = sourceFileInfo.SourceFilePath; - if (sourceFileInfo.EmbeddedText is { } embeddedText) + var originalFilePath = sourceTextInfo.OriginalSourceFilePath; + string? onDiskPath = null; + foreach (var link in SourceLinkEntries) { - return new ResolvedSource(OnDiskPath: null, embeddedText, sourceFileInfo); - } - else - { - string? onDiskPath = null; - foreach (var link in sourceLinks) + if (originalFilePath.StartsWith(link.Prefix, FileNameEqualityComparer.StringComparison)) { - if (sourceFileInfo.SourceFilePath.StartsWith(link.Prefix)) + onDiskPath = Path.GetFullPath(Path.Combine(Options.SourcePath, originalFilePath.Substring(link.Prefix.Length))); + if (File.Exists(onDiskPath)) { - onDiskPath = Path.GetFullPath(Path.Combine(_options.SourcePath, pdbDocumentPath.Substring(link.Prefix.Length))); - if (File.Exists(onDiskPath)) - { - break; - } + break; } } - - // if no source links exist to let us prefix the source path, - // then assume the file path in the pdb points to the on-disk location of the file. - onDiskPath ??= pdbDocumentPath; - - using var fileStream = File.OpenRead(onDiskPath); - var sourceText = SourceText.From(fileStream, encoding: encoding, checksumAlgorithm: SourceHashAlgorithm.Sha256, canBeEmbedded: false); - if (!sourceText.GetChecksum().AsSpan().SequenceEqual(sourceFileInfo.Hash)) - { - throw new Exception($@"File ""{onDiskPath}"" has incorrect hash"); - } - - return new ResolvedSource(onDiskPath, sourceText, sourceFileInfo); } - throw new FileNotFoundException(pdbDocumentPath); - } - - internal ImmutableArray? ResolveSources( - ImmutableArray sourceFileInfos, - ImmutableArray sourceLinks, - Encoding encoding) - { - _logger.LogInformation("Locating source files"); + // if no source links exist to let us prefix the source path, + // then assume the file path in the pdb points to the on-disk location of the file. + onDiskPath ??= originalFilePath; - var sources = ImmutableArray.CreateBuilder(sourceFileInfos.Length); - var isError = false; - foreach (var sourceFileInfo in sourceFileInfos) + using var fileStream = File.OpenRead(onDiskPath); + var sourceText = SourceText.From(fileStream, encoding: sourceTextInfo.SourceTextEncoding, checksumAlgorithm: SourceHashAlgorithm.Sha256, canBeEmbedded: false); + if (!sourceText.GetChecksum().SequenceEqual(sourceTextInfo.Hash)) { - try - { - sources.Add(ResolveSource(sourceFileInfo, sourceLinks, encoding)); - } - catch (Exception ex) - { - _logger.LogError($"Unable to resolve {sourceFileInfo.SourceFilePath}: {ex.Message}"); - isError = true; - } + throw new Exception($@"File ""{onDiskPath}"" has incorrect hash"); } - return isError ? null : sources.MoveToImmutable(); + return sourceText; } } } diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 0b179b5df159e..83e4b6b9005f2 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -9,17 +9,11 @@ using System.CommandLine.Invocation; using System.IO; using System.Linq; -using System.Net.Http.Headers; -using System.Reflection; -using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Rebuild; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; using Newtonsoft.Json; namespace BuildValidator @@ -40,28 +34,28 @@ static int Main(string[] args) var rootCommand = new RootCommand { new Option( - "--assembliesPath", "Path to assemblies to rebuild (can be specified one or more times)" + "--assembliesPath", BuildValidatorResources.Path_to_assemblies_to_rebuild_can_be_specified_one_or_more_times ) { IsRequired = true, Argument = { Arity = ArgumentArity.OneOrMore } }, new Option( - "--exclude", "Assemblies to be excluded (substring match)" + "--exclude", BuildValidatorResources.Assemblies_to_be_excluded_substring_match ) { Argument = { Arity = ArgumentArity.ZeroOrMore } }, new Option( - "--sourcePath", "Path to sources to use in rebuild" + "--sourcePath", BuildValidatorResources.Path_to_sources_to_use_in_rebuild ) { IsRequired = true }, new Option( - "--referencesPath", "Path to referenced assemblies (can be specified zero or more times)" + "--referencesPath", BuildValidatorResources.Path_to_referenced_assemblies_can_be_specified_zero_or_more_times ) { Argument = { Arity = ArgumentArity.ZeroOrMore } }, new Option( - "--verbose", "Output verbose log information" + "--verbose", BuildValidatorResources.Output_verbose_log_information ), new Option( - "--quiet", "Do not output log information to console" + "--quiet", BuildValidatorResources.Do_not_output_log_information_to_console ), new Option( - "--debug", "Output debug info when rebuild is not equal to the original" + "--debug", BuildValidatorResources.Output_debug_info_when_rebuild_is_not_equal_to_the_original ), new Option( - "--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." + "--debugPath", BuildValidatorResources.Path_to_output_debug_info ) }; rootCommand.Handler = CommandHandler.Create(new Func(HandleCommand)); @@ -200,14 +194,12 @@ static IEnumerable getAssemblyPaths(string directory) private static bool ValidateFiles(IEnumerable assemblyInfos, Options options, ILoggerFactory loggerFactory) { var logger = loggerFactory.CreateLogger(); - - var sourceResolver = new LocalSourceResolver(options, loggerFactory); var referenceResolver = new LocalReferenceResolver(options, loggerFactory); var assembliesCompiled = new List(); foreach (var assemblyInfo in assemblyInfos) { - var compilationDiff = ValidateFile(assemblyInfo, logger, sourceResolver, referenceResolver); + var compilationDiff = ValidateFile(options, assemblyInfo, logger, referenceResolver); assembliesCompiled.Add(compilationDiff); if (!compilationDiff.Succeeded) @@ -273,9 +265,9 @@ private static bool ValidateFiles(IEnumerable assemblyInfos, Optio } private static CompilationDiff ValidateFile( + Options options, AssemblyInfo assemblyInfo, ILogger logger, - LocalSourceResolver sourceResolver, LocalReferenceResolver referenceResolver) { // Find the embedded pdb @@ -303,25 +295,9 @@ private static CompilationDiff ValidateFile( return CompilationDiff.CreateMiscError(assemblyInfo, "Missing metadata compilation options"); } - var encoding = optionsReader.GetEncoding(); - var metadataReferenceInfos = optionsReader.GetMetadataReferences(); - var sourceFileInfos = optionsReader.GetSourceFileInfos(encoding); - - logger.LogInformation("Locating metadata references"); - if (!referenceResolver.TryResolveReferences(metadataReferenceInfos, out var metadataReferences)) - { - logger.LogError($"Failed to rebuild {originalBinary.Name} due to missing metadata references"); - return CompilationDiff.CreateMissingReferences(assemblyInfo, referenceResolver, metadataReferenceInfos); - } - logResolvedMetadataReferences(); - var sourceLinks = ResolveSourceLinks(optionsReader, logger); - if (sourceResolver.ResolveSources(sourceFileInfos, sourceLinks, encoding) is not { } sources) - { - logger.LogError($"Failed to resolve sources"); - return CompilationDiff.CreateMiscError(assemblyInfo, "Failed to resolve sources"); - } - logResolvedSources(); + var sourceResolver = new LocalSourceResolver(options, sourceLinks, logger); + var artifactResolver = new RebuildArtifactResolver(sourceResolver, referenceResolver); CompilationFactory compilationFactory; try @@ -329,44 +305,20 @@ private static CompilationDiff ValidateFile( compilationFactory = CompilationFactory.Create( originalBinary.Name, optionsReader); + + return CompilationDiff.Create( + assemblyInfo, + compilationFactory, + artifactResolver, + logger); } catch (Exception ex) { return CompilationDiff.CreateMiscError(assemblyInfo, ex.Message); } - - return CompilationDiff.Create( - assemblyInfo, - compilationFactory, - sources.SelectAsArray(x => compilationFactory.CreateSyntaxTree(x.SourceFileInfo.SourceFilePath, x.SourceText)), - metadataReferences, - logger); - - void logResolvedMetadataReferences() - { - using var _ = logger.BeginScope("Metadata References"); - for (var i = 0; i < metadataReferenceInfos.Length; i++) - { - logger.LogInformation($@"""{metadataReferences[i].Display}"" - {metadataReferenceInfos[i].Mvid}"); - } - } - - void logResolvedSources() - { - using var _ = logger.BeginScope("Source Names"); - foreach (var resolvedSource in sources) - { - var sourceFileInfo = resolvedSource.SourceFileInfo; - var hash = BitConverter.ToString(sourceFileInfo.Hash).Replace("-", ""); - var embeddedCompressedHash = sourceFileInfo.EmbeddedCompressedHash is { } compressedHash - ? ("[uncompressed]" + BitConverter.ToString(compressedHash).Replace("-", "")) - : null; - logger.LogInformation($@"""{resolvedSource.DisplayPath}"" - {sourceFileInfo.HashAlgorithm} - {hash} - {embeddedCompressedHash}"); - } - } } - private static ImmutableArray ResolveSourceLinks(CompilationOptionsReader compilationOptionsReader, ILogger logger) + private static ImmutableArray ResolveSourceLinks(CompilationOptionsReader compilationOptionsReader, ILogger logger) { using var _ = logger.BeginScope("Source Links"); @@ -374,7 +326,7 @@ private static ImmutableArray ResolveSourceLinks(CompilationOptionsR if (sourceLinkUTF8 is null) { logger.LogInformation("No source link cdi found in pdb"); - return ImmutableArray.Empty; + return ImmutableArray.Empty; } var parseResult = JsonConvert.DeserializeAnonymousType(Encoding.UTF8.GetString(sourceLinkUTF8), new { documents = (Dictionary?)null }); @@ -383,7 +335,7 @@ private static ImmutableArray ResolveSourceLinks(CompilationOptionsR if (sourceLinks.IsDefault) { logger.LogInformation("Empty source link cdi found in pdb"); - sourceLinks = ImmutableArray.Empty; + sourceLinks = ImmutableArray.Empty; } else { @@ -394,13 +346,13 @@ private static ImmutableArray ResolveSourceLinks(CompilationOptionsR } return sourceLinks; - static SourceLink makeSourceLink(KeyValuePair entry) + static SourceLinkEntry makeSourceLink(KeyValuePair entry) { // TODO: determine if this subsitution is correct var (key, value) = (entry.Key, entry.Value); // TODO: use Deconstruct in .NET Core var prefix = key.Remove(key.LastIndexOf("*")); var replace = value.Remove(value.LastIndexOf("*")); - return new SourceLink(prefix, replace); + return new SourceLinkEntry(prefix, replace); } } } diff --git a/src/Tools/BuildValidator/RebuildArtifactResolver.cs b/src/Tools/BuildValidator/RebuildArtifactResolver.cs new file mode 100644 index 0000000000000..8610719620d1e --- /dev/null +++ b/src/Tools/BuildValidator/RebuildArtifactResolver.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Rebuild; +using Microsoft.CodeAnalysis.Text; + +namespace BuildValidator +{ + internal sealed class RebuildArtifactResolver : IRebuildArtifactResolver + { + internal LocalSourceResolver SourceResolver { get; } + internal LocalReferenceResolver ReferenceResolver { get; } + + internal RebuildArtifactResolver(LocalSourceResolver sourceResolver, LocalReferenceResolver referenceResolver) + { + SourceResolver = sourceResolver; + ReferenceResolver = referenceResolver; + } + + public SourceText ResolveSourceText(SourceTextInfo sourceTextInfo) + => SourceResolver.ResolveSource(sourceTextInfo); + + public MetadataReference ResolveMetadataReference(MetadataReferenceInfo metadataReferenceInfo) + { + if (!ReferenceResolver.TryResolveReferences(metadataReferenceInfo, out var metadataReference)) + { + throw new InvalidOperationException($"Could not resolve reference: {metadataReferenceInfo.FileName}"); + } + + return metadataReference; + } + } +} diff --git a/src/Tools/BuildValidator/Records.cs b/src/Tools/BuildValidator/Records.cs index b0c600999efb5..0c85c72419a4f 100644 --- a/src/Tools/BuildValidator/Records.cs +++ b/src/Tools/BuildValidator/Records.cs @@ -29,11 +29,16 @@ internal record Options( bool Debug, string DebugPath); - internal record ResolvedSource( - string? OnDiskPath, - SourceText SourceText, - SourceFileInfo SourceFileInfo) + /// An entry in the source-link.json dictionary. + public record SourceLinkEntry { - public string DisplayPath => OnDiskPath ?? ("[embedded]" + SourceFileInfo.SourceFilePath); + public string Prefix { get; } + public string Replace { get; } + + public SourceLinkEntry(string prefix, string replace) + { + Prefix = prefix; + Replace = replace; + } } } diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.cs.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.cs.xlf new file mode 100644 index 0000000000000..da6aa8f3e236e --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.cs.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.de.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.de.xlf new file mode 100644 index 0000000000000..804baaa8dfcaf --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.de.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.es.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.es.xlf new file mode 100644 index 0000000000000..46aacbd857dda --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.es.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.fr.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.fr.xlf new file mode 100644 index 0000000000000..6b1ce5954ec2f --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.fr.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.it.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.it.xlf new file mode 100644 index 0000000000000..61ab76376365d --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.it.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.ja.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.ja.xlf new file mode 100644 index 0000000000000..0e66c2d79f153 --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.ja.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.ko.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.ko.xlf new file mode 100644 index 0000000000000..0b9227bb3239a --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.ko.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.pl.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.pl.xlf new file mode 100644 index 0000000000000..5f2a45f19b020 --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.pl.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.pt-BR.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.pt-BR.xlf new file mode 100644 index 0000000000000..3636535380da9 --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.pt-BR.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.ru.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.ru.xlf new file mode 100644 index 0000000000000..7be7f6c1d4506 --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.ru.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.tr.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.tr.xlf new file mode 100644 index 0000000000000..1f3ee02cac9cd --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.tr.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.zh-Hans.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.zh-Hans.xlf new file mode 100644 index 0000000000000..6a822e11dca96 --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.zh-Hans.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/BuildValidator/xlf/BuildValidatorResources.zh-Hant.xlf b/src/Tools/BuildValidator/xlf/BuildValidatorResources.zh-Hant.xlf new file mode 100644 index 0000000000000..96c947915202b --- /dev/null +++ b/src/Tools/BuildValidator/xlf/BuildValidatorResources.zh-Hant.xlf @@ -0,0 +1,47 @@ + + + + + + Assemblies to be excluded (substring match) + Assemblies to be excluded (substring match) + + + + Do not output log information to console + Do not output log information to console + + + + Output debug info when rebuild is not equal to the original + Output debug info when rebuild is not equal to the original + + + + Output verbose log information + Output verbose log information + + + + Path to assemblies to rebuild (can be specified one or more times) + Path to assemblies to rebuild (can be specified one or more times) + + + + 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. + 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. + + + + Path to referenced assemblies (can be specified zero or more times) + Path to referenced assemblies (can be specified zero or more times) + + + + Path to sources to use in rebuild + Path to sources to use in rebuild + + + + + \ No newline at end of file diff --git a/src/Tools/ExternalAccess/Debugger/DebuggerFindReferencesService.cs b/src/Tools/ExternalAccess/Debugger/DebuggerFindReferencesService.cs index 5d93f8524a0a4..c7526f1d34bc8 100644 --- a/src/Tools/ExternalAccess/Debugger/DebuggerFindReferencesService.cs +++ b/src/Tools/ExternalAccess/Debugger/DebuggerFindReferencesService.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.Composition; using System.Threading; @@ -37,15 +35,20 @@ public async Task FindSymbolReferencesAsync(ISymbol symbol, Project project, Can // Let the presenter know we're starting a search. It will give us back // the context object that the FAR service will push results into. - var context = streamingPresenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true, cancellationToken); + // + // We're awaiting the work to find the symbols (as opposed to kicking it off in a + // fire-and-forget streaming fashion). As such, we do not want to use the cancellation + // token provided by the presenter. Instead, we'll let our caller own if this work + // is cancelable. + var (context, _) = streamingPresenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true); try { - await AbstractFindUsagesService.FindSymbolReferencesAsync(context, symbol, project).ConfigureAwait(false); + await AbstractFindUsagesService.FindSymbolReferencesAsync(context, symbol, project, cancellationToken).ConfigureAwait(false); } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index c57245eb30301..8e148e1a50534 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.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.Collections.Generic; using System.Composition; @@ -12,6 +10,7 @@ using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Classification; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Classification @@ -21,6 +20,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Classification internal class FSharpClassificationService : IClassificationService { private readonly IFSharpClassificationService _service; + private readonly ObjectPool> s_listPool = new(() => new()); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -29,24 +29,47 @@ public FSharpClassificationService(IFSharpClassificationService service) _service = service; } - public void AddLexicalClassifications(SourceText text, TextSpan textSpan, List result, CancellationToken cancellationToken) + public void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) { - _service.AddLexicalClassifications(text, textSpan, result, cancellationToken); + using var _ = s_listPool.GetPooledObject(out var list); + _service.AddLexicalClassifications(text, textSpan, list, cancellationToken); + result.AddRange(list); } - public Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken) + public async Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) { - return _service.AddSemanticClassificationsAsync(document, textSpan, result, cancellationToken); + using var _ = s_listPool.GetPooledObject(out var list); + await _service.AddSemanticClassificationsAsync(document, textSpan, list, cancellationToken).ConfigureAwait(false); + result.AddRange(list); } - public Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken) + public async Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) { - return _service.AddSyntacticClassificationsAsync(document, textSpan, result, cancellationToken); + using var _ = s_listPool.GetPooledObject(out var list); + await _service.AddSyntacticClassificationsAsync(document, textSpan, list, cancellationToken).ConfigureAwait(false); + result.AddRange(list); } public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan) { return _service.AdjustStaleClassification(text, classifiedSpan); } + + public void AddSyntacticClassifications(Workspace workspace, SyntaxNode root, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) + { + // F# does not support syntax. + } + + public TextChangeRange? ComputeSyntacticChangeRange(Workspace workspace, SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken) + { + // F# does not support syntax. + return null; + } + + public ValueTask ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) + { + // not currently supported by F#. + return new(); + } } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorFormattingService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorFormattingService.cs index 29650ffcfb422..b551a058854ed 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorFormattingService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpEditorFormattingService.cs @@ -4,19 +4,21 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor { [Shared] - [ExportLanguageService(typeof(IEditorFormattingService), LanguageNames.FSharp)] - internal class FSharpEditorFormattingService : IEditorFormattingService + [ExportLanguageService(typeof(IFormattingInteractionService), LanguageNames.FSharp)] + internal class FSharpEditorFormattingService : IFormattingInteractionService { private readonly IFSharpEditorFormattingService _service; @@ -35,22 +37,22 @@ public FSharpEditorFormattingService(IFSharpEditorFormattingService service) public bool SupportsFormatOnReturn => _service.SupportsFormatOnReturn; - public Task> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken) + public Task> GetFormattingChangesAsync(Document document, TextSpan? textSpan, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) { return _service.GetFormattingChangesAsync(document, textSpan, cancellationToken); } - public Task?> GetFormattingChangesAsync(Document document, char typedChar, int position, CancellationToken cancellationToken) + public Task?> GetFormattingChangesAsync(Document document, char typedChar, int position, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) { return _service.GetFormattingChangesAsync(document, typedChar, position, cancellationToken); } - public Task> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + public Task> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) { return _service.GetFormattingChangesOnPasteAsync(document, textSpan, cancellationToken); } - public Task?> GetFormattingChangesOnReturnAsync(Document document, int position, CancellationToken cancellationToken) + public Task?> GetFormattingChangesOnReturnAsync(Document document, int position, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) { return _service.GetFormattingChangesOnReturnAsync(document, position, cancellationToken); } @@ -59,5 +61,29 @@ public bool SupportsFormattingOnTypedCharacter(Document document, char ch) { return _service.SupportsFormattingOnTypedCharacter(document, ch); } + + async Task> IFormattingInteractionService.GetFormattingChangesAsync(Document document, TextSpan? textSpan, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) + { + var changes = await GetFormattingChangesAsync(document, textSpan, documentOptions, cancellationToken).ConfigureAwait(false); + return changes?.ToImmutableArray() ?? ImmutableArray.Empty; + } + + async Task> IFormattingInteractionService.GetFormattingChangesAsync(Document document, char typedChar, int position, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) + { + var changes = await GetFormattingChangesAsync(document, typedChar, position, documentOptions, cancellationToken).ConfigureAwait(false); + return changes?.ToImmutableArray() ?? ImmutableArray.Empty; + } + + async Task> IFormattingInteractionService.GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) + { + var changes = await GetFormattingChangesOnPasteAsync(document, textSpan, documentOptions, cancellationToken).ConfigureAwait(false); + return changes?.ToImmutableArray() ?? ImmutableArray.Empty; + } + + async Task> IFormattingInteractionService.GetFormattingChangesOnReturnAsync(Document document, int position, DocumentOptionSet? documentOptions, CancellationToken cancellationToken) + { + var changes = await GetFormattingChangesOnReturnAsync(document, position, documentOptions, cancellationToken).ConfigureAwait(false); + return changes?.ToImmutableArray() ?? ImmutableArray.Empty; + } } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index e733f7e0c4ca8..2101d89b1da2a 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -2,21 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Linq; -using System.Composition; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Notification; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor { @@ -24,29 +25,41 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor [ExportLanguageService(typeof(INavigationBarItemService), LanguageNames.FSharp)] internal class FSharpNavigationBarItemService : INavigationBarItemService { + private readonly IThreadingContext _threadingContext; private readonly IFSharpNavigationBarItemService _service; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FSharpNavigationBarItemService(IFSharpNavigationBarItemService service) + public FSharpNavigationBarItemService( + IThreadingContext threadingContext, + IFSharpNavigationBarItemService service) { + _threadingContext = threadingContext; _service = service; } - public async Task> GetItemsAsync(Document document, CancellationToken cancellationToken) + public async Task> GetItemsAsync(Document document, ITextSnapshot textSnapshot, CancellationToken cancellationToken) { var items = await _service.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); - return items?.Select(x => ConvertToNavigationBarItem(x)).ToList(); + return items == null + ? ImmutableArray.Empty + : ConvertItems(textSnapshot, items); } - public void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) + private static ImmutableArray ConvertItems(ITextSnapshot textSnapshot, IList items) + => (items ?? SpecializedCollections.EmptyList()).Where(x => x.Spans.Any()).SelectAsArray(x => ConvertToNavigationBarItem(x, textSnapshot)); + + public async Task TryNavigateToItemAsync( + Document document, NavigationBarItem item, ITextView view, ITextSnapshot textSnapshot, CancellationToken cancellationToken) { // The logic here was ported from FSharp's implementation. The main reason was to avoid shimming INotificationService. - if (item.Spans.Count > 0) + if (item.NavigationTrackingSpan != null) { - var span = item.Spans.First(); + var span = item.NavigationTrackingSpan.GetSpan(textSnapshot); var workspace = document.Project.Solution.Workspace; - var navigationService = workspace.Services.GetService(); + var navigationService = workspace.Services.GetRequiredService(); + + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); if (navigationService.CanNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, cancellationToken)) { @@ -54,10 +67,12 @@ public void NavigateToItem(Document document, NavigationBarItem item, ITextView } else { - var notificationService = workspace.Services.GetService(); + var notificationService = workspace.Services.GetRequiredService(); notificationService.SendNotification(EditorFeaturesResources.The_definition_of_the_object_is_hidden, severity: NotificationSeverity.Error); } } + + return true; } public bool ShowItemGrayedIfNear(NavigationBarItem item) @@ -65,29 +80,22 @@ public bool ShowItemGrayedIfNear(NavigationBarItem item) return false; } - private static NavigationBarItem ConvertToNavigationBarItem(FSharpNavigationBarItem item) + private static NavigationBarItem ConvertToNavigationBarItem(FSharpNavigationBarItem item, ITextSnapshot textSnapshot) { - return - new InternalNavigationBarItem( - item.Text, - FSharpGlyphHelpers.ConvertTo(item.Glyph), - item.Spans, - item.ChildItems?.Select(x => ConvertToNavigationBarItem(x)).ToList(), - item.Indent, - item.Bolded, - item.Grayed); + return new InternalNavigationBarItem( + item.Text, + FSharpGlyphHelpers.ConvertTo(item.Glyph), + NavigationBarItem.GetTrackingSpans(textSnapshot, item.Spans.ToImmutableArrayOrEmpty()), + ConvertItems(textSnapshot, item.ChildItems), + item.Indent, + item.Bolded, + item.Grayed); } private class InternalNavigationBarItem : NavigationBarItem { - public InternalNavigationBarItem( - string text, - Glyph glyph, - IList spans, - IList childItems, - int indent, - bool bolded, - bool grayed) : base(text, glyph, spans, childItems, indent, bolded, grayed) + public InternalNavigationBarItem(string text, Glyph glyph, ImmutableArray trackingSpans, ImmutableArray childItems, int indent, bool bolded, bool grayed) + : base(text, glyph, trackingSpans, trackingSpans.First(), childItems, indent, bolded, grayed) { } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs index 21bc3c80e9ed4..e8bf8c1cb9041 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesContext.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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.FindUsages; @@ -14,39 +12,39 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor.FindUsage internal class FSharpFindUsagesContext : IFSharpFindUsagesContext { private readonly IFindUsagesContext _context; + private readonly CancellationToken _cancellationToken; - public FSharpFindUsagesContext(IFindUsagesContext context) + public FSharpFindUsagesContext(IFindUsagesContext context, CancellationToken cancellationToken) { _context = context; + _cancellationToken = cancellationToken; } - public CancellationToken CancellationToken => _context.CancellationToken; + public CancellationToken CancellationToken => _cancellationToken; public Task OnDefinitionFoundAsync(FSharp.FindUsages.FSharpDefinitionItem definition) { - return _context.OnDefinitionFoundAsync(definition.RoslynDefinitionItem).AsTask(); + return _context.OnDefinitionFoundAsync(definition.RoslynDefinitionItem, _cancellationToken).AsTask(); } public Task OnReferenceFoundAsync(FSharp.FindUsages.FSharpSourceReferenceItem reference) { - return _context.OnReferenceFoundAsync(reference.RoslynSourceReferenceItem).AsTask(); + return _context.OnReferenceFoundAsync(reference.RoslynSourceReferenceItem, _cancellationToken).AsTask(); } public Task ReportMessageAsync(string message) { - return _context.ReportMessageAsync(message).AsTask(); + return _context.ReportMessageAsync(message, _cancellationToken).AsTask(); } public Task ReportProgressAsync(int current, int maximum) { -#pragma warning disable CS0618 // Type or member is obsolete - return _context.ReportProgressAsync(current, maximum).AsTask(); -#pragma warning restore CS0618 // Type or member is obsolete + return Task.CompletedTask; } public Task SetSearchTitleAsync(string title) { - return _context.SetSearchTitleAsync(title).AsTask(); + return _context.SetSearchTitleAsync(title, _cancellationToken).AsTask(); } } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesService.cs index 91396d44f17da..623dda3cb550c 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FindUsages/FSharpFindUsagesService.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.FindUsages; @@ -23,18 +22,12 @@ internal class FSharpFindUsagesService : IFindUsagesService [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public FSharpFindUsagesService(IFSharpFindUsagesService service) - { - _service = service; - } + => _service = service; - public Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context) - { - return _service.FindImplementationsAsync(document, position, new FSharpFindUsagesContext(context)); - } + public Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) + => _service.FindImplementationsAsync(document, position, new FSharpFindUsagesContext(context, cancellationToken)); - public Task FindReferencesAsync(Document document, int position, IFindUsagesContext context) - { - return _service.FindReferencesAsync(document, position, new FSharpFindUsagesContext(context)); - } + public Task FindReferencesAsync(Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken) + => _service.FindReferencesAsync(document, position, new FSharpFindUsagesContext(context, cancellationToken)); } } diff --git a/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs b/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs index 0b5175a2cb4dc..123c14ebb4baa 100644 --- a/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs +++ b/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs @@ -13,7 +13,6 @@ internal static class FSharpNavigateToItemKind public static string Line => NavigateToItemKind.Line; public static string File = NavigateToItemKind.File; public static string Class => NavigateToItemKind.Class; - public static string Record => NavigateToItemKind.Record; public static string Structure => NavigateToItemKind.Structure; public static string Interface => NavigateToItemKind.Interface; public static string Delegate => NavigateToItemKind.Delegate; diff --git a/src/Tools/ExternalAccess/OmniSharp.CSharp/Completion/OmniSharpCompletionProviderNames.cs b/src/Tools/ExternalAccess/OmniSharp.CSharp/Completion/OmniSharpCompletionProviderNames.cs new file mode 100644 index 0000000000000..b2d6ed6a1ae9d --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp.CSharp/Completion/OmniSharpCompletionProviderNames.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp.Completion +{ + internal static class OmniSharpCompletionProviderNames + { + internal static string ObjectCreationCompletionProvider = typeof(ObjectCreationCompletionProvider).FullName; + internal static string OverrideCompletionProvider = typeof(OverrideCompletionProvider).FullName; + internal static string PartialMethodCompletionProvider = typeof(PartialMethodCompletionProvider).FullName; + internal static string InternalsVisibleToCompletionProvider = typeof(InternalsVisibleToCompletionProvider).FullName; + internal static string TypeImportCompletionProvider = typeof(TypeImportCompletionProvider).FullName; + internal static string ExtensionMethodImportCompletionProvider = typeof(ExtensionMethodImportCompletionProvider).FullName; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp.CSharp/DocumentationComments/OmniSharpDocCommentConverter.cs b/src/Tools/ExternalAccess/OmniSharp.CSharp/DocumentationComments/OmniSharpDocCommentConverter.cs new file mode 100644 index 0000000000000..f0c79747cf881 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp.CSharp/DocumentationComments/OmniSharpDocCommentConverter.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.DocumentationComments; +using Microsoft.CodeAnalysis.DocumentationComments; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp.DocumentationComments +{ + internal static class OmniSharpDocCommentConverter + { + public static SyntaxNode ConvertToRegularComments(SyntaxNode node, Project project, CancellationToken cancellationToken) + { + var formattingService = project.GetRequiredLanguageService(); + return DocCommentConverter.ConvertToRegularComments(node, formattingService, cancellationToken); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp.CSharp/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp.csproj b/src/Tools/ExternalAccess/OmniSharp.CSharp/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp.csproj new file mode 100644 index 0000000000000..5797434532fdf --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp.CSharp/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp.csproj @@ -0,0 +1,46 @@ + + + + netstandard2.0 + + true + Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp + + A supporting package for OmniSharp: + https://github.com/OmniSharp/omnisharp-roslyn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tools/ExternalAccess/OmniSharp.CSharp/PublicAPI.Shipped.txt b/src/Tools/ExternalAccess/OmniSharp.CSharp/PublicAPI.Shipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Tools/ExternalAccess/OmniSharp.CSharp/PublicAPI.Unshipped.txt b/src/Tools/ExternalAccess/OmniSharp.CSharp/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp.CSharp/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + diff --git a/src/Tools/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeAction.cs b/src/Tools/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeAction.cs new file mode 100644 index 0000000000000..2ce633288986c --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeAction.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeActions; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CodeActions +{ + internal static class OmniSharpCodeAction + { + public static ImmutableArray GetNestedCodeActions(this CodeAction codeAction) + => codeAction.NestedCodeActions; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/CodeRefactorings/WorkspaceServices/IOmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/Tools/ExternalAccess/OmniSharp/CodeRefactorings/WorkspaceServices/IOmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs new file mode 100644 index 0000000000000..6172afce6fe85 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/CodeRefactorings/WorkspaceServices/IOmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using Microsoft.CodeAnalysis.CodeActions; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CodeRefactorings.WorkspaceServices +{ + interface IOmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService + { + CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs new file mode 100644 index 0000000000000..baa9a7342b059 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion +{ + internal static class OmniSharpCompletionService + { + public static Task<(CompletionList completionList, bool expandItemsAvailable)> GetCompletionsAsync( + this CompletionService completionService, + Document document, + int caretPosition, + CompletionTrigger trigger = default, + ImmutableHashSet? roles = null, + OptionSet? options = null, + CancellationToken cancellationToken = default) + => completionService.GetCompletionsInternalAsync(document, caretPosition, trigger, roles, options, cancellationToken); + + public static string GetProviderName(this CompletionItem completionItem) => completionItem.ProviderName; + + public static PerLanguageOption ShowItemsFromUnimportedNamespaces = (PerLanguageOption)CompletionOptions.ShowItemsFromUnimportedNamespaces; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/DocumentationComments/OmniSharpDocumentationCommentsSnippetService.cs b/src/Tools/ExternalAccess/OmniSharp/DocumentationComments/OmniSharpDocumentationCommentsSnippetService.cs new file mode 100644 index 0000000000000..8524e662304f8 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/DocumentationComments/OmniSharpDocumentationCommentsSnippetService.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using Microsoft.CodeAnalysis.DocumentationComments; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.DocumentationComments +{ + internal static class OmniSharpDocumentationCommentsSnippetService + { + public static OmniSharpDocumentationCommentSnippet? GetDocumentationCommentSnippetOnCharacterTyped( + Document document, + SyntaxTree syntaxTree, + SourceText text, + int position, + DocumentOptionSet options, + CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + return Translate(service.GetDocumentationCommentSnippetOnCharacterTyped(syntaxTree, text, position, options, cancellationToken)); + } + + public static OmniSharpDocumentationCommentSnippet? GetDocumentationCommentSnippetOnCommandInvoke( + Document document, + SyntaxTree syntaxTree, + SourceText text, + int position, + DocumentOptionSet options, + CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + return Translate(service.GetDocumentationCommentSnippetOnCommandInvoke(syntaxTree, text, position, options, cancellationToken)); + } + + public static OmniSharpDocumentationCommentSnippet? GetDocumentationCommentSnippetOnEnterTyped( + Document document, + SyntaxTree syntaxTree, + SourceText text, + int position, + DocumentOptionSet options, + CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + return Translate(service.GetDocumentationCommentSnippetOnEnterTyped(syntaxTree, text, position, options, cancellationToken)); + } + + public static OmniSharpDocumentationCommentSnippet? GetDocumentationCommentSnippetFromPreviousLine( + Document document, + DocumentOptionSet options, + TextLine currentLine, + TextLine previousLine) + { + var service = document.GetRequiredLanguageService(); + return Translate(service.GetDocumentationCommentSnippetFromPreviousLine(options, currentLine, previousLine)); + } + + private static OmniSharpDocumentationCommentSnippet? Translate(DocumentationCommentSnippet? result) + => result == null ? null : new(result.SpanToReplace, result.SnippetText, result.CaretOffset); + } + + internal sealed class OmniSharpDocumentationCommentSnippet + { + /// + /// The span in the original text that should be replaced with the documentation comment. + /// + public TextSpan SpanToReplace { get; } + + /// + /// The documentation comment text to replace the span with + /// + public string SnippetText { get; } + + /// + /// The offset within where the caret should be positioned after replacement + /// + public int CaretOffset { get; } + + internal OmniSharpDocumentationCommentSnippet(TextSpan spanToReplace, string snippetText, int caretOffset) + { + SpanToReplace = spanToReplace; + SnippetText = snippetText; + CaretOffset = caretOffset; + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs b/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs new file mode 100644 index 0000000000000..54faf1aea3097 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/ExtractClass/IOmniSharpExtractClassOptionsService.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractClass +{ + internal interface IOmniSharpExtractClassOptionsService + { + Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ISymbol? selectedMember); + } + + internal sealed class OmniSharpExtractClassOptions + { + public string FileName { get; } + public string TypeName { get; } + public bool SameFile { get; } + public ImmutableArray MemberAnalysisResults { get; } + + public OmniSharpExtractClassOptions( + string fileName, + string typeName, + bool sameFile, + ImmutableArray memberAnalysisResults) + { + FileName = fileName; + TypeName = typeName; + SameFile = sameFile; + MemberAnalysisResults = memberAnalysisResults; + } + } + internal sealed class OmniSharpExtractClassMemberAnalysisResult + { + /// + /// The member needs to be pulled up. + /// + public ISymbol Member { get; } + + /// + /// Whether to make the member abstract when added to the new class + /// + public bool MakeAbstract { get; } + + public OmniSharpExtractClassMemberAnalysisResult( + ISymbol member, + bool makeAbstract) + { + Member = member; + MakeAbstract = makeAbstract; + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/ExtractInterface/IOmniSharpExtractInterfaceOptionsService.cs b/src/Tools/ExternalAccess/OmniSharp/ExtractInterface/IOmniSharpExtractInterfaceOptionsService.cs new file mode 100644 index 0000000000000..b0d0a1fcdcc3d --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/ExtractInterface/IOmniSharpExtractInterfaceOptionsService.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.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractInterface +{ + internal interface IOmniSharpExtractInterfaceOptionsService + { + // OmniSharp only uses these two arguments from the full IExtractInterfaceOptionsService + Task GetExtractInterfaceOptionsAsync( + List extractableMembers, + string defaultInterfaceName); + } + + internal class OmniSharpExtractInterfaceOptionsResult + { + public enum OmniSharpExtractLocation + { + SameFile, + NewFile + } + + public bool IsCancelled { get; } + public ImmutableArray IncludedMembers { get; } + public string InterfaceName { get; } + public string FileName { get; } + public OmniSharpExtractLocation Location { get; } + + public OmniSharpExtractInterfaceOptionsResult(bool isCancelled, ImmutableArray includedMembers, string interfaceName, string fileName, OmniSharpExtractLocation location) + { + IsCancelled = isCancelled; + IncludedMembers = includedMembers; + InterfaceName = interfaceName; + Location = location; + FileName = fileName; + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs b/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs new file mode 100644 index 0000000000000..156b9e3c75623 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Navigation; +using Microsoft.CodeAnalysis.GoToDefinition; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.GoToDefinition +{ + internal static class OmniSharpFindDefinitionService + { + internal static async Task> FindDefinitionsAsync(Document document, int position, CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + var result = await service.FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false); + return result.NullToEmpty().SelectAsArray(original => new OmniSharpNavigableItem(original.DisplayTaggedParts, original.Document, original.SourceSpan)); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs b/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs new file mode 100644 index 0000000000000..121e0f7f342ec --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.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 Microsoft.CodeAnalysis.ImplementType; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ImplementType +{ + internal static class OmniSharpImplementTypeOptions + { + public static OmniSharpImplementTypeInsertionBehavior GetInsertionBehavior(OptionSet options, string language) + => (OmniSharpImplementTypeInsertionBehavior)options.GetOption(ImplementTypeOptions.InsertionBehavior, language); + + public static OptionSet SetInsertionBehavior(OptionSet options, string language, OmniSharpImplementTypeInsertionBehavior value) + => options.WithChangedOption(ImplementTypeOptions.InsertionBehavior, language, (ImplementTypeInsertionBehavior)value); + + public static OmniSharpImplementTypePropertyGenerationBehavior GetPropertyGenerationBehavior(OptionSet options, string language) + => (OmniSharpImplementTypePropertyGenerationBehavior)options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior, language); + + public static OptionSet SetPropertyGenerationBehavior(OptionSet options, string language, OmniSharpImplementTypePropertyGenerationBehavior value) + => options.WithChangedOption(ImplementTypeOptions.PropertyGenerationBehavior, language, (ImplementTypePropertyGenerationBehavior)value); + } + + internal enum OmniSharpImplementTypeInsertionBehavior + { + WithOtherMembersOfTheSameKind = ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, + AtTheEnd = ImplementTypeInsertionBehavior.AtTheEnd, + } + + internal enum OmniSharpImplementTypePropertyGenerationBehavior + { + PreferThrowingProperties = ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, + PreferAutoProperties = ImplementTypePropertyGenerationBehavior.PreferAutoProperties, + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/InlineHints/OmniSharpInlineHintsService.cs b/src/Tools/ExternalAccess/OmniSharp/InlineHints/OmniSharpInlineHintsService.cs new file mode 100644 index 0000000000000..d0822334c1185 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/InlineHints/OmniSharpInlineHintsService.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.InlineHints; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.InlineHints +{ + internal static class OmniSharpInlineHintsService + { + public static async Task> GetInlineHintsAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + var hints = await service.GetInlineHintsAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + return hints.SelectAsArray(static h => new OmniSharpInlineHint( + h.Span, + h.DisplayParts, + (document, cancellationToken) => h.GetDescriptionAsync(document, cancellationToken))); + } + } + + internal readonly struct OmniSharpInlineHint + { + private readonly Func>> _getDescriptionAsync; + + public OmniSharpInlineHint( + TextSpan span, + ImmutableArray displayParts, + Func>> getDescriptionAsync) + { + Span = span; + DisplayParts = displayParts; + _getDescriptionAsync = getDescriptionAsync; + } + + public readonly TextSpan Span { get; } + public readonly ImmutableArray DisplayParts { get; } + + public Task> GetDescrptionAsync(Document document, CancellationToken cancellationToken) + => _getDescriptionAsync.Invoke(document, cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Internal/CodeRefactorings/WorkspaceServices/OmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/Tools/ExternalAccess/OmniSharp/Internal/CodeRefactorings/WorkspaceServices/OmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs new file mode 100644 index 0000000000000..6b441873fb405 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Internal/CodeRefactorings/WorkspaceServices/OmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CodeRefactorings.WorkspaceServices; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Internal.CodeRefactorings.WorkspaceServices +{ + [Shared] + [ExportWorkspaceService(typeof(ISymbolRenamedCodeActionOperationFactoryWorkspaceService))] + class OmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService : ISymbolRenamedCodeActionOperationFactoryWorkspaceService + { + private readonly IOmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService _service; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService(IOmniSharpSymbolRenamedCodeActionOperationFactoryWorkspaceService service) + { + _service = service; + } + + public CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) + { + return _service.CreateSymbolRenamedOperation(symbol, newName, startingSolution, updatedSolution); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs b/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs new file mode 100644 index 0000000000000..a60eda3cfa1d3 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractClass/OmniSharpExtractClassOptionsService.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractClass; +using Microsoft.CodeAnalysis.ExtractClass; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Internal.ExtractClass +{ + [Shared] + [ExportWorkspaceService(typeof(IExtractClassOptionsService))] + internal class OmniSharpExtractClassOptionsService : IExtractClassOptionsService + { + private readonly IOmniSharpExtractClassOptionsService _omniSharpExtractClassOptionsService; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OmniSharpExtractClassOptionsService(IOmniSharpExtractClassOptionsService omniSharpExtractClassOptionsService) + { + _omniSharpExtractClassOptionsService = omniSharpExtractClassOptionsService; + } + + public async Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol originalType, ISymbol? selectedMember) + { + var result = await _omniSharpExtractClassOptionsService.GetExtractClassOptionsAsync(document, originalType, selectedMember).ConfigureAwait(false); + return result == null + ? null + : new ExtractClassOptions( + result.FileName, + result.TypeName, + result.SameFile, + result.MemberAnalysisResults.SelectAsArray(m => new ExtractClassMemberAnalysisResult(m.Member, m.MakeAbstract))); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.cs b/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.cs new file mode 100644 index 0000000000000..ffcb263fbf3d4 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.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.Collections.Generic; +using System.Composition; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractInterface; +using Microsoft.CodeAnalysis.ExtractInterface; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Notification; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Internal.ExtractInterface +{ + [Shared] + [ExportWorkspaceService(typeof(IExtractInterfaceOptionsService))] + internal class OmniSharpExtractInterfaceOptionsService : IExtractInterfaceOptionsService + { + private readonly IOmniSharpExtractInterfaceOptionsService _omniSharpExtractInterfaceOptionsService; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OmniSharpExtractInterfaceOptionsService(IOmniSharpExtractInterfaceOptionsService omniSharpExtractInterfaceOptionsService) + { + _omniSharpExtractInterfaceOptionsService = omniSharpExtractInterfaceOptionsService; + } + + public async Task GetExtractInterfaceOptionsAsync(ISyntaxFactsService syntaxFactsService, INotificationService notificationService, List extractableMembers, string defaultInterfaceName, List conflictingTypeNames, string defaultNamespace, string generatedNameTypeParameterSuffix, string languageName) + { + var result = await _omniSharpExtractInterfaceOptionsService.GetExtractInterfaceOptionsAsync(extractableMembers, defaultInterfaceName).ConfigureAwait(false); + return new( + result.IsCancelled, + result.IncludedMembers, + result.InterfaceName, + result.FileName, + (ExtractInterfaceOptionsResult.ExtractLocation)result.Location); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Internal/PickMembers/OmniSharpPickMembersService.cs b/src/Tools/ExternalAccess/OmniSharp/Internal/PickMembers/OmniSharpPickMembersService.cs new file mode 100644 index 0000000000000..b10e56077fb01 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Internal/PickMembers/OmniSharpPickMembersService.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.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.PickMembers; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PickMembers; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Internal.PickMembers +{ + [Shared] + [ExportWorkspaceService(typeof(IPickMembersService), ServiceLayer.Host)] + internal class OmniSharpPickMembersService : IPickMembersService + { + private readonly IOmniSharpPickMembersService _omniSharpPickMembersService; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OmniSharpPickMembersService(IOmniSharpPickMembersService omniSharpPickMembersService) + { + _omniSharpPickMembersService = omniSharpPickMembersService; + } + + public PickMembersResult PickMembers(string title, ImmutableArray members, ImmutableArray options = default, bool selectAll = true) + { + var result = _omniSharpPickMembersService.PickMembers(title, members, options.IsDefault ? default : options.SelectAsArray(o => new OmniSharpPickMembersOption(o)), selectAll: true); + return new(result.Members, result.Options.SelectAsArray(o => o.PickMembersOptionInternal), result.SelectedAll); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/MetadataAsSource/OmniSharpMetadataAsSourceHelpers.cs b/src/Tools/ExternalAccess/OmniSharp/MetadataAsSource/OmniSharpMetadataAsSourceHelpers.cs new file mode 100644 index 0000000000000..8ccfbc90dd413 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/MetadataAsSource/OmniSharpMetadataAsSourceHelpers.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.MetadataAsSource; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.MetadataAsSource +{ + internal static class OmniSharpMetadataAsSourceHelpers + { + public static string GetAssemblyInfo(IAssemblySymbol assemblySymbol) + => MetadataAsSourceHelpers.GetAssemblyInfo(assemblySymbol); + + public static string GetAssemblyDisplay(Compilation compilation, IAssemblySymbol assemblySymbol) + => MetadataAsSourceHelpers.GetAssemblyDisplay(compilation, assemblySymbol); + + public static Task GetLocationInGeneratedSourceAsync(ISymbol symbol, Document generatedDocument, CancellationToken cancellationToken) + { + var symbolKey = SymbolKey.Create(symbol, cancellationToken); + return MetadataAsSourceHelpers.GetLocationInGeneratedSourceAsync(symbolKey, generatedDocument, cancellationToken); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/MetadataAsSource/OmniSharpMetadataAsSourceService.cs b/src/Tools/ExternalAccess/OmniSharp/MetadataAsSource/OmniSharpMetadataAsSourceService.cs new file mode 100644 index 0000000000000..8b0c46170b18c --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/MetadataAsSource/OmniSharpMetadataAsSourceService.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.MetadataAsSource; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.MetadataAsSource +{ + internal static class OmniSharpMetadataAsSourceService + { + /// + /// Generates formatted source code containing general information about the symbol's + /// containing assembly, and the public, protected, and protected-or-internal interface of + /// which the given ISymbol is or is a part of into the given document + /// + /// The document to generate source into + /// The in which is resolved. + /// The symbol to generate source for + /// To cancel document operations + /// The updated document + public static Task AddSourceToAsync(Document document, Compilation symbolCompilation, ISymbol symbol, CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + return service.AddSourceToAsync(document, symbolCompilation, symbol, cancellationToken); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.csproj b/src/Tools/ExternalAccess/OmniSharp/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.csproj new file mode 100644 index 0000000000000..91f25fc7b6bed --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.csproj @@ -0,0 +1,47 @@ + + + + netstandard2.0 + + true + Microsoft.CodeAnalysis.ExternalAccess.OmniSharp + + A supporting package for OmniSharp: + https://github.com/OmniSharp/omnisharp-roslyn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tools/ExternalAccess/OmniSharp/Navigation/OmniSharpNavigableItem.cs b/src/Tools/ExternalAccess/OmniSharp/Navigation/OmniSharpNavigableItem.cs new file mode 100644 index 0000000000000..08a2daa4859a6 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Navigation/OmniSharpNavigableItem.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Navigation +{ + internal readonly struct OmniSharpNavigableItem + { + public OmniSharpNavigableItem(ImmutableArray displayTaggedParts, Document document, TextSpan sourceSpan) + { + DisplayTaggedParts = displayTaggedParts; + Document = document; + SourceSpan = sourceSpan; + } + + public ImmutableArray DisplayTaggedParts { get; } + + public Document Document { get; } + + public TextSpan SourceSpan { get; } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/PickMembers/IOmniSharpPickMembersService.cs b/src/Tools/ExternalAccess/OmniSharp/PickMembers/IOmniSharpPickMembersService.cs new file mode 100644 index 0000000000000..0ffe5cf8e6e2e --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/PickMembers/IOmniSharpPickMembersService.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PickMembers; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.PickMembers +{ + internal interface IOmniSharpPickMembersService + { + OmniSharpPickMembersResult PickMembers( + string title, ImmutableArray members, + ImmutableArray options = default, + bool selectAll = true); + } + + internal class OmniSharpPickMembersOption + { + internal readonly PickMembersOption PickMembersOptionInternal; + + internal OmniSharpPickMembersOption(PickMembersOption pickMembersOption) + { + PickMembersOptionInternal = pickMembersOption; + } + + public string Id => PickMembersOptionInternal.Id; + public string Title => PickMembersOptionInternal.Title; + public bool Value { get => PickMembersOptionInternal.Value; set => PickMembersOptionInternal.Value = value; } + } + + internal class OmniSharpPickMembersResult + { + public readonly ImmutableArray Members; + public readonly ImmutableArray Options; + public readonly bool SelectedAll; + + public OmniSharpPickMembersResult( + ImmutableArray members, + ImmutableArray options, + bool selectedAll) + { + Members = members; + Options = options; + SelectedAll = selectedAll; + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/PublicAPI.Shipped.txt b/src/Tools/ExternalAccess/OmniSharp/PublicAPI.Shipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Tools/ExternalAccess/OmniSharp/PublicAPI.Unshipped.txt b/src/Tools/ExternalAccess/OmniSharp/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockSpan.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockSpan.cs new file mode 100644 index 0000000000000..5489df595309c --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockSpan.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure +{ + internal readonly struct OmniSharpBlockSpan + { + private const string Ellipses = "..."; + + /// + /// Whether or not this span can be collapsed. + /// + public bool IsCollapsible { get; } + + /// + /// The span of text to collapse. + /// + public TextSpan TextSpan { get; } + + /// + /// The span of text to display in the hint on mouse hover. + /// + public TextSpan HintSpan { get; } + + /// + /// The text to display inside the collapsed region. + /// + public string BannerText { get; } + + /// + /// Whether or not this region should be automatically collapsed when the 'Collapse to Definitions' command is invoked. + /// + public bool AutoCollapse { get; } + + /// + /// Whether this region should be collapsed by default when a file is opened the first time. + /// + public bool IsDefaultCollapsed { get; } + + public string Type { get; } + + public OmniSharpBlockSpan( + string type, bool isCollapsible, TextSpan textSpan, string bannerText = Ellipses, bool autoCollapse = false, bool isDefaultCollapsed = false) + : this(type, isCollapsible, textSpan, textSpan, bannerText, autoCollapse, isDefaultCollapsed) + { + } + + public OmniSharpBlockSpan( + string type, bool isCollapsible, TextSpan textSpan, TextSpan hintSpan, string bannerText = Ellipses, bool autoCollapse = false, bool isDefaultCollapsed = false) + { + TextSpan = textSpan; + BannerText = bannerText; + HintSpan = hintSpan; + AutoCollapse = autoCollapse; + IsDefaultCollapsed = isDefaultCollapsed; + IsCollapsible = isCollapsible; + Type = type; + } + + public override string ToString() + { + return this.TextSpan != this.HintSpan + ? $"{{Span={TextSpan}, HintSpan={HintSpan}, BannerText=\"{BannerText}\", AutoCollapse={AutoCollapse}, IsDefaultCollapsed={IsDefaultCollapsed}}}" + : $"{{Span={TextSpan}, BannerText=\"{BannerText}\", AutoCollapse={AutoCollapse}, IsDefaultCollapsed={IsDefaultCollapsed}}}"; + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructure.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructure.cs new file mode 100644 index 0000000000000..688dae9307494 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructure.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure +{ + internal sealed class OmniSharpBlockStructure + { + public ImmutableArray Spans { get; } + + public OmniSharpBlockStructure(ImmutableArray spans) + { + Spans = spans; + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs new file mode 100644 index 0000000000000..e2ea39bb9534c --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Structure; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure +{ + internal static class OmniSharpBlockStructureOptions + { + public static readonly PerLanguageOption ShowBlockStructureGuidesForCommentsAndPreprocessorRegions = (PerLanguageOption)BlockStructureOptions.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions; + + public static readonly PerLanguageOption ShowOutliningForCommentsAndPreprocessorRegions = (PerLanguageOption)BlockStructureOptions.ShowOutliningForCommentsAndPreprocessorRegions; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs new file mode 100644 index 0000000000000..7b56f4a38cf46 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Structure; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure +{ + internal static class OmniSharpBlockStructureService + { + public static async Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken) + { + var service = document.GetRequiredLanguageService(); + var blockStructure = await service.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); + if (blockStructure != null) + { + return new OmniSharpBlockStructure(blockStructure.Spans.SelectAsArray(x => new OmniSharpBlockSpan(x.Type, x.IsCollapsible, x.TextSpan, x.HintSpan, x.BannerText, x.AutoCollapse, x.IsDefaultCollapsed))); + } + else + { + return null; + } + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockTypes.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockTypes.cs new file mode 100644 index 0000000000000..c738d35e77385 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockTypes.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. + +#nullable disable + +using Microsoft.CodeAnalysis.Structure; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure +{ + internal static class OmniSharpBlockTypes + { + // Basic types. + public static string Nonstructural => BlockTypes.Nonstructural; + + // Trivstatic + public static string Comment => BlockTypes.Comment; + public static string PreprocessorRegion => BlockTypes.PreprocessorRegion; + + // Top static declarations. + public static string Imports => BlockTypes.Imports; + public static string Namespace => BlockTypes.Namespace; + public static string Type => BlockTypes.Type; + public static string Member => BlockTypes.Member; + + // Statstatic and expressions. + public static string Statement => BlockTypes.Statement; + public static string Conditional => BlockTypes.Conditional; + public static string Loop => BlockTypes.Loop; + + public static string Expression => BlockTypes.Expression; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharpTest/EnumTests.cs b/src/Tools/ExternalAccess/OmniSharpTest/EnumTests.cs new file mode 100644 index 0000000000000..a552f27c40fc6 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharpTest/EnumTests.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.Linq; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractInterface; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ImplementType; +using Microsoft.CodeAnalysis.ExtractInterface; +using Microsoft.CodeAnalysis.ImplementType; +using Xunit; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests +{ + public class EnumTests + { + [Theory] + [InlineData(typeof(ExtractInterfaceOptionsResult.ExtractLocation), + typeof(OmniSharpExtractInterfaceOptionsResult.OmniSharpExtractLocation))] + [InlineData(typeof(ImplementTypeInsertionBehavior), typeof(OmniSharpImplementTypeInsertionBehavior))] + [InlineData(typeof(ImplementTypePropertyGenerationBehavior), typeof(OmniSharpImplementTypePropertyGenerationBehavior))] + public void AssertEnumsInSync(Type internalType, Type externalType) + { + var internalValues = Enum.GetValues(internalType).Cast().ToArray(); + var internalNames = Enum.GetNames(internalType); + var externalValues = Enum.GetValues(externalType).Cast().ToArray(); + var externalNames = Enum.GetNames(externalType); + + Assert.Equal(internalValues, externalValues); + Assert.Equal(internalNames, externalNames); + } + } +} diff --git a/src/Tools/ExternalAccess/OmniSharpTest/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests.csproj b/src/Tools/ExternalAccess/OmniSharpTest/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests.csproj new file mode 100644 index 0000000000000..faa1126ba2319 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharpTest/Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests.csproj @@ -0,0 +1,21 @@ + + + + + Library + Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests + net472 + + + + + + + + + + + + + + diff --git a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs index fae5a5ee05b3a..ddd37a1577a3d 100644 --- a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs @@ -5,14 +5,17 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor { - internal sealed class RazorSpanMappingServiceWrapper : ISpanMappingService + internal sealed class RazorSpanMappingServiceWrapper : AbstractSpanMappingService { private readonly IRazorSpanMappingService _razorSpanMappingService; @@ -25,9 +28,28 @@ public RazorSpanMappingServiceWrapper(IRazorSpanMappingService razorSpanMappingS /// Modern razor span mapping service can handle if we add imports. Razor will then rewrite that /// to their own form. /// - public bool SupportsMappingImportDirectives => true; + public override bool SupportsMappingImportDirectives => true; - public async Task> MapSpansAsync(Document document, IEnumerable spans, CancellationToken cancellationToken) + public override async Task> GetMappedTextChangesAsync( + Document oldDocument, + Document newDocument, + CancellationToken cancellationToken) + { + var diffService = newDocument.Project.Solution.Workspace.Services.GetRequiredService(); + + // This is a hack that finds a minimal diff. It's not the ideal algorithm but should cover most scenarios. In the future, + // we should improve this algorithm - see https://github.com/dotnet/roslyn/issues/53346 for additional details. + var textChanges = await diffService.GetTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); + var mappedSpanResults = await MapSpansAsync(oldDocument, textChanges.Select(tc => tc.Span), cancellationToken).ConfigureAwait(false); + + var mappedTextChanges = MatchMappedSpansToTextChanges(textChanges, mappedSpanResults); + return mappedTextChanges; + } + + public override async Task> MapSpansAsync( + Document document, + IEnumerable spans, + CancellationToken cancellationToken) { var razorSpans = await _razorSpanMappingService.MapSpansAsync(document, spans, cancellationToken).ConfigureAwait(false); var roslynSpans = new MappedSpanResult[razorSpans.Length]; diff --git a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs index 6d3bccd1c90d5..6eba6f1291013 100644 --- a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs +++ b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs @@ -64,17 +64,14 @@ public void GlobalSetup() // Explicitly choose the sqlite db to test. _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite))); + .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite) + .WithChangedOption(StorageOptions.DatabaseMustSucceed, true))); var connectionPoolService = _workspace.ExportProvider.GetExportedValue(); - _storageService = new SQLitePersistentStorageService(connectionPoolService, new LocationService()); + _storageService = new SQLitePersistentStorageService(_workspace.Options, connectionPoolService, new LocationService()); var solution = _workspace.CurrentSolution; _storage = _storageService.GetStorageWorkerAsync(_workspace, SolutionKey.ToSolutionKey(solution), solution, CancellationToken.None).AsTask().GetAwaiter().GetResult(); - if (_storage == NoOpPersistentStorage.Instance) - { - throw new InvalidOperationException("We didn't properly get the sqlite storage instance."); - } Console.WriteLine("Storage type: " + _storage.GetType()); _document = _workspace.CurrentSolution.Projects.Single().Documents.Single(); diff --git a/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs b/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs new file mode 100644 index 0000000000000..d088da456b528 --- /dev/null +++ b/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Storage.CloudCache; +using Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks; + +namespace CloudCache +{ + [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), ServiceLayer.Host), Shared] + internal class IdeCoreBenchmarksCloudCacheServiceProvider : ICloudCacheStorageServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public IdeCoreBenchmarksCloudCacheServiceProvider() + { + Console.WriteLine($"Instantiated {nameof(IdeCoreBenchmarksCloudCacheServiceProvider)}"); + } + + public AbstractPersistentStorageService Create(IPersistentStorageLocationService locationService) + { + return new MockCloudCachePersistentStorageService( + locationService, @"C:\github\roslyn", cs => + { + if (cs is IAsyncDisposable asyncDisposable) + { + asyncDisposable.DisposeAsync().AsTask().Wait(); + } + else if (cs is IDisposable disposable) + { + disposable.Dispose(); + } + }); + } + } +} diff --git a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs index 3e87232092900..cc87d359b680b 100644 --- a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs @@ -5,16 +5,20 @@ #nullable disable using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using AnalyzerRunner; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; +using Microsoft.Build.Locator; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.Storage; @@ -23,77 +27,86 @@ namespace IdeCoreBenchmarks [MemoryDiagnoser] public class FindReferencesBenchmarks { - private readonly string _solutionPath; - - private MSBuildWorkspace _workspace; - - public FindReferencesBenchmarks() - { - var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); - _solutionPath = Path.Combine(roslynRoot, @"C:\github\roslyn\Roslyn.sln"); - - if (!File.Exists(_solutionPath)) - throw new ArgumentException("Couldn't find Roslyn.sln"); - - Console.Write("Found roslyn.sln: " + Process.GetCurrentProcess().Id); - } - - [GlobalSetup] - public void Setup() - { - _workspace = AnalyzerRunnerHelper.CreateWorkspace(); - if (_workspace == null) - throw new ArgumentException("Couldn't create workspace"); - - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite))); - - Console.WriteLine("Opening roslyn. Attach to: " + Process.GetCurrentProcess().Id); - - var start = DateTime.Now; - _ = _workspace.OpenSolutionAsync(_solutionPath, progress: null, CancellationToken.None).Result; - Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start)); - - // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we - // perform, including seeing how big the initial string table is. - var storageService = _workspace.Services.GetService(); - if (storageService == null) - throw new ArgumentException("Couldn't get storage service"); - - using var storage = storageService.GetStorageAsync(_workspace.CurrentSolution, CancellationToken.None).AsTask().GetAwaiter().GetResult(); - } - - [GlobalCleanup] - public void Cleanup() - { - _workspace?.Dispose(); - _workspace = null; - } - [Benchmark] public async Task RunFindReferences() { - var solution = _workspace.CurrentSolution; - - // There might be multiple projects with this name. That's ok. FAR goes and finds all the linked-projects - // anyways to perform the search on all the equivalent symbols from them. So the end perf cost is the - // same. - var project = solution.Projects.First(p => p.AssemblyName == "Microsoft.CodeAnalysis.CSharp"); - - var start = DateTime.Now; - var compilation = await project.GetCompilationAsync(); - Console.WriteLine("Time to get first compilation: " + (DateTime.Now - start)); - var type = compilation.GetTypeByMetadataName("Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.LanguageParser"); - if (type == null) - throw new Exception("Couldn't find type"); - - start = DateTime.Now; - var references = await SymbolFinder.FindReferencesAsync(type, solution); - Console.WriteLine("Time to find-refs: " + (DateTime.Now - start)); - var refList = references.ToList(); - Console.WriteLine($"References count: {refList.Count}"); - var locations = refList.SelectMany(r => r.Locations).ToList(); - Console.WriteLine($"Locations count: {locations.Count}"); + try + { + // QueryVisualStudioInstances returns Visual Studio installations on .NET Framework, and .NET Core SDK + // installations on .NET Core. We use the one with the most recent version. + var msBuildInstance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(x => x.Version).First(); + + MSBuildLocator.RegisterInstance(msBuildInstance); + + var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); + var solutionPath = Path.Combine(roslynRoot, @"C:\github\roslyn\Compilers.sln"); + + if (!File.Exists(solutionPath)) + throw new ArgumentException("Couldn't find Compilers.sln"); + + Console.Write("Found Compilers.sln: " + Process.GetCurrentProcess().Id); + + var assemblies = MSBuildMefHostServices.DefaultAssemblies + .Add(typeof(AnalyzerRunnerHelper).Assembly) + .Add(typeof(FindReferencesBenchmarks).Assembly); + var services = MefHostServices.Create(assemblies); + + var workspace = MSBuildWorkspace.Create(new Dictionary + { + // Use the latest language version to force the full set of available analyzers to run on the project. + { "LangVersion", "9.0" }, + }, services); + + if (workspace == null) + throw new ArgumentException("Couldn't create workspace"); + + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options + .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite) + .WithChangedOption(StorageOptions.DatabaseMustSucceed, true))); + + Console.WriteLine("Opening roslyn. Attach to: " + Process.GetCurrentProcess().Id); + + var start = DateTime.Now; + var solution = workspace.OpenSolutionAsync(solutionPath, progress: null, CancellationToken.None).Result; + Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start)); + + // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we + // perform, including seeing how big the initial string table is. + var storageService = workspace.Services.GetService(); + if (storageService == null) + throw new ArgumentException("Couldn't get storage service"); + + using (var storage = await storageService.GetStorageAsync(workspace.CurrentSolution, CancellationToken.None)) + { + Console.WriteLine(); + } + + // There might be multiple projects with this name. That's ok. FAR goes and finds all the linked-projects + // anyways to perform the search on all the equivalent symbols from them. So the end perf cost is the + // same. + var project = solution.Projects.First(p => p.AssemblyName == "Microsoft.CodeAnalysis"); + + start = DateTime.Now; + var compilation = await project.GetCompilationAsync(); + Console.WriteLine("Time to get first compilation: " + (DateTime.Now - start)); + var type = compilation.GetTypeByMetadataName("Microsoft.CodeAnalysis.SyntaxToken"); + if (type == null) + throw new Exception("Couldn't find type"); + + Console.WriteLine("Starting find-refs"); + start = DateTime.Now; + var references = await SymbolFinder.FindReferencesAsync(type, solution); + Console.WriteLine("Time to find-refs: " + (DateTime.Now - start)); + var refList = references.ToList(); + Console.WriteLine($"References count: {refList.Count}"); + var locations = refList.SelectMany(r => r.Locations).ToList(); + Console.WriteLine($"Locations count: {locations.Count}"); + } + catch (ReflectionTypeLoadException ex) + { + foreach (var ex2 in ex.LoaderExceptions) + Console.WriteLine(ex2); + } } } } diff --git a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj index 759610bb4ce53..5ece62295b8fd 100644 --- a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj +++ b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj @@ -10,14 +10,36 @@ AnyCPU true + 9 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index d61028cb6b9ff..164b23ee594c3 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -10,13 +10,16 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using AnalyzerRunner; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; +using Microsoft.Build.Locator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Storage; @@ -26,82 +29,95 @@ namespace IdeCoreBenchmarks [MemoryDiagnoser] public class NavigateToBenchmarks { - private readonly string _solutionPath; + [Benchmark] + public async Task RunNavigateTo() + { + try + { + // QueryVisualStudioInstances returns Visual Studio installations on .NET Framework, and .NET Core SDK + // installations on .NET Core. We use the one with the most recent version. + var msBuildInstance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(x => x.Version).First(); - private MSBuildWorkspace _workspace; + MSBuildLocator.RegisterInstance(msBuildInstance); - public NavigateToBenchmarks() - { - // var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); - _solutionPath = @"C:\github\roslyn\Roslyn.sln"; + var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); + var solutionPath = Path.Combine(roslynRoot, @"C:\github\roslyn\Roslyn.sln"); - if (!File.Exists(_solutionPath)) - throw new ArgumentException("Couldn't find Roslyn.sln"); + if (!File.Exists(solutionPath)) + throw new ArgumentException("Couldn't find Roslyn.sln"); - Console.Write("Found roslyn.sln"); - } + Console.Write("Found Roslyn.sln: " + Process.GetCurrentProcess().Id); - [GlobalSetup] - public void Setup() - { - _workspace = AnalyzerRunnerHelper.CreateWorkspace(); - if (_workspace == null) - throw new ArgumentException("Couldn't create workspace"); + var assemblies = MSBuildMefHostServices.DefaultAssemblies + .Add(typeof(AnalyzerRunnerHelper).Assembly) + .Add(typeof(FindReferencesBenchmarks).Assembly); + var services = MefHostServices.Create(assemblies); - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite))); + var workspace = MSBuildWorkspace.Create(new Dictionary + { + // Use the latest language version to force the full set of available analyzers to run on the project. + { "LangVersion", "9.0" }, + }, services); - Console.WriteLine("Opening roslyn"); - var start = DateTime.Now; - _ = _workspace.OpenSolutionAsync(_solutionPath, progress: null, CancellationToken.None).Result; - Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start)); + if (workspace == null) + throw new ArgumentException("Couldn't create workspace"); - var storageService = _workspace.Services.GetService(); - if (storageService == null) - throw new ArgumentException("Couldn't get storage service"); + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options + .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite) + .WithChangedOption(StorageOptions.DatabaseMustSucceed, true))); - // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we - // perform, including seeing how big the initial string table is. - using var storage = storageService.GetStorageAsync(_workspace.CurrentSolution, CancellationToken.None).AsTask().GetAwaiter().GetResult(); - } + Console.WriteLine("Opening roslyn. Attach to: " + Process.GetCurrentProcess().Id); - [GlobalCleanup] - public void Cleanup() - { - _workspace?.Dispose(); - _workspace = null; - } + var start = DateTime.Now; + var solution = workspace.OpenSolutionAsync(solutionPath, progress: null, CancellationToken.None).Result; + Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start)); - [Benchmark] - public async Task RunNavigateTo() - { - var sw = new Stopwatch(); - sw.Start(); - var solution = _workspace.CurrentSolution; - // Search each project with an independent threadpool task. - var searchTasks = solution.Projects.Select( - p => Task.Run(() => SearchAsync(p, priorityDocuments: ImmutableArray.Empty), CancellationToken.None)).ToArray(); - - var result = await Task.WhenAll(searchTasks).ConfigureAwait(false); - var sum = result.Sum(); - sw.Stop(); - - Console.WriteLine($"Time: {sw.ElapsedMilliseconds}"); + // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we + // perform, including seeing how big the initial string table is. + var storageService = workspace.Services.GetService(); + if (storageService == null) + throw new ArgumentException("Couldn't get storage service"); + + using (var storage = await storageService.GetStorageAsync(workspace.CurrentSolution, CancellationToken.None)) + { + Console.WriteLine(); + } + + Console.WriteLine("Starting navigate to"); + + start = DateTime.Now; + // Search each project with an independent threadpool task. + var searchTasks = solution.Projects.Select( + p => Task.Run(() => SearchAsync(p, priorityDocuments: ImmutableArray.Empty), CancellationToken.None)).ToArray(); + + var result = await Task.WhenAll(searchTasks).ConfigureAwait(false); + var sum = result.Sum(); + + start = DateTime.Now; + Console.WriteLine("Num results: " + (DateTime.Now - start)); + } + catch (ReflectionTypeLoadException ex) + { + foreach (var ex2 in ex.LoaderExceptions) + Console.WriteLine(ex2); + } } private async Task SearchAsync(Project project, ImmutableArray priorityDocuments) { var service = project.LanguageServices.GetService(); - var count = 0; + var results = new List(); await service.SearchProjectAsync( project, priorityDocuments, "Document", service.KindsProvided, r => { - Interlocked.Increment(ref count); + lock (results) + results.Add(r); + return Task.CompletedTask; }, isFullyLoaded: true, CancellationToken.None); - return count; + return results.Count; } } } diff --git a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs new file mode 100644 index 0000000000000..f0ebc7a40a8cd --- /dev/null +++ b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System; +using System.IO; +using System.Threading; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace IdeCoreBenchmarks +{ + [MemoryDiagnoser] + public class SyntacticChangeRangeBenchmark + { + private int _index; + private SourceText _text; + private SyntaxTree _tree; + private SyntaxNode _root; + + private SyntaxNode _rootWithSimpleEdit; + private SyntaxNode _rootWithComplexEdit; + + [GlobalSetup] + public void GlobalSetup() + { + var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); + var csFilePath = Path.Combine(roslynRoot, @"src\Compilers\CSharp\Portable\Generated\BoundNodes.xml.Generated.cs"); + + if (!File.Exists(csFilePath)) + throw new FileNotFoundException(csFilePath); + + var text = File.ReadAllText(csFilePath); + _index = text.IndexOf("switch (node.Kind)"); + if (_index < 0) + throw new ArgumentException("Code location not found"); + + _text = SourceText.From(text); + _tree = SyntaxFactory.ParseSyntaxTree(_text); + _root = _tree.GetCompilationUnitRoot(); + _rootWithSimpleEdit = WithSimpleEditAtMiddle(); + _rootWithComplexEdit = WithDestabalizingEditAtMiddle(); + } + + private SyntaxNode WithSimpleEditAtMiddle() + { + // this will change the switch statement to `mode.kind` instead of `node.kind`. This should be reuse most + // of the tree and should result in a very small diff. + var newText = _text.WithChanges(new TextChange(new TextSpan(_index + 8, 1), "m")); + var newTree = _tree.WithChangedText(newText); + var newRoot = newTree.GetRoot(); + return newRoot; + } + + private SyntaxNode WithDestabalizingEditAtMiddle() + { + // this will change the switch statement to a switch expression. This may have large cascading changes. + var newText = _text.WithChanges(new TextChange(new TextSpan(_index, 0), "var v = x ")); + var newTree = _tree.WithChangedText(newText); + var newRoot = newTree.GetRoot(); + return newRoot; + } + + [Benchmark] + public void SimpleEditAtMiddle() + { + var newRoot = WithSimpleEditAtMiddle(); + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, TimeSpan.MaxValue, CancellationToken.None); + } + + [Benchmark] + public void DestabalizingEditAtMiddle() + { + var newRoot = WithDestabalizingEditAtMiddle(); + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, TimeSpan.MaxValue, CancellationToken.None); + } + + [Benchmark] + public void SimpleEditAtMiddle_NoParse() + { + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithSimpleEdit, TimeSpan.MaxValue, CancellationToken.None); + } + + [Benchmark] + public void DestabalizingEditAtMiddle_NoParse() + { + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithComplexEdit, TimeSpan.MaxValue, CancellationToken.None); + } + } +} diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/AbstractFileWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/AbstractFileWriter.cs index 6b2cca25ead4a..5fec3287b3cfd 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/AbstractFileWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/AbstractFileWriter.cs @@ -372,6 +372,22 @@ protected static bool IsKeyword(string name) } } + protected List GetKindsOfFieldOrNearestParent(TreeType nd, Field field) + { + while ((field.Kinds is null || field.Kinds.Count == 0) && IsOverride(field)) + { + nd = GetTreeType(nd.Base); + field = (nd switch + { + Node node => node.Fields, + AbstractNode abstractNode => abstractNode.Fields, + _ => throw new InvalidOperationException("Unexpected node type.") + }).Single(f => f.Name == field.Name); + } + + return field.Kinds; + } + #endregion Node helpers } } diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs index d1a017c1e0a94..152871bbd512e 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs @@ -1555,9 +1555,10 @@ private void WriteRedFactory(Node nd) if (field.Type == "SyntaxToken") { - if (field.Kinds != null && field.Kinds.Count > 0) + var fieldKinds = GetKindsOfFieldOrNearestParent(nd, field); + if (fieldKinds != null && fieldKinds.Count > 0) { - var kinds = field.Kinds.ToList(); + var kinds = fieldKinds.ToList(); if (IsOptional(field)) { kinds.Add(new Kind { Name = "None" }); diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/TestWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/TestWriter.cs index b13b8fda21721..16cdb9e0ca888 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/TestWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/TestWriter.cs @@ -150,7 +150,7 @@ private void WriteNodeGenerator(Node node, bool isGreen) } else if (field.Type == "SyntaxToken") { - var kind = ChooseValidKind(field); + var kind = ChooseValidKind(field, node); var leadingTrivia = isGreen ? "null, " : string.Empty; var trailingTrivia = isGreen ? ", null" : string.Empty; if (kind == "IdentifierToken") @@ -171,7 +171,7 @@ private void WriteNodeGenerator(Node node, bool isGreen) } else { - Write($"{syntaxFactory}.Token(SyntaxKind.{ChooseValidKind(field)})"); + Write($"{syntaxFactory}.Token(SyntaxKind.{kind})"); } } else if (field.Type == "CSharpSyntaxNode") @@ -257,13 +257,14 @@ private void WriteFactoryPropertyTest(Node node, bool isGreen) } else if (field.Type == "SyntaxToken") { + var kind = ChooseValidKind(field, node); if (!isGreen) { - WriteLine($"Assert.Equal(SyntaxKind.{ChooseValidKind(field)}, node.{field.Name}.Kind());"); + WriteLine($"Assert.Equal(SyntaxKind.{kind}, node.{field.Name}.Kind());"); } else { - WriteLine($"Assert.Equal(SyntaxKind.{ChooseValidKind(field)}, node.{field.Name}.Kind);"); + WriteLine($"Assert.Equal(SyntaxKind.{kind}, node.{field.Name}.Kind);"); } } else @@ -384,9 +385,10 @@ private void WriteIdentityRewriterTest(Node node) } //guess a reasonable kind if there are no constraints - private static string ChooseValidKind(Field field) + private string ChooseValidKind(Field field, Node nd) { - return field.Kinds.Any() ? field.Kinds[0].Name : "IdentifierToken"; + var fieldKinds = GetKindsOfFieldOrNearestParent(nd, field); + return fieldKinds?.Any() == true ? fieldKinds[0].Name : "IdentifierToken"; } } } diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs index 698fb13621295..a894d43850f08 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs @@ -156,6 +156,21 @@ private void WriteFiles() } } + using (_writer = new StreamWriter(File.Open(Path.Combine(_location, "OperationKind.Generated.cs"), FileMode.Create), Encoding.UTF8)) + { + writeHeader(); + WriteUsing("System"); + WriteUsing("System.ComponentModel"); + WriteUsing("Microsoft.CodeAnalysis.FlowAnalysis"); + WriteUsing("Microsoft.CodeAnalysis.Operations"); + + WriteStartNamespace(namespaceSuffix: null); + + WriteOperationKind(); + + WriteEndNamespace(); + } + void writeHeader() { WriteLine("// Licensed to the .NET Foundation under one or more agreements."); diff --git a/src/Tools/Source/RunTests/TestRunner.cs b/src/Tools/Source/RunTests/TestRunner.cs index 1d4ebb4f74fdf..cab5d85735c12 100644 --- a/src/Tools/Source/RunTests/TestRunner.cs +++ b/src/Tools/Source/RunTests/TestRunner.cs @@ -158,7 +158,9 @@ string makeHelixWorkItemProject(AssemblyInfo assemblyInfo) var rehydrateFilename = isUnix ? "rehydrate.sh" : "rehydrate.cmd"; var lsCommand = isUnix ? "ls" : "dir"; var rehydrateCommand = isUnix ? $"./{rehydrateFilename}" : $@"call .\{rehydrateFilename}"; - var setEnvironmentVariables = Environment.GetEnvironmentVariable("ROSLYN_TEST_IOPERATION") is { } iop + var setRollforward = $"{(isUnix ? "export" : "set")} DOTNET_ROLL_FORWARD=LatestMajor"; + var setPrereleaseRollforward = $"{(isUnix ? "export" : "set")} DOTNET_ROLL_FORWARD_TO_PRERELEASE=1"; + var setTestIOperation = Environment.GetEnvironmentVariable("ROSLYN_TEST_IOPERATION") is { } iop ? $"{(isUnix ? "export" : "set")} ROSLYN_TEST_IOPERATION={iop}" : ""; var workItem = $@" @@ -168,8 +170,10 @@ string makeHelixWorkItemProject(AssemblyInfo assemblyInfo) {lsCommand} {rehydrateCommand} {lsCommand} - dotnet --version - {setEnvironmentVariables} + {setRollforward} + {setPrereleaseRollforward} + dotnet --info + {setTestIOperation} dotnet {commandLineArguments} 00:15:00 @@ -245,7 +249,7 @@ internal async Task RunAllAsync(IEnumerable assembly } // Display the current status of the TestRunner. - // Note: The { ... , 2 } is to right align the values, thus aligns sections into columns. + // Note: The { ... , 2 } is to right align the values, thus aligns sections into columns. ConsoleUtil.Write($" {running.Count,2} running, {waiting.Count,2} queued, {completed.Count,2} completed"); if (failures > 0) { diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs index f9f39b3d1d5d5..c987308a0c697 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs @@ -41,8 +41,10 @@ private class NodeLocator : AbstractNodeLocator case SyntaxKind.AttributeArgument: return GetStartPoint(text, (AttributeArgumentSyntax)node, part); case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.EnumDeclaration: return GetStartPoint(text, (BaseTypeDeclarationSyntax)node, part); case SyntaxKind.MethodDeclaration: @@ -89,8 +91,10 @@ private class NodeLocator : AbstractNodeLocator case SyntaxKind.AttributeArgument: return GetEndPoint(text, (AttributeArgumentSyntax)node, part); case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.EnumDeclaration: return GetEndPoint(text, (BaseTypeDeclarationSyntax)node, part); case SyntaxKind.MethodDeclaration: diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs index 26716a4b8856d..8949d161763dd 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs @@ -169,7 +169,9 @@ protected override void AppendNodeName(StringBuilder builder, SyntaxNode node) break; case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: var typeDeclaration = (TypeDeclarationSyntax)node; builder.Append(typeDeclaration.Identifier.ValueText); diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs index 1c41e712d2b92..b971a269abbb5 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs @@ -88,6 +88,7 @@ private static bool IsNameableNode(SyntaxNode node) switch (node.Kind()) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.DelegateDeclaration: @@ -102,6 +103,7 @@ private static bool IsNameableNode(SyntaxNode node) case SyntaxKind.OperatorDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return true; case SyntaxKind.VariableDeclarator: diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs index ef1afad5952f6..93823b5ae6970 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpCodeCleanupFixerProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeCleanup { [Export(typeof(ICodeCleanUpFixerProvider))] - [AppliesToProject(ContentTypeNames.CSharpContentType)] + [AppliesToProject("CSharp")] [ContentType(ContentTypeNames.CSharpContentType)] internal class CSharpCodeCleanUpFixerProvider : AbstractCodeCleanUpFixerProvider { diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs index eb480e1993f1b..600982810868c 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs @@ -215,6 +215,13 @@ private static bool TryGetTextForOperator(SyntaxToken token, Document document, return true; } + // Workaround IsPredefinedOperator returning true for '<' in generics. + if (token is { RawKind: (int)SyntaxKind.LessThanToken, Parent: not BinaryExpressionSyntax }) + { + text = null; + return false; + } + var syntaxFacts = document.GetLanguageService(); if (syntaxFacts.IsOperator(token) || syntaxFacts.IsPredefinedOperator(token) || SyntaxFacts.IsAssignmentExpressionOperatorToken(token.Kind())) { diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index eb5e861771f5d..eb94fe1fa39dc 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -45,6 +45,9 @@ + + + + @@ -257,6 +264,13 @@ + + + + + diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs index c1942f695298e..8d6f37ebbdc88 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs @@ -6,6 +6,7 @@ using System.Windows; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.Editing; @@ -26,6 +27,7 @@ using Microsoft.CodeAnalysis.ValidateFormatString; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.ColorSchemes; +using Microsoft.VisualStudio.LanguageServices.Implementation; using Microsoft.VisualStudio.LanguageServices.Implementation.Options; namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options @@ -45,6 +47,7 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(Background_analysis_scope_full_solution, SolutionCrawlerOptions.BackgroundAnalysisScopeOption, BackgroundAnalysisScope.FullSolution, LanguageNames.CSharp); BindToOption(Enable_navigation_to_decompiled_sources, FeatureOnOffOptions.NavigateToDecompiledSources); BindToOption(Use_64bit_analysis_process, RemoteHostOptions.OOP64Bit); + BindToOption(Enable_file_logging_for_diagnostics, InternalDiagnosticsOptions.EnableFileLoggingForDiagnostics); BindToOption(Show_Remove_Unused_References_command_in_Solution_Explorer_experimental, FeatureOnOffOptions.OfferRemoveUnusedReferences, () => { // If the option has not been set by the user, check if the option to remove unused references @@ -86,6 +89,13 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(EnableHighlightReferences, FeatureOnOffOptions.ReferenceHighlighting, LanguageNames.CSharp); BindToOption(EnableHighlightKeywords, FeatureOnOffOptions.KeywordHighlighting, LanguageNames.CSharp); BindToOption(RenameTrackingPreview, FeatureOnOffOptions.RenameTrackingPreview, LanguageNames.CSharp); + BindToOption(Underline_reassigned_variables, ClassificationOptions.ClassifyReassignedVariables, LanguageNames.CSharp); + BindToOption(Enable_all_features_in_opened_files_from_source_generators, SourceGeneratedFileManager.EnableOpeningInWorkspace, () => + { + // If the option has not been set by the user, check if the option is enabled from experimentation. + // If so, default to that. Otherwise default to disabled + return experimentationService?.IsExperimentEnabled(WellKnownExperimentNames.SourceGeneratorsEnableOpeningInWorkspace) ?? false; + }); BindToOption(DontPutOutOrRefOnStruct, ExtractMethodOptions.DontPutOutOrRefOnStruct, LanguageNames.CSharp); @@ -118,6 +128,8 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(ShowHintsForVariablesWithInferredTypes, InlineHintsOptions.ForImplicitVariableTypes, LanguageNames.CSharp); BindToOption(ShowHintsForLambdaParameterTypes, InlineHintsOptions.ForLambdaParameterTypes, LanguageNames.CSharp); BindToOption(ShowHintsForImplicitObjectCreation, InlineHintsOptions.ForImplicitObjectCreation, LanguageNames.CSharp); + + BindToOption(ShowInheritanceMargin, FeatureOnOffOptions.ShowInheritanceMargin, LanguageNames.CSharp); } // Since this dialog is constructed once for the lifetime of the application and VS Theme can be changed after the application has started, diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs index da10bad576be9..8454fcf481045 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs @@ -82,15 +82,14 @@ public static string Option_Show_hints_for_implicit_object_creation public static string Option_DisplayLineSeparators => CSharpVSResources.Show_procedure_line_separators; + public static string Option_Underline_reassigned_variables + => ServicesVSResources.Underline_reassigned_variables; + public static string Option_DontPutOutOrRefOnStruct - { - get { return CSharpVSResources.Don_t_put_ref_or_out_on_custom_struct; } - } + => CSharpVSResources.Don_t_put_ref_or_out_on_custom_struct; public static string Option_EditorHelp - { - get { return CSharpVSResources.Editor_Help; } - } + => CSharpVSResources.Editor_Help; public static string Option_EnableHighlightKeywords { @@ -267,5 +266,17 @@ public static SchemeName Color_Scheme_VisualStudio2017_Tag public static string Option_Show_Remove_Unused_References_command_in_Solution_Explorer_experimental => ServicesVSResources.Show_Remove_Unused_References_command_in_Solution_Explorer_experimental; + + public static string Enable_all_features_in_opened_files_from_source_generators_experimental + => ServicesVSResources.Enable_all_features_in_opened_files_from_source_generators_experimental; + + public static string Option_Enable_file_logging_for_diagnostics + => ServicesVSResources.Enable_file_logging_for_diagnostics; + + public static string Show_inheritance_margin + => ServicesVSResources.Show_inheritance_margin; + + public static string Inheritance_Margin_experimental + => ServicesVSResources.Inheritance_Margin_experimental; } } diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs deleted file mode 100644 index c9534c2b42bbe..0000000000000 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs +++ /dev/null @@ -1,837 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Runtime.InteropServices; -using System.Xml.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.BraceCompletion; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Formatting; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.DocumentationComments; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Editor.Shared.Options; -using Microsoft.CodeAnalysis.ExtractMethod; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.SymbolSearch; - -namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options -{ - using Workspace = Microsoft.CodeAnalysis.Workspace; - - [ComVisible(true)] - public class AutomationObject - { - private readonly Workspace _workspace; - - internal AutomationObject(Workspace workspace) - => _workspace = workspace; - - /// - /// Unused. But kept around for back compat. Note this option is not about - /// turning warning into errors. It's about an aspect of 'remove unused using' - /// functionality we don't support anymore. Namely whether or not 'remove unused - /// using' should warn if you have any build errors as that might mean we - /// remove some usings inappropriately. - /// - public int WarnOnBuildErrors - { - get { return 0; } - set { } - } - - public int AutoComment - { - get { return GetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration); } - set { SetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration, value); } - } - - public int AutoInsertAsteriskForNewLinesOfBlockComments - { - get { return GetBooleanOption(FeatureOnOffOptions.AutoInsertBlockCommentStartString); } - set { SetBooleanOption(FeatureOnOffOptions.AutoInsertBlockCommentStartString, value); } - } - - public int BringUpOnIdentifier - { - get { return GetBooleanOption(CompletionOptions.TriggerOnTypingLetters2); } - set { SetBooleanOption(CompletionOptions.TriggerOnTypingLetters2, value); } - } - - public int HighlightMatchingPortionsOfCompletionListItems - { - get { return GetBooleanOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems); } - set { SetBooleanOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems, value); } - } - - public int ShowCompletionItemFilters - { - get { return GetBooleanOption(CompletionOptions.ShowCompletionItemFilters); } - set { SetBooleanOption(CompletionOptions.ShowCompletionItemFilters, value); } - } - - public int ShowItemsFromUnimportedNamespaces - { - get { return GetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces); } - set { SetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, value); } - } - - [Obsolete("ClosedFileDiagnostics has been deprecated")] - public int ClosedFileDiagnostics - { - get { return 0; } - set { } - } - - [Obsolete("CSharpClosedFileDiagnostics has been deprecated")] - public int CSharpClosedFileDiagnostics - { - get { return 0; } - set { } - } - - public int DisplayLineSeparators - { - get { return GetBooleanOption(FeatureOnOffOptions.LineSeparator); } - set { SetBooleanOption(FeatureOnOffOptions.LineSeparator, value); } - } - - public int EnableHighlightRelatedKeywords - { - get { return GetBooleanOption(FeatureOnOffOptions.KeywordHighlighting); } - set { SetBooleanOption(FeatureOnOffOptions.KeywordHighlighting, value); } - } - - public int EnterOutliningModeOnOpen - { - get { return GetBooleanOption(FeatureOnOffOptions.Outlining); } - set { SetBooleanOption(FeatureOnOffOptions.Outlining, value); } - } - - public int ExtractMethod_DoNotPutOutOrRefOnStruct - { - get { return GetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct); } - set { SetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct, value); } - } - - public int Formatting_TriggerOnBlockCompletion - { - get { return GetBooleanOption(BraceCompletionOptions.AutoFormattingOnCloseBrace); } - set { SetBooleanOption(BraceCompletionOptions.AutoFormattingOnCloseBrace, value); } - } - - public int Formatting_TriggerOnPaste - { - get { return GetBooleanOption(FeatureOnOffOptions.FormatOnPaste); } - set { SetBooleanOption(FeatureOnOffOptions.FormatOnPaste, value); } - } - - public int Formatting_TriggerOnStatementCompletion - { - get { return GetBooleanOption(FeatureOnOffOptions.AutoFormattingOnSemicolon); } - set { SetBooleanOption(FeatureOnOffOptions.AutoFormattingOnSemicolon, value); } - } - - public int HighlightReferences - { - get { return GetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting); } - set { SetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting, value); } - } - - public int Indent_BlockContents - { - get { return GetBooleanOption(CSharpFormattingOptions2.IndentBlock); } - set { SetBooleanOption(CSharpFormattingOptions2.IndentBlock, value); } - } - - public int Indent_Braces - { - get { return GetBooleanOption(CSharpFormattingOptions2.IndentBraces); } - set { SetBooleanOption(CSharpFormattingOptions2.IndentBraces, value); } - } - - public int Indent_CaseContents - { - get { return GetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSection); } - set { SetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSection, value); } - } - - public int Indent_CaseContentsWhenBlock - { - get { return GetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSectionWhenBlock); } - set { SetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSectionWhenBlock, value); } - } - - public int Indent_CaseLabels - { - get { return GetBooleanOption(CSharpFormattingOptions2.IndentSwitchSection); } - set { SetBooleanOption(CSharpFormattingOptions2.IndentSwitchSection, value); } - } - - public int Indent_FlushLabelsLeft - { - get - { - var option = _workspace.Options.GetOption(CSharpFormattingOptions2.LabelPositioning); - return option == LabelPositionOptions.LeftMost ? 1 : 0; - } - - set - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(CSharpFormattingOptions2.LabelPositioning, value == 1 ? LabelPositionOptions.LeftMost : LabelPositionOptions.NoIndent))); - } - } - - public int Indent_UnindentLabels - { - get - { - return (int)_workspace.Options.GetOption(CSharpFormattingOptions2.LabelPositioning); - } - - set - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(CSharpFormattingOptions2.LabelPositioning, value))); - } - } - - public int InsertNewlineOnEnterWithWholeWord - { - get { return (int)GetOption(CompletionOptions.EnterKeyBehavior); } - set { SetOption(CompletionOptions.EnterKeyBehavior, (EnterKeyRule)value); } - } - - public int EnterKeyBehavior - { - get { return (int)GetOption(CompletionOptions.EnterKeyBehavior); } - set { SetOption(CompletionOptions.EnterKeyBehavior, (EnterKeyRule)value); } - } - - public int SnippetsBehavior - { - get { return (int)GetOption(CompletionOptions.SnippetsBehavior); } - set { SetOption(CompletionOptions.SnippetsBehavior, (SnippetsRule)value); } - } - - public int NewLines_AnonymousTypeInitializer_EachMember - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInAnonymousTypes); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInAnonymousTypes, value); } - } - - public int NewLines_Braces_AnonymousMethod - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousMethods); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousMethods, value); } - } - - public int NewLines_Braces_AnonymousTypeInitializer - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousTypes); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousTypes, value); } - } - - public int NewLines_Braces_ControlFlow - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInControlBlocks); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInControlBlocks, value); } - } - - public int NewLines_Braces_LambdaExpressionBody - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInLambdaExpressionBody); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInLambdaExpressionBody, value); } - } - - public int NewLines_Braces_Method - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInMethods); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInMethods, value); } - } - - public int NewLines_Braces_Property - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInProperties); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInProperties, value); } - } - - public int NewLines_Braces_Accessor - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAccessors); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAccessors, value); } - } - - public int NewLines_Braces_ObjectInitializer - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, value); } - } - - public int NewLines_Braces_Type - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInTypes); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInTypes, value); } - } - - public int NewLines_Keywords_Catch - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForCatch); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLineForCatch, value); } - } - - public int NewLines_Keywords_Else - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForElse); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLineForElse, value); } - } - - public int NewLines_Keywords_Finally - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForFinally); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLineForFinally, value); } - } - - public int NewLines_ObjectInitializer_EachMember - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInObjectInit); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInObjectInit, value); } - } - - public int NewLines_QueryExpression_EachClause - { - get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForClausesInQuery); } - set { SetBooleanOption(CSharpFormattingOptions2.NewLineForClausesInQuery, value); } - } - - public int Refactoring_Verification_Enabled - { - get { return GetBooleanOption(FeatureOnOffOptions.RefactoringVerification); } - set { SetBooleanOption(FeatureOnOffOptions.RefactoringVerification, value); } - } - - public int RenameSmartTagEnabled - { - get { return GetBooleanOption(FeatureOnOffOptions.RenameTracking); } - set { SetBooleanOption(FeatureOnOffOptions.RenameTracking, value); } - } - - public int RenameTrackingPreview - { - get { return GetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview); } - set { SetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview, value); } - } - - public int ShowKeywords - { - get { return 0; } - set { } - } - - [Obsolete("Use SnippetsBehavior instead")] - public int ShowSnippets - { - get - { - return GetOption(CompletionOptions.SnippetsBehavior) == SnippetsRule.AlwaysInclude - ? 1 : 0; - } - - set - { - if (value == 0) - { - SetOption(CompletionOptions.SnippetsBehavior, SnippetsRule.NeverInclude); - } - else - { - SetOption(CompletionOptions.SnippetsBehavior, SnippetsRule.AlwaysInclude); - } - } - } - - public int SortUsings_PlaceSystemFirst - { - get { return GetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst); } - set { SetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst, value); } - } - - public int AddImport_SuggestForTypesInReferenceAssemblies - { - get { return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies); } - set { SetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, value); } - } - - public int AddImport_SuggestForTypesInNuGetPackages - { - get { return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages); } - set { SetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages, value); } - } - - public int Space_AfterBasesColon - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterColonInBaseTypeDeclaration); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterColonInBaseTypeDeclaration, value); } - } - - public int Space_AfterCast - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterCast); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterCast, value); } - } - - public int Space_AfterComma - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterComma); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterComma, value); } - } - - public int Space_AfterDot - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterDot); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterDot, value); } - } - - public int Space_AfterMethodCallName - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterMethodCallName); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterMethodCallName, value); } - } - - public int Space_AfterMethodDeclarationName - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpacingAfterMethodDeclarationName); } - set { SetBooleanOption(CSharpFormattingOptions2.SpacingAfterMethodDeclarationName, value); } - } - - public int Space_AfterSemicolonsInForStatement - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterSemicolonsInForStatement); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterSemicolonsInForStatement, value); } - } - - public int Space_AroundBinaryOperator - { - get - { - var option = _workspace.Options.GetOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator); - return option == BinaryOperatorSpacingOptions.Single ? 1 : 0; - } - - set - { - var option = value == 1 ? BinaryOperatorSpacingOptions.Single : BinaryOperatorSpacingOptions.Ignore; - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator, option))); - } - } - - public int Space_BeforeBasesColon - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeColonInBaseTypeDeclaration); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeColonInBaseTypeDeclaration, value); } - } - - public int Space_BeforeComma - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeComma); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeComma, value); } - } - - public int Space_BeforeDot - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeDot); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeDot, value); } - } - - public int Space_BeforeOpenSquare - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeOpenSquareBracket); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeOpenSquareBracket, value); } - } - - public int Space_BeforeSemicolonsInForStatement - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeSemicolonsInForStatement); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeSemicolonsInForStatement, value); } - } - - public int Space_BetweenEmptyMethodCallParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodCallParentheses); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodCallParentheses, value); } - } - - public int Space_BetweenEmptyMethodDeclarationParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodDeclarationParentheses); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodDeclarationParentheses, value); } - } - - public int Space_BetweenEmptySquares - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptySquareBrackets); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptySquareBrackets, value); } - } - - public int Space_InControlFlowConstruct - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterControlFlowStatementKeyword); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterControlFlowStatementKeyword, value); } - } - - public int Space_WithinCastParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinCastParentheses); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinCastParentheses, value); } - } - - public int Space_WithinExpressionParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinExpressionParentheses); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinExpressionParentheses, value); } - } - - public int Space_WithinMethodCallParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodCallParentheses); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodCallParentheses, value); } - } - - public int Space_WithinMethodDeclarationParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodDeclarationParenthesis); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodDeclarationParenthesis, value); } - } - - public int Space_WithinOtherParentheses - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinOtherParentheses); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinOtherParentheses, value); } - } - - public int Space_WithinSquares - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinSquareBrackets); } - set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinSquareBrackets, value); } - } - - public string Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration_CodeStyle - { - get { return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration); } - set { SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, value); } - } - - public string Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess_CodeStyle - { - get { return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); } - set { SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, value); } - } - - public string Style_NamingPreferences - { - get - { - return _workspace.Options.GetOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp).CreateXElement().ToString(); - } - - set - { - try - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp, NamingStylePreferences.FromXElement(XElement.Parse(value))))); - } - catch (Exception) - { - } - } - } - - public string Style_QualifyFieldAccess - { - get { return GetXmlOption(CodeStyleOptions2.QualifyFieldAccess); } - set { SetXmlOption(CodeStyleOptions2.QualifyFieldAccess, value); } - } - - public string Style_QualifyPropertyAccess - { - get { return GetXmlOption(CodeStyleOptions2.QualifyPropertyAccess); } - set { SetXmlOption(CodeStyleOptions2.QualifyPropertyAccess, value); } - } - - public string Style_QualifyMethodAccess - { - get { return GetXmlOption(CodeStyleOptions2.QualifyMethodAccess); } - set { SetXmlOption(CodeStyleOptions2.QualifyMethodAccess, value); } - } - - public string Style_QualifyEventAccess - { - get { return GetXmlOption(CodeStyleOptions2.QualifyEventAccess); } - set { SetXmlOption(CodeStyleOptions2.QualifyEventAccess, value); } - } - - public string Style_PreferThrowExpression - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferThrowExpression); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferThrowExpression, value); } - } - - public string Style_PreferObjectInitializer - { - get { return GetXmlOption(CodeStyleOptions2.PreferObjectInitializer); } - set { SetXmlOption(CodeStyleOptions2.PreferObjectInitializer, value); } - } - - public string Style_PreferCollectionInitializer - { - get { return GetXmlOption(CodeStyleOptions2.PreferCollectionInitializer); } - set { SetXmlOption(CodeStyleOptions2.PreferCollectionInitializer, value); } - } - - public string Style_PreferCoalesceExpression - { - get { return GetXmlOption(CodeStyleOptions2.PreferCoalesceExpression); } - set { SetXmlOption(CodeStyleOptions2.PreferCoalesceExpression, value); } - } - - public string Style_PreferNullPropagation - { - get { return GetXmlOption(CodeStyleOptions2.PreferNullPropagation); } - set { SetXmlOption(CodeStyleOptions2.PreferNullPropagation, value); } - } - - public string Style_PreferInlinedVariableDeclaration - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferInlinedVariableDeclaration); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferInlinedVariableDeclaration, value); } - } - - public string Style_PreferExplicitTupleNames - { - get { return GetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames); } - set { SetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames, value); } - } - - public string Style_PreferInferredTupleNames - { - get { return GetXmlOption(CodeStyleOptions2.PreferInferredTupleNames); } - set { SetXmlOption(CodeStyleOptions2.PreferInferredTupleNames, value); } - } - - public string Style_PreferInferredAnonymousTypeMemberNames - { - get { return GetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames); } - set { SetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames, value); } - } - - [Obsolete("Use Style_UseImplicitTypeWherePossible, Style_UseImplicitTypeWhereApparent or Style_UseImplicitTypeForIntrinsicTypes", error: true)] - public int Style_UseVarWhenDeclaringLocals - { - get { return 0; } - set { } - } - - public string Style_UseImplicitTypeWherePossible - { - get { return GetXmlOption(CSharpCodeStyleOptions.VarElsewhere); } - set { SetXmlOption(CSharpCodeStyleOptions.VarElsewhere, value); } - } - - public string Style_UseImplicitTypeWhereApparent - { - get { return GetXmlOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent); } - set { SetXmlOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent, value); } - } - - public string Style_UseImplicitTypeForIntrinsicTypes - { - get { return GetXmlOption(CSharpCodeStyleOptions.VarForBuiltInTypes); } - set { SetXmlOption(CSharpCodeStyleOptions.VarForBuiltInTypes, value); } - } - - public string Style_PreferConditionalDelegateCall - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall, value); } - } - - public string Style_PreferSwitchExpression - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferSwitchExpression); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferSwitchExpression, value); } - } - - public string Style_PreferPatternMatching - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatching); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferPatternMatching, value); } - } - - public string Style_PreferPatternMatchingOverAsWithNullCheck - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck, value); } - } - - public string Style_PreferPatternMatchingOverIsWithCastCheck - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck, value); } - } - - public string Style_PreferExpressionBodiedConstructors - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, value); } - } - - public string Style_PreferExpressionBodiedMethods - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, value); } - } - - public string Style_PreferExpressionBodiedOperators - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedOperators); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, value); } - } - - public string Style_PreferExpressionBodiedProperties - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, value); } - } - - public string Style_PreferExpressionBodiedIndexers - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, value); } - } - - public string Style_PreferExpressionBodiedAccessors - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, value); } - } - - public string Style_PreferBraces - { - get { return GetXmlOption(CSharpCodeStyleOptions.PreferBraces); } - set { SetXmlOption(CSharpCodeStyleOptions.PreferBraces, value); } - } - - public string Style_PreferReadonly - { - get { return GetXmlOption(CodeStyleOptions2.PreferReadonly); } - set { SetXmlOption(CodeStyleOptions2.PreferReadonly, value); } - } - - public int Wrapping_IgnoreSpacesAroundBinaryOperators - { - get - { - return (int)_workspace.Options.GetOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator); - } - - set - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator, value))); - } - } - - public int Wrapping_IgnoreSpacesAroundVariableDeclaration - { - get { return GetBooleanOption(CSharpFormattingOptions2.SpacesIgnoreAroundVariableDeclaration); } - set { SetBooleanOption(CSharpFormattingOptions2.SpacesIgnoreAroundVariableDeclaration, value); } - } - - public int Wrapping_KeepStatementsOnSingleLine - { - get { return GetBooleanOption(CSharpFormattingOptions2.WrappingKeepStatementsOnSingleLine); } - set { SetBooleanOption(CSharpFormattingOptions2.WrappingKeepStatementsOnSingleLine, value); } - } - - public int Wrapping_PreserveSingleLine - { - get { return GetBooleanOption(CSharpFormattingOptions2.WrappingPreserveSingleLine); } - set { SetBooleanOption(CSharpFormattingOptions2.WrappingPreserveSingleLine, value); } - } - - private int GetBooleanOption(Option2 key) - => _workspace.Options.GetOption(key) ? 1 : 0; - - private int GetBooleanOption(PerLanguageOption2 key) - => _workspace.Options.GetOption(key, LanguageNames.CSharp) ? 1 : 0; - - private T GetOption(PerLanguageOption2 key) - => _workspace.Options.GetOption(key, LanguageNames.CSharp); - - private void SetBooleanOption(Option2 key, int value) - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(key, value != 0))); - } - - private void SetBooleanOption(PerLanguageOption2 key, int value) - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(key, LanguageNames.CSharp, value != 0))); - } - - private void SetOption(PerLanguageOption2 key, T value) - { - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(key, LanguageNames.CSharp, value))); - } - - private int GetBooleanOption(PerLanguageOption2 key) - { - var option = _workspace.Options.GetOption(key, LanguageNames.CSharp); - if (!option.HasValue) - { - return -1; - } - - return option.Value ? 1 : 0; - } - - private string GetXmlOption(Option2> option) - => _workspace.Options.GetOption(option).ToXElement().ToString(); - - private void SetBooleanOption(PerLanguageOption2 key, int value) - { - var boolValue = (value < 0) ? (bool?)null : (value > 0); - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(key, LanguageNames.CSharp, boolValue))); - } - - private string GetXmlOption(PerLanguageOption2> option) - => _workspace.Options.GetOption(option, LanguageNames.CSharp).ToXElement().ToString(); - - private void SetXmlOption(Option2> option, string value) - { - var convertedValue = CodeStyleOption2.FromXElement(XElement.Parse(value)); - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(option, convertedValue))); - } - - private void SetXmlOption(PerLanguageOption2> option, string value) - { - var convertedValue = CodeStyleOption2.FromXElement(XElement.Parse(value)); - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(option, LanguageNames.CSharp, convertedValue))); - } - } -} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.BraceCompletion.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.BraceCompletion.cs new file mode 100644 index 0000000000000..e9f83e5bcb7d6 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.BraceCompletion.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.BraceCompletion; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int Formatting_TriggerOnBlockCompletion + { + get { return GetBooleanOption(BraceCompletionOptions.AutoFormattingOnCloseBrace); } + set { SetBooleanOption(BraceCompletionOptions.AutoFormattingOnCloseBrace, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Completion.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Completion.cs new file mode 100644 index 0000000000000..dfa9ff94f3ea8 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Completion.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Completion; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int BringUpOnIdentifier + { + get { return GetBooleanOption(CompletionOptions.TriggerOnTypingLetters2); } + set { SetBooleanOption(CompletionOptions.TriggerOnTypingLetters2, value); } + } + + public int HighlightMatchingPortionsOfCompletionListItems + { + get { return GetBooleanOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems); } + set { SetBooleanOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems, value); } + } + + public int ShowCompletionItemFilters + { + get { return GetBooleanOption(CompletionOptions.ShowCompletionItemFilters); } + set { SetBooleanOption(CompletionOptions.ShowCompletionItemFilters, value); } + } + + public int ShowItemsFromUnimportedNamespaces + { + get { return GetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces); } + set { SetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, value); } + } + + public int InsertNewlineOnEnterWithWholeWord + { + get { return (int)GetOption(CompletionOptions.EnterKeyBehavior); } + set { SetOption(CompletionOptions.EnterKeyBehavior, (EnterKeyRule)value); } + } + + public int EnterKeyBehavior + { + get { return (int)GetOption(CompletionOptions.EnterKeyBehavior); } + set { SetOption(CompletionOptions.EnterKeyBehavior, (EnterKeyRule)value); } + } + + public int SnippetsBehavior + { + get { return (int)GetOption(CompletionOptions.SnippetsBehavior); } + set { SetOption(CompletionOptions.SnippetsBehavior, (SnippetsRule)value); } + } + + public int TriggerInArgumentLists + { + get { return GetBooleanOption(CompletionOptions.TriggerInArgumentLists); } + set { SetBooleanOption(CompletionOptions.TriggerInArgumentLists, value); } + } + + public int EnableArgumentCompletionSnippets + { + get { return GetBooleanOption(CompletionOptions.EnableArgumentCompletionSnippets); } + set { SetBooleanOption(CompletionOptions.EnableArgumentCompletionSnippets, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.DocumentationComment.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.DocumentationComment.cs new file mode 100644 index 0000000000000..eba7b134e76c2 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.DocumentationComment.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.DocumentationComments; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int AutoComment + { + get { return GetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration); } + set { SetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.ExtractMethod.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.ExtractMethod.cs new file mode 100644 index 0000000000000..05f3658e2a39d --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.ExtractMethod.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.ExtractMethod; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int ExtractMethod_AllowBestEffort + { + get { return GetBooleanOption(ExtractMethodOptions.AllowBestEffort); } + set { SetBooleanOption(ExtractMethodOptions.AllowBestEffort, value); } + } + + public int ExtractMethod_DoNotPutOutOrRefOnStruct + { + get { return GetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct); } + set { SetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Formatting.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Formatting.cs new file mode 100644 index 0000000000000..61f1062754499 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Formatting.cs @@ -0,0 +1,324 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Formatting; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int Indent_BlockContents + { + get { return GetBooleanOption(CSharpFormattingOptions2.IndentBlock); } + set { SetBooleanOption(CSharpFormattingOptions2.IndentBlock, value); } + } + + public int Indent_Braces + { + get { return GetBooleanOption(CSharpFormattingOptions2.IndentBraces); } + set { SetBooleanOption(CSharpFormattingOptions2.IndentBraces, value); } + } + + public int Indent_CaseContents + { + get { return GetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSection); } + set { SetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSection, value); } + } + + public int Indent_CaseContentsWhenBlock + { + get { return GetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSectionWhenBlock); } + set { SetBooleanOption(CSharpFormattingOptions2.IndentSwitchCaseSectionWhenBlock, value); } + } + + public int Indent_CaseLabels + { + get { return GetBooleanOption(CSharpFormattingOptions2.IndentSwitchSection); } + set { SetBooleanOption(CSharpFormattingOptions2.IndentSwitchSection, value); } + } + + public int Indent_FlushLabelsLeft + { + get { return GetOption(CSharpFormattingOptions2.LabelPositioning) == LabelPositionOptions.LeftMost ? 1 : 0; } + set { SetOption(CSharpFormattingOptions2.LabelPositioning, value == 1 ? LabelPositionOptions.LeftMost : LabelPositionOptions.NoIndent); } + } + + public int Indent_UnindentLabels + { + get { return (int)GetOption(CSharpFormattingOptions2.LabelPositioning); } + set { SetOption(CSharpFormattingOptions2.LabelPositioning, (LabelPositionOptions)value); } + } + + public int NewLines_AnonymousTypeInitializer_EachMember + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInAnonymousTypes); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInAnonymousTypes, value); } + } + + public int NewLines_Braces_AnonymousMethod + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousMethods); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousMethods, value); } + } + + public int NewLines_Braces_AnonymousTypeInitializer + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousTypes); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAnonymousTypes, value); } + } + + public int NewLines_Braces_ControlFlow + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInControlBlocks); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInControlBlocks, value); } + } + + public int NewLines_Braces_LambdaExpressionBody + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInLambdaExpressionBody); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInLambdaExpressionBody, value); } + } + + public int NewLines_Braces_Method + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInMethods); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInMethods, value); } + } + + public int NewLines_Braces_Property + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInProperties); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInProperties, value); } + } + + public int NewLines_Braces_Accessor + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAccessors); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInAccessors, value); } + } + + public int NewLines_Braces_ObjectInitializer + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInObjectCollectionArrayInitializers, value); } + } + + public int NewLines_Braces_Type + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInTypes); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLinesForBracesInTypes, value); } + } + + public int NewLines_Keywords_Catch + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForCatch); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLineForCatch, value); } + } + + public int NewLines_Keywords_Else + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForElse); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLineForElse, value); } + } + + public int NewLines_Keywords_Finally + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForFinally); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLineForFinally, value); } + } + + public int NewLines_ObjectInitializer_EachMember + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInObjectInit); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLineForMembersInObjectInit, value); } + } + + public int NewLines_QueryExpression_EachClause + { + get { return GetBooleanOption(CSharpFormattingOptions2.NewLineForClausesInQuery); } + set { SetBooleanOption(CSharpFormattingOptions2.NewLineForClausesInQuery, value); } + } + + public int Space_AfterBasesColon + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterColonInBaseTypeDeclaration); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterColonInBaseTypeDeclaration, value); } + } + + public int Space_AfterCast + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterCast); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterCast, value); } + } + + public int Space_AfterComma + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterComma); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterComma, value); } + } + + public int Space_AfterDot + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterDot); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterDot, value); } + } + + public int Space_AfterMethodCallName + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterMethodCallName); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterMethodCallName, value); } + } + + public int Space_AfterMethodDeclarationName + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpacingAfterMethodDeclarationName); } + set { SetBooleanOption(CSharpFormattingOptions2.SpacingAfterMethodDeclarationName, value); } + } + + public int Space_AfterSemicolonsInForStatement + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterSemicolonsInForStatement); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterSemicolonsInForStatement, value); } + } + + public int Space_AroundBinaryOperator + { + get { return GetOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator) == BinaryOperatorSpacingOptions.Single ? 1 : 0; } + set { SetOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator, value == 1 ? BinaryOperatorSpacingOptions.Single : BinaryOperatorSpacingOptions.Ignore); } + } + + public int Space_BeforeBasesColon + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeColonInBaseTypeDeclaration); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeColonInBaseTypeDeclaration, value); } + } + + public int Space_BeforeComma + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeComma); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeComma, value); } + } + + public int Space_BeforeDot + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeDot); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeDot, value); } + } + + public int Space_BeforeOpenSquare + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeOpenSquareBracket); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeOpenSquareBracket, value); } + } + + public int Space_BeforeSemicolonsInForStatement + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBeforeSemicolonsInForStatement); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBeforeSemicolonsInForStatement, value); } + } + + public int Space_BetweenEmptyMethodCallParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodCallParentheses); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodCallParentheses, value); } + } + + public int Space_BetweenEmptyMethodDeclarationParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodDeclarationParentheses); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptyMethodDeclarationParentheses, value); } + } + + public int Space_BetweenEmptySquares + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptySquareBrackets); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceBetweenEmptySquareBrackets, value); } + } + + public int Space_InControlFlowConstruct + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceAfterControlFlowStatementKeyword); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceAfterControlFlowStatementKeyword, value); } + } + + public int Space_WithinCastParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinCastParentheses); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinCastParentheses, value); } + } + + public int Space_WithinExpressionParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinExpressionParentheses); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinExpressionParentheses, value); } + } + + public int Space_WithinMethodCallParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodCallParentheses); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodCallParentheses, value); } + } + + public int Space_WithinMethodDeclarationParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodDeclarationParenthesis); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinMethodDeclarationParenthesis, value); } + } + + public int Space_WithinOtherParentheses + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinOtherParentheses); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinOtherParentheses, value); } + } + + public int Space_WithinSquares + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpaceWithinSquareBrackets); } + set { SetBooleanOption(CSharpFormattingOptions2.SpaceWithinSquareBrackets, value); } + } + + public int Wrapping_IgnoreSpacesAroundBinaryOperators + { + get { return (int)GetOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator); } + set { SetOption(CSharpFormattingOptions2.SpacingAroundBinaryOperator, (BinaryOperatorSpacingOptions)value); } + } + + public int Wrapping_IgnoreSpacesAroundVariableDeclaration + { + get { return GetBooleanOption(CSharpFormattingOptions2.SpacesIgnoreAroundVariableDeclaration); } + set { SetBooleanOption(CSharpFormattingOptions2.SpacesIgnoreAroundVariableDeclaration, value); } + } + + public int Wrapping_KeepStatementsOnSingleLine + { + get { return GetBooleanOption(CSharpFormattingOptions2.WrappingKeepStatementsOnSingleLine); } + set { SetBooleanOption(CSharpFormattingOptions2.WrappingKeepStatementsOnSingleLine, value); } + } + + public int Wrapping_PreserveSingleLine + { + get { return GetBooleanOption(CSharpFormattingOptions2.WrappingPreserveSingleLine); } + set { SetBooleanOption(CSharpFormattingOptions2.WrappingPreserveSingleLine, value); } + } + + public int Formatting_TriggerOnPaste + { + get { return GetBooleanOption(FormattingOptions2.FormatOnPaste); } + set { SetBooleanOption(FormattingOptions2.FormatOnPaste, value); } + } + + public int Formatting_TriggerOnStatementCompletion + { + get { return GetBooleanOption(FormattingOptions2.AutoFormattingOnSemicolon); } + set { SetBooleanOption(FormattingOptions2.AutoFormattingOnSemicolon, value); } + } + + public int AutoFormattingOnTyping + { + get { return GetBooleanOption(FormattingOptions2.AutoFormattingOnTyping); } + set { SetBooleanOption(FormattingOptions2.AutoFormattingOnTyping, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Generation.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Generation.cs new file mode 100644 index 0000000000000..44ec8542d1f00 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Generation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Editing; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int SortUsings_SeparateImportDirectiveGroups + { + get { return GetBooleanOption(GenerationOptions.SeparateImportDirectiveGroups); } + set { SetBooleanOption(GenerationOptions.SeparateImportDirectiveGroups, value); } + } + + public int SortUsings_PlaceSystemFirst + { + get { return GetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst); } + set { SetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Naming.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Naming.cs new file mode 100644 index 0000000000000..2a46c91f0952a --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Naming.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.Simplification; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public string Style_NamingPreferences + { + get { return GetOption(NamingStyleOptions.NamingPreferences).CreateXElement().ToString(); } + set + { + try + { + SetOption(NamingStyleOptions.NamingPreferences, NamingStylePreferences.FromXElement(XElement.Parse(value))); + } + catch (Exception) + { + } + } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.ObsoleteAndUnused.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.ObsoleteAndUnused.cs new file mode 100644 index 0000000000000..a508bc0006be6 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.ObsoleteAndUnused.cs @@ -0,0 +1,74 @@ +// 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.Completion; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + /// + /// Unused. But kept around for back compat. Note this option is not about + /// turning warning into errors. It's about an aspect of 'remove unused using' + /// functionality we don't support anymore. Namely whether or not 'remove unused + /// using' should warn if you have any build errors as that might mean we + /// remove some usings inappropriately. + /// + public int WarnOnBuildErrors + { + get { return 0; } + set { } + } + + public int ShowKeywords + { + get { return 0; } + set { } + } + + [Obsolete("ClosedFileDiagnostics has been deprecated")] + public int ClosedFileDiagnostics + { + get { return 0; } + set { } + } + + [Obsolete("CSharpClosedFileDiagnostics has been deprecated")] + public int CSharpClosedFileDiagnostics + { + get { return 0; } + set { } + } + + [Obsolete("Use SnippetsBehavior instead")] + public int ShowSnippets + { + get + { + return GetOption(CompletionOptions.SnippetsBehavior) == SnippetsRule.AlwaysInclude + ? 1 : 0; + } + + set + { + if (value == 0) + { + SetOption(CompletionOptions.SnippetsBehavior, SnippetsRule.NeverInclude); + } + else + { + SetOption(CompletionOptions.SnippetsBehavior, SnippetsRule.AlwaysInclude); + } + } + } + + [Obsolete("Use Style_UseImplicitTypeWherePossible, Style_UseImplicitTypeWhereApparent or Style_UseImplicitTypeForIntrinsicTypes", error: true)] + public int Style_UseVarWhenDeclaringLocals + { + get { return 0; } + set { } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs new file mode 100644 index 0000000000000..efa1016d1bb16 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.OnOff.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Editor.Shared.Options; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int AutoInsertAsteriskForNewLinesOfBlockComments + { + get { return GetBooleanOption(FeatureOnOffOptions.AutoInsertBlockCommentStartString); } + set { SetBooleanOption(FeatureOnOffOptions.AutoInsertBlockCommentStartString, value); } + } + + public int DisplayLineSeparators + { + get { return GetBooleanOption(FeatureOnOffOptions.LineSeparator); } + set { SetBooleanOption(FeatureOnOffOptions.LineSeparator, value); } + } + + public int EnableHighlightRelatedKeywords + { + get { return GetBooleanOption(FeatureOnOffOptions.KeywordHighlighting); } + set { SetBooleanOption(FeatureOnOffOptions.KeywordHighlighting, value); } + } + + public int EnterOutliningModeOnOpen + { + get { return GetBooleanOption(FeatureOnOffOptions.Outlining); } + set { SetBooleanOption(FeatureOnOffOptions.Outlining, value); } + } + + public int HighlightReferences + { + get { return GetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting); } + set { SetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting, value); } + } + + public int Refactoring_Verification_Enabled + { + get { return GetBooleanOption(FeatureOnOffOptions.RefactoringVerification); } + set { SetBooleanOption(FeatureOnOffOptions.RefactoringVerification, value); } + } + + public int RenameSmartTagEnabled + { + get { return GetBooleanOption(FeatureOnOffOptions.RenameTracking); } + set { SetBooleanOption(FeatureOnOffOptions.RenameTracking, value); } + } + + public int RenameTrackingPreview + { + get { return GetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview); } + set { SetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview, value); } + } + + public int NavigateToDecompiledSources + { + get { return GetBooleanOption(FeatureOnOffOptions.NavigateToDecompiledSources); } + set { SetBooleanOption(FeatureOnOffOptions.NavigateToDecompiledSources, value); } + } + + public int UseEnhancedColors + { + get { return GetOption(FeatureOnOffOptions.UseEnhancedColors); } + set { SetOption(FeatureOnOffOptions.UseEnhancedColors, value); } + } + + public int AddImportsOnPaste + { + get { return GetBooleanOption(FeatureOnOffOptions.AddImportsOnPaste); } + set { SetBooleanOption(FeatureOnOffOptions.AddImportsOnPaste, value); } + } + + public int OfferRemoveUnusedReferences + { + get { return GetBooleanOption(FeatureOnOffOptions.OfferRemoveUnusedReferences); } + set { SetBooleanOption(FeatureOnOffOptions.OfferRemoveUnusedReferences, value); } + } + + public int AutomaticallyCompleteStatementOnSemicolon + { + get { return GetBooleanOption(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon); } + set { SetBooleanOption(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs new file mode 100644 index 0000000000000..cb3fd043aa11c --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs @@ -0,0 +1,396 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public string Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration_CodeStyle + { + get { return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration); } + set { SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, value); } + } + + public string Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess_CodeStyle + { + get { return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); } + set { SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, value); } + } + + public string Style_QualifyFieldAccess + { + get { return GetXmlOption(CodeStyleOptions2.QualifyFieldAccess); } + set { SetXmlOption(CodeStyleOptions2.QualifyFieldAccess, value); } + } + + public string Style_QualifyPropertyAccess + { + get { return GetXmlOption(CodeStyleOptions2.QualifyPropertyAccess); } + set { SetXmlOption(CodeStyleOptions2.QualifyPropertyAccess, value); } + } + + public string Style_QualifyMethodAccess + { + get { return GetXmlOption(CodeStyleOptions2.QualifyMethodAccess); } + set { SetXmlOption(CodeStyleOptions2.QualifyMethodAccess, value); } + } + + public string Style_QualifyEventAccess + { + get { return GetXmlOption(CodeStyleOptions2.QualifyEventAccess); } + set { SetXmlOption(CodeStyleOptions2.QualifyEventAccess, value); } + } + + public string Style_PreferThrowExpression + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferThrowExpression); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferThrowExpression, value); } + } + + public string Style_PreferObjectInitializer + { + get { return GetXmlOption(CodeStyleOptions2.PreferObjectInitializer); } + set { SetXmlOption(CodeStyleOptions2.PreferObjectInitializer, value); } + } + + public string Style_PreferCollectionInitializer + { + get { return GetXmlOption(CodeStyleOptions2.PreferCollectionInitializer); } + set { SetXmlOption(CodeStyleOptions2.PreferCollectionInitializer, value); } + } + + public string Style_PreferCoalesceExpression + { + get { return GetXmlOption(CodeStyleOptions2.PreferCoalesceExpression); } + set { SetXmlOption(CodeStyleOptions2.PreferCoalesceExpression, value); } + } + + public string Style_PreferNullPropagation + { + get { return GetXmlOption(CodeStyleOptions2.PreferNullPropagation); } + set { SetXmlOption(CodeStyleOptions2.PreferNullPropagation, value); } + } + + public string Style_PreferInlinedVariableDeclaration + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferInlinedVariableDeclaration); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferInlinedVariableDeclaration, value); } + } + + public string Style_PreferExplicitTupleNames + { + get { return GetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames); } + set { SetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames, value); } + } + + public string Style_PreferInferredTupleNames + { + get { return GetXmlOption(CodeStyleOptions2.PreferInferredTupleNames); } + set { SetXmlOption(CodeStyleOptions2.PreferInferredTupleNames, value); } + } + + public string Style_PreferInferredAnonymousTypeMemberNames + { + get { return GetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames); } + set { SetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames, value); } + } + + public string Style_UseImplicitTypeWherePossible + { + get { return GetXmlOption(CSharpCodeStyleOptions.VarElsewhere); } + set { SetXmlOption(CSharpCodeStyleOptions.VarElsewhere, value); } + } + + public string Style_UseImplicitTypeWhereApparent + { + get { return GetXmlOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent); } + set { SetXmlOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent, value); } + } + + public string Style_UseImplicitTypeForIntrinsicTypes + { + get { return GetXmlOption(CSharpCodeStyleOptions.VarForBuiltInTypes); } + set { SetXmlOption(CSharpCodeStyleOptions.VarForBuiltInTypes, value); } + } + + public string Style_PreferConditionalDelegateCall + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall, value); } + } + + public string Style_PreferSwitchExpression + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferSwitchExpression); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferSwitchExpression, value); } + } + + public string Style_PreferPatternMatching + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatching); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferPatternMatching, value); } + } + + public string Style_PreferPatternMatchingOverAsWithNullCheck + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck, value); } + } + + public string Style_PreferPatternMatchingOverIsWithCastCheck + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck, value); } + } + + public string Style_PreferExpressionBodiedConstructors + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, value); } + } + + public string Style_PreferExpressionBodiedMethods + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, value); } + } + + public string Style_PreferExpressionBodiedOperators + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedOperators); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedOperators, value); } + } + + public string Style_PreferExpressionBodiedProperties + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, value); } + } + + public string Style_PreferExpressionBodiedIndexers + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers, value); } + } + + public string Style_PreferExpressionBodiedAccessors + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, value); } + } + + public string Style_PreferExpressionBodiedLambdas + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, value); } + } + + public string Style_PreferExpressionBodiedLocalFunctions + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedLocalFunctions); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferExpressionBodiedLocalFunctions, value); } + } + + public string Style_PreferBraces + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferBraces); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferBraces, value); } + } + + public string Style_PreferReadonly + { + get { return GetXmlOption(CodeStyleOptions2.PreferReadonly); } + set { SetXmlOption(CodeStyleOptions2.PreferReadonly, value); } + } + + public int Style_PreferObjectInitializer_FadeOutCode + { + get { return GetBooleanOption(CodeStyleOptions2.PreferObjectInitializer_FadeOutCode); } + set { SetBooleanOption(CodeStyleOptions2.PreferObjectInitializer_FadeOutCode, value); } + } + + public int Style_PreferCollectionInitializer_FadeOutCode + { + get { return GetBooleanOption(CodeStyleOptions2.PreferCollectionInitializer_FadeOutCode); } + set { SetBooleanOption(CodeStyleOptions2.PreferCollectionInitializer_FadeOutCode, value); } + } + + public string Style_PreferSimplifiedBooleanExpressions + { + get { return GetXmlOption(CodeStyleOptions2.PreferSimplifiedBooleanExpressions); } + set { SetXmlOption(CodeStyleOptions2.PreferSimplifiedBooleanExpressions, value); } + } + + public string Style_PreferAutoProperties + { + get { return GetXmlOption(CodeStyleOptions2.PreferAutoProperties); } + set { SetXmlOption(CodeStyleOptions2.PreferAutoProperties, value); } + } + + public string Style_PreferIsNullCheckOverReferenceEqualityMethod + { + get { return GetXmlOption(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod); } + set { SetXmlOption(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, value); } + } + + public string Style_PreferConditionalExpressionOverAssignment + { + get { return GetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverAssignment); } + set { SetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverAssignment, value); } + } + + public string Style_PreferConditionalExpressionOverReturn + { + get { return GetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverReturn); } + set { SetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverReturn, value); } + } + + public string Style_PreferCompoundAssignment + { + get { return GetXmlOption(CodeStyleOptions2.PreferCompoundAssignment); } + set { SetXmlOption(CodeStyleOptions2.PreferCompoundAssignment, value); } + } + + public string Style_PreferSimplifiedInterpolation + { + get { return GetXmlOption(CodeStyleOptions2.PreferSimplifiedInterpolation); } + set { SetXmlOption(CodeStyleOptions2.PreferSimplifiedInterpolation, value); } + } + + public string Style_RequireAccessibilityModifiers + { + get { return GetXmlOption(CodeStyleOptions2.RequireAccessibilityModifiers); } + set { SetXmlOption(CodeStyleOptions2.RequireAccessibilityModifiers, value); } + } + + public string Style_RemoveUnnecessarySuppressionExclusions + { + get { return GetOption(CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions); } + set { SetOption(CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions, value); } + } + + public string Style_PreferSystemHashCode + { + get { return GetXmlOption(CodeStyleOptions2.PreferSystemHashCode); } + set { SetXmlOption(CodeStyleOptions2.PreferSystemHashCode, value); } + } + + public string Style_PreferNamespaceAndFolderMatchStructure + { + get { return GetXmlOption(CodeStyleOptions2.PreferNamespaceAndFolderMatchStructure); } + set { SetXmlOption(CodeStyleOptions2.PreferNamespaceAndFolderMatchStructure, value); } + } + + public string Style_AllowMultipleBlankLines + { + get { return GetXmlOption(CodeStyleOptions2.AllowMultipleBlankLines); } + set { SetXmlOption(CodeStyleOptions2.AllowMultipleBlankLines, value); } + } + + public string Style_AllowStatementImmediatelyAfterBlock + { + get { return GetXmlOption(CodeStyleOptions2.AllowStatementImmediatelyAfterBlock); } + set { SetXmlOption(CodeStyleOptions2.AllowStatementImmediatelyAfterBlock, value); } + } + + public string Style_PreferNotPattern + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferNotPattern); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferNotPattern, value); } + } + + public string Style_PreferDeconstructedVariableDeclaration + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferDeconstructedVariableDeclaration); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferDeconstructedVariableDeclaration, value); } + } + + public string Style_PreferIndexOperator + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferIndexOperator); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferIndexOperator, value); } + } + + public string Style_PreferRangeOperator + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferRangeOperator); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferRangeOperator, value); } + } + + public string Style_PreferSimpleDefaultExpression + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferSimpleDefaultExpression); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferSimpleDefaultExpression, value); } + } + + public string Style_PreferredModifierOrder + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferredModifierOrder); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferredModifierOrder, value); } + } + + public string Style_PreferStaticLocalFunction + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferStaticLocalFunction); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferStaticLocalFunction, value); } + } + + public string Style_PreferSimpleUsingStatement + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferSimpleUsingStatement); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferSimpleUsingStatement, value); } + } + + public string Style_PreferLocalOverAnonymousFunction + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction, value); } + } + + public string Style_PreferredUsingDirectivePlacement + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, value); } + } + + public string Style_UnusedValueExpressionStatement + { + get { return GetXmlOption(CSharpCodeStyleOptions.UnusedValueExpressionStatement); } + set { SetXmlOption(CSharpCodeStyleOptions.UnusedValueExpressionStatement, value); } + } + + public string Style_UnusedValueAssignment + { + get { return GetXmlOption(CSharpCodeStyleOptions.UnusedValueAssignment); } + set { SetXmlOption(CSharpCodeStyleOptions.UnusedValueAssignment, value); } + } + + public string Style_ImplicitObjectCreationWhenTypeIsApparent + { + get { return GetXmlOption(CSharpCodeStyleOptions.ImplicitObjectCreationWhenTypeIsApparent); } + set { SetXmlOption(CSharpCodeStyleOptions.ImplicitObjectCreationWhenTypeIsApparent, value); } + } + + public string Style_AllowEmbeddedStatementsOnSameLine + { + get { return GetXmlOption(CSharpCodeStyleOptions.AllowEmbeddedStatementsOnSameLine); } + set { SetXmlOption(CSharpCodeStyleOptions.AllowEmbeddedStatementsOnSameLine, value); } + } + + public string Style_AllowBlankLinesBetweenConsecutiveBraces + { + get { return GetXmlOption(CSharpCodeStyleOptions.AllowBlankLinesBetweenConsecutiveBraces); } + set { SetXmlOption(CSharpCodeStyleOptions.AllowBlankLinesBetweenConsecutiveBraces, value); } + } + + public string Style_AllowBlankLineAfterColonInConstructorInitializer + { + get { return GetXmlOption(CSharpCodeStyleOptions.AllowBlankLineAfterColonInConstructorInitializer); } + set { SetXmlOption(CSharpCodeStyleOptions.AllowBlankLineAfterColonInConstructorInitializer, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.SymbolSearch.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.SymbolSearch.cs new file mode 100644 index 0000000000000..bce969be1174f --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.SymbolSearch.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.SymbolSearch; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + public partial class AutomationObject + { + public int AddImport_SuggestForTypesInReferenceAssemblies + { + get { return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies); } + set { SetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, value); } + } + + public int AddImport_SuggestForTypesInNuGetPackages + { + get { return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages); } + set { SetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages, value); } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.cs new file mode 100644 index 0000000000000..c966bf97d6a83 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices.Implementation.Options; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options +{ + [ComVisible(true)] + public partial class AutomationObject : AbstractAutomationObject + { + internal AutomationObject(Workspace workspace) : base(workspace, LanguageNames.CSharp) + { + } + + private int GetBooleanOption(Option2 key) + => GetOption(key) ? 1 : 0; + + private int GetBooleanOption(PerLanguageOption2 key) + => GetOption(key) ? 1 : 0; + + private void SetBooleanOption(Option2 key, int value) + => SetOption(key, value != 0); + + private void SetBooleanOption(PerLanguageOption2 key, int value) + => SetOption(key, value != 0); + } +} diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/FormattingOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/FormattingOptionPageControl.xaml.cs index 9a32223b849d2..b595de2c15574 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/FormattingOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/FormattingOptionPageControl.xaml.cs @@ -6,7 +6,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.BraceCompletion; -using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Formatting; using Microsoft.VisualStudio.LanguageServices.Implementation.Options; using System.Runtime.CompilerServices; @@ -34,11 +33,11 @@ public FormattingOptionPageControl(OptionStore optionStore) : base(optionStore) FormatOnReturnCheckBox.Content = CSharpVSResources.Automatically_format_on_return; FormatOnPasteCheckBox.Content = CSharpVSResources.Automatically_format_on_paste; - BindToOption(FormatWhenTypingCheckBox, FeatureOnOffOptions.AutoFormattingOnTyping, LanguageNames.CSharp); + BindToOption(FormatWhenTypingCheckBox, FormattingOptions2.AutoFormattingOnTyping, LanguageNames.CSharp); BindToOption(FormatOnCloseBraceCheckBox, BraceCompletionOptions.AutoFormattingOnCloseBrace, LanguageNames.CSharp); - BindToOption(FormatOnSemicolonCheckBox, FeatureOnOffOptions.AutoFormattingOnSemicolon, LanguageNames.CSharp); + BindToOption(FormatOnSemicolonCheckBox, FormattingOptions2.AutoFormattingOnSemicolon, LanguageNames.CSharp); BindToOption(FormatOnReturnCheckBox, FormattingOptions2.AutoFormattingOnReturn, LanguageNames.CSharp); - BindToOption(FormatOnPasteCheckBox, FeatureOnOffOptions.FormatOnPaste, LanguageNames.CSharp); + BindToOption(FormatOnPasteCheckBox, FormattingOptions2.FormatOnPaste, LanguageNames.CSharp); } } } diff --git a/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs b/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs index 563dbc5fc85dd..3311697417755 100644 --- a/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs +++ b/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs @@ -68,6 +68,7 @@ public IEnumerable GetTopLevelNodesFromDocument(SyntaxNode root, Can { if (node.Kind() == SyntaxKind.ClassDeclaration || node.Kind() == SyntaxKind.RecordDeclaration || + node.Kind() == SyntaxKind.RecordStructDeclaration || node.Kind() == SyntaxKind.DelegateDeclaration || node.Kind() == SyntaxKind.EnumDeclaration || node.Kind() == SyntaxKind.InterfaceDeclaration || diff --git a/src/VisualStudio/CSharp/Impl/VSPackage.resx b/src/VisualStudio/CSharp/Impl/VSPackage.resx index 271c9c15fc5fd..2177228dbf5fb 100644 --- a/src/VisualStudio/CSharp/Impl/VSPackage.resx +++ b/src/VisualStudio/CSharp/Impl/VSPackage.resx @@ -142,7 +142,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf index 5088dcf3c3f9f..0040a6a73293d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Povolit prázdný řádek za dvojtečkou v inicializátoru konstruktoru Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Povolit prázdné řádky mezi po sobě jdoucími závorkami Allow embedded statements on same line - Allow embedded statements on same line + Povolit vložené příkazy na stejném řádku @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Automaticky dokončit příkaz středníkem Automatically show completion list in argument lists - Automaticky zobrazovat seznam dokončení v seznamech argumentů (experimentální) + Automaticky zobrazovat seznam dokončení v seznamech argumentů @@ -54,7 +54,7 @@ Completion - Completion + Dokončení @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Pro objekty, kolekce, pole a inicializátory with umístit levou složenou závorku na nový řádek {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf index b490eb348c717..09219c0444092 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Leere Zeile nach Doppelpunkt in Konstruktorinitialisierer zulassen Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Leere Zeilen zwischen aufeinanderfolgenden geschweiften Klammern zulassen Allow embedded statements on same line - Allow embedded statements on same line + Eingebettete Anweisungen in derselben Zeile zulassen @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Anweisung bei Semikolon automatisch abschließen Automatically show completion list in argument lists - Vervollständigungsliste in Argumentlisten automatisch anzeigen (experimentell) + Vervollständigungsliste in Argumentlisten automatisch anzeigen @@ -54,7 +54,7 @@ Completion - Completion + Abschluss @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Öffnende geschweifte Klammer für Objekt-, Sammlungs-, Array- und with-Initialisierer in neue Zeile einfügen {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf index 6f4e5b3e28991..26e4894cb76cd 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Permitir una línea en blanco después de dos puntos en el inicializador del constructor Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Permitir líneas en blanco entre llaves consecutivas Allow embedded statements on same line - Allow embedded statements on same line + Permitir instrucciones incrustadas en la misma línea @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Completar automáticamente la instrucción al introducir punto y coma Automatically show completion list in argument lists - Mostrar automáticamente la lista de finalización en las listas de argumentos (experimental) + Mostrar automáticamente la lista de finalización en las listas de argumentos @@ -54,7 +54,7 @@ Completion - Completion + Finalización @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Colocar llave de apertura en la nueva línea para los inicializadores de objeto, colección, matriz y with {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf index 23b773ec1e4c2..5b98d2c024953 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Autoriser une ligne vide après le signe des deux-points dans l'initialiseur du constructeur Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Autoriser les lignes vides entre les accolades consécutives Allow embedded statements on same line - Allow embedded statements on same line + Autoriser les instructions imbriquées sur la même ligne @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Effectuer la complétion automatique de l'instruction après l'entrée d'un point-virgule Automatically show completion list in argument lists - Afficher automatiquement la liste de complétion dans les listes d'arguments (expérimental) + Afficher automatiquement la liste de complétion dans les listes d'arguments @@ -54,7 +54,7 @@ Completion - Completion + Complétion @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Placer une accolade ouvrante sur une nouvelle ligne pour les initialiseurs d'objets, de collections, de tableaux et with {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf index 565c3ac635c6c..f7c2cad124934 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Consenti riga vuota dopo i due punti nell'inizializzatore del costruttore Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Consenti righe vuote tra parentesi graffe consecutive Allow embedded statements on same line - Allow embedded statements on same line + Consenti istruzioni incorporate sulla stessa riga @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Completa automaticamente istruzione dopo punto e virgola Automatically show completion list in argument lists - Mostra automaticamente l'elenco di completamento negli elenchi di argomenti (sperimentale) + Mostra automaticamente l'elenco di completamento negli elenchi di argomenti @@ -54,7 +54,7 @@ Completion - Completion + Completamento @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Inserisci parentesi graffa di apertura in una nuova riga per oggetto, raccolta, matrice e inizializzatori with {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf index 3d6d7ce291af2..f3ef968687913 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + コンストラクター初期化子のコロンの後に空白行を許可する Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + 連続する中かっこの間に空白行を許可する Allow embedded statements on same line - Allow embedded statements on same line + 同一の行に複数の埋め込みステートメントを許可する @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + セミコロンでステートメントをオートコンプリートに設定する Automatically show completion list in argument lists - 入力候補一覧を引数リストに自動的に表示する (試験段階) + 引数リストに入力候補一覧を自動的に表示する @@ -54,7 +54,7 @@ Completion - Completion + 入力候補 @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 新しい行にオブジェクト、コレクション、配列、with 初期化子用の始めかっこを配置する {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf index cdd947cd9d038..e87fdf18182d5 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + 생성자 이니셜라이저의 콜론 뒤에 빈 줄 허용 Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + 연속 중괄호 사이에 빈 줄 허용 Allow embedded statements on same line - Allow embedded statements on same line + 같은 줄에 포함 문 허용 @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + 세미콜론 입력 시 자동으로 문 완성 Automatically show completion list in argument lists - 인수 목록에 자동으로 완성 목록 표시(실험적) + 인수 목록에 자동으로 완성 목록 표시 @@ -54,7 +54,7 @@ Completion - Completion + 완성 @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 개체, 컬렉션, 배열 및 with 이니셜라이저의 여는 중괄호를 새 줄에 배치 {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf index 6729fd0bb7421..0899eae4a2035 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Zezwalaj na pusty wiersz po dwukropku w inicjatorze konstruktora Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Zezwalaj na puste wiersze między kolejnymi nawiasami klamrowymi Allow embedded statements on same line - Allow embedded statements on same line + Zezwalaj na osadzone instrukcje w tym samym wierszu @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Automatyczne uzupełniaj instrukcje po naciśnięciu średnika Automatically show completion list in argument lists - Automatycznie pokaż listę uzupełniania na listach argumentów (eksperymentalne) + Automatycznie pokaż listę uzupełniania na listach argumentów @@ -54,7 +54,7 @@ Completion - Completion + Uzupełnianie @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Umieść otwierający nawias klamrowy w nowym wierszu dla inicjatorów obiektów, kolekcji, tablic i with {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf index 0434ca1ab545e..43527d65a3b0d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Permitir uma linha em branco após os dois-pontos no inicializador do construtor Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Permitir linhas em branco entre chaves consecutivas Allow embedded statements on same line - Allow embedded statements on same line + Permitir instruções inseridas na mesma linha @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Concluir a instrução automaticamente no ponto e vírgula Automatically show completion list in argument lists - Mostrar automaticamente a lista de conclusão nas listas de argumentos (experimental) + Mostrar automaticamente a lista de conclusão nas listas de argumentos @@ -54,7 +54,7 @@ Completion - Completion + Conclusão @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Colocar uma chave de abertura em uma nova linha para inicializadores de objeto, coleção, matriz e with {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf index b85d500b965aa..a32c43edda66e 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Разрешать пустую строку после двоеточия в инициализаторе конструктора Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Разрешать пустые строки между последовательными фигурными скобками Allow embedded statements on same line - Allow embedded statements on same line + Разрешать встроенные операторы на одной строке @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Автоматически завершать инструкцию при вводе точки с запятой Automatically show completion list in argument lists - Автоматически показывать список завершения в списках аргументов (экспериментальная функция) + Автоматически показывать список завершения в списках аргументов @@ -54,7 +54,7 @@ Completion - Completion + Завершение @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Помещать открывающую фигурную скобку на новой строке для объекта, коллекции, массива и инициализаторов with {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf index b34b1dc64d4d8..65cd8caa4d2b8 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + Oluşturucu başlatıcıda iki noktadan sonra boş satıra izin ver Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + Ardışık küme ayraçları arasında boş satırlara izin ver Allow embedded statements on same line - Allow embedded statements on same line + Aynı satırda katıştırılmış ifadelere izin ver @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + Deyimi noktalı virgülle otomatik olarak tamamla Automatically show completion list in argument lists - Bağımsız değişken listelerinde tamamlama listesini otomatik olarak göster (deneysel) + Bağımsız değişken listelerinde tamamlama listesini otomatik olarak göster @@ -54,7 +54,7 @@ Completion - Completion + Tamamlama @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + Nesne, koleksiyon, dizi ve with başlatıcıları için açma küme ayracını yeni satıra yerleştir {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf index ad32353154461..6694126dc85fa 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + 允许在构造函数初始值设定项中的冒号后面使用空白行 Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + 允许在连续大括号之间使用空白行 Allow embedded statements on same line - Allow embedded statements on same line + 允许将嵌入的语句放在同一行上 @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + 以分号自动补全语句 Automatically show completion list in argument lists - 自动显示参数列表中的完成列表(实验性) + 自动显示参数列表中的补全列表 @@ -54,7 +54,7 @@ Completion - Completion + 完成 @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 对于对象、集合、数组和 with 初始值设定项,另起一行放置左花括号 {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf index 2d2b18f613244..c23c57609349a 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -9,17 +9,17 @@ Allow blank line after colon in constructor initializer - Allow blank line after colon in constructor initializer + 允許在建構函式初始設定式的冒號後面加上空白行 Allow blank lines between consecutive braces - Allow blank lines between consecutive braces + 允許在連續的大括弧之間使用空白行 Allow embedded statements on same line - Allow embedded statements on same line + 允許在同一行使用內嵌陳述式 @@ -29,12 +29,12 @@ Automatically complete statement on semicolon - Automatically complete statement on semicolon + 在遇到分號時自動完成陳述式 Automatically show completion list in argument lists - 自動在引數清單中顯示自動完成清單 (實驗性) + 自動在引數清單中顯示自動完成清單 @@ -54,7 +54,7 @@ Completion - Completion + 完成 @@ -109,7 +109,7 @@ Place open brace on new line for object, collection, array, and with initializers - Place open brace on new line for object, collection, array, and with initializers + 在物件、集合、陣列以及 with 初始設定式的新行上放上左大括弧 {Locked="with"} diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf index 90a7db406d738..5ceecaa1ec191 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Zobrazovat vložené nápovědy; + Zobrazovat vložené nápovědy; Zobrazit diagnostiku pro zavřené soubory; Vybarvit regulární výraz; Zvýrazňovat související komponenty pod kurzorem; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Změnit nastavení pro seznam dokončení;Předvolit naposledy použitou položku; Seznamy dokončení; + Změnit nastavení pro seznam dokončení;Předvolit naposledy použitou položku; Seznamy dokončení; Po zadání znaku zobrazit seznam dokončení; Po odstranění znaku zobrazit seznam dokončení; Automaticky zobrazovat seznam dokončení v seznamech argumentů (experimentální); Zvýraznit odpovídající části položek seznamu dokončení; Zobrazit filtry položek dokončení; +Automaticky dokončit příkaz středníkem; Chování fragmentů; Nikdy nezahrnovat fragmenty; Vždy zahrnovat fragmenty; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf index 12a8a5df05f9f..ec058fb7caf1b 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Inlinehinweise anzeigen; + Inlinehinweise anzeigen; Diagnoseinformationen für geschlossene Dateien anzeigen; Reguläre Ausdrücke farbig hervorheben; Zugehörige Komponenten unter dem Cursor markieren; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Einstellungen der Vervollständigungsliste ändern;Vorauswahl des zuletzt verwendeten Members; Vervollständigungslisten; + Einstellungen der Vervollständigungsliste ändern;Vorauswahl des zuletzt verwendeten Members; Vervollständigungslisten; Vervollständigungsliste nach Eingabe eines Zeichens anzeigen; Vervollständigungsliste nach Löschen eines Zeichens anzeigen; Vervollständigungsliste in Argumentlisten automatisch anzeigen (experimentell); \Übereinstimmende Teile der Vervollständigungslistenelemente anzeigen; Vervollständigungselementfilter anzeigen; +Anweisung bei Semikolon automatisch abschließen; Ausschnittverhalten; Ausschnitte nie einschließen; Ausschnitte immer einschließen; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf index 680fadd5c63b9..79041ce406cea 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Mostrar sugerencias insertadas; + Mostrar sugerencias insertadas; Mostrar diagnóstico para archivos cerrados; Colorear expresión regular; Resaltar componentes relacionados bajo el cursor; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Cambiar configuración de la lista de finalización;Preseleccionar el miembro usado recientemente; Listas de finalización; + Cambiar configuración de la lista de finalización;Preseleccionar el miembro usado recientemente; Listas de finalización; Mostrar lista de finalización después de escribir un carácter; Mostrar lista de finalización después de eliminar un carácter; Mostrar lista de finalización en las listas de argumentos automáticamente (experimental); Resaltar partes coincidentes de elementos de la lista de finalización; Mostrar filtros de elementos de finalización; +Completar automáticamente la instrucción al introducir punto y coma; Comportamiento de los fragmentos de código; No incluir nunca fragmentos de código; Incluir siempre fragmentos de código; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf index b757a541bcab2..60533c2835efe 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Afficher les indicateurs inline ; + Afficher les indicateurs inline ; Afficher les diagnostics pour les fichiers fermés ; Mettre en couleurs l'expression régulière ; Surligner les composants liés sous le curseur ; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Changer les paramètres de la liste de complétion;Présélectionner le dernier membre utilisé récemment;Listes de complétion; + Changer les paramètres de la liste de complétion;Présélectionner le dernier membre utilisé récemment;Listes de complétion; Afficher la liste de complétion une fois qu'un caractère a été tapé ; Afficher la liste de complétion après la suppression d'un caractère ; Afficher automatiquement la liste de complétion dans les listes d'arguments (expérimental) ; Mettre en surbrillance les parties correspondantes des éléments de liste de complétion ; Afficher les filtres d'éléments de complétion ; +Effectuer la complétion automatique de l'instruction après l'entrée d'un point-virgule ; Comportement des extraits ; Ne jamais inclure d'extraits ; Toujours inclure les extraits ; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf index 8296006b151fd..18600b271474a 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Visualizza suggerimenti inline; + Visualizza suggerimenti inline; Mostra diagnostica per file chiusi; Colora espressione regolare; Evidenzia i componenti correlati sotto il cursore; @@ -290,19 +291,20 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Modifica impostazioni di completamento elenco;Preseleziona membri usati più di recente elenco di completamento;Elenchi di completamento; + Modifica impostazioni dell'elenco di completamento;Preseleziona membri usati di recente; Elenchi di completamento; Mostra elenco di completamento dopo la digitazione di un carattere; Mostra elenco di completamento dopo l'eliminazione di un carattere; Mostra automaticamente l'elenco di completamento negli elenchi di argomenti (sperimentale); Evidenzia le parti corrispondenti di voci dell'elenco di completamento; Mostra i filtri per le voci di completamento; +Completa automaticamente istruzione dopo punto e virgola; Comportamento dei frammenti; Non includere mai i frammenti; Includi sempre i frammenti; Includi i frammenti quando si digita ?+TAB dopo un identificatore; Comportamento del tasto INVIO; Non aggiungere mai una nuova riga dopo INVIO; -Aggiungi una nuova riga dopo INVIO alla fine della parola digitata; +Aggiungi una nuova riga dopo INVIO solo alla fine della parola digitata; Aggiungi sempre una nuova riga dopo INVIO; Mostra suggerimenti per nomi; Mostra elementi da spazi dei nomi non importati (sperimentale); diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf index 1762469aed9ca..d80ccb3217566 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - インラインのヒントを表示する; + インラインのヒントを表示する; 閉じているファイルの診断結果を表示する; 正規表現をカラー化する; カーソルの下にある関連コンポーネントを強調表示する; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - 入力候補一覧の設定を変更する;最近使用されたメンバーをあらかじめ選択する; 入力候補一覧; + 入力候補一覧の設定を変更する;最近使用されたメンバーをあらかじめ選択する; 入力候補一覧; 文字が入力された後に入力候補一覧を表示する; 文字が削除された後に入力候補一覧を表示する; 引数リストに入力候補一覧を自動的に表示する (試験段階); 入力候補一覧の項目の一致している部分を強調表示する; 入力候補の項目フィルターを表示する; +セミコロンでステートメントをオートコンプリートに設定する; スニペットの動作; スニペットを含めない; 常にスニペットを含める; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf index 8774e370cfa82..e08d82d4fbf68 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - 인라인 힌트 표시; + 인라인 힌트 표시; 닫힌 파일에 대한 진단 표시; 정규식 색 지정, 커서 아래의 관련 구성 요소 강조 표시, @@ -290,20 +291,21 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - 완성 목록 설정 변경;가장 최근에 사용한 멤버 미리 선택; 완성 목록; + 완성 목록 설정 변경;가장 최근에 사용한 멤버 미리 선택; 완성 목록; 문자를 입력하면 완성 목록 표시; 문자를 삭제하면 완성 목록 표시; 인수 목록에 자동으로 완성 목록 표시(실험적); 완성 목록 항목에서 일치하는 부분 강조 표시; 완성 항목 필터 표시; +세미콜론 입력 시 자동으로 문 완성; 코드 조각 동작; 코드 조각 포함 안 함; 코드 조각 항상 포함; 식별자 뒤에 ?-Tab을 입력하면 코드 조각 포함; -<Enter> 키 동작; -<Enter> 키를 누를 때 새 줄 추가 안 함; -단어를 모두 입력한 후 <Enter> 키를 누를 때만 새 줄 추가; -<Enter> 키를 누를 때 항상 새 줄 추가; +&lt;Enter&gt; 키 동작; +&lt;Enter&gt; 키를 누를 때 새 줄 추가 안 함; +단어를 모두 입력한 후 &lt;Enter&gt; 키를 누를 때만 새 줄 추가; +&lt;Enter&gt; 키를 누를 때 항상 새 줄 추가; 이름 제안 표시; 가져오지 않은 네임스페이스의 항목 표시(실험적); C# IntelliSense options page keywords diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf index 05f1b376b2640..1e0ac9a636259 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Wyświetlaj wskazówki w tekście; + Wyświetlaj wskazówki w tekście; Pokaż dane diagnostyczne dla zamkniętych plików; Koloruj wyrażenia regularne; Wyróżnij powiązane składniki pod kursorem; @@ -290,7 +291,7 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Zmień ustawienia listy uzupełniania;Wybierz wstępnie ostatnio używaną składową; Listy uzupełniania; + Zmień ustawienia listy uzupełniania;Wybierz wstępnie ostatnio używaną składową; Listy uzupełniania; Pokaż listę uzupełniania po wpisaniu znaku; Pokaż listę uzupełniania po usunięciu znaku; Automatycznie pokazuj listę uzupełniania na liście argumentów (funkcja eksperymentalna); diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf index 88b956e65da2e..8193368f22710 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Exibir as dicas embutidas; + Exibir as dicas embutidas; Mostrar os diagnósticos de arquivos fechados; Colorir a expressão regular; Realçar os componentes relacionados sob o cursor; @@ -290,20 +291,21 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Alterar as configurações da lista de conclusão;Pré-selecionar o membro usado mais recentemente; Listas de Conclusão; + Alterar as configurações da lista de conclusão;Pré-selecionar o membro usado mais recentemente; Listas de Conclusão; Mostrar a lista de conclusão após a digitação de um caractere; Mostrar a lista de conclusão após a exclusão de um caractere; Mostrar automaticamente a lista de conclusão nas listas de argumentos (experimental); Realçar as partes correspondentes dos itens da lista de conclusão; Mostrar os filtros de itens de conclusão; -Comportamento de trechos; -Nunca incluir trechos; -Sempre incluir trechos; -Incluir trechos quando ?-Tab é digitado após um identificador; -Inserir o comportamento chave; +Concluir a instrução automaticamente no ponto e vírgula; +Comportamento de snippets; +Nunca incluir snippets; +Sempre incluir snippets; +Incluir snippets quando ?-Tab é digitado após um identificador; +Comportamento da tecla Enter; Nunca adicionar uma nova linha ao pressionar Enter; -Apenas adicionar uma nova linha ao pressionar Enter depois que a palavra for digitada completamente; -Sempre adicionar nova linha ao pressionar Enter; +Apenas adicionar uma nova linha ao pressionar Enter depois que a palavra é digitada completamente; +Sempre adicionar uma nova linha ao pressionar Enter; Mostrar sugestões de nomes; Mostrar itens de namespaces não importados (experimental); C# IntelliSense options page keywords diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf index 8425701259dbc..8db36cfa8dba9 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Отображать встроенные подсказки; + Отображать встроенные подсказки; Отображать диагностику для закрытых файлов; Выделять регулярные выражения цветом; Выделять связанные компоненты под курсором; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Изменить параметры списка завершения;Предварительно выбрать наиболее часто используемый элемент; Списки завершения; + Изменить параметры списка завершения;Предварительно выбрать наиболее часто используемый элемент; Списки завершения; Показывать список завершения после ввода символа; Показывать список завершения после удаления символа; Автоматически показывать список завершения в списках аргументов (экспериментальная функция); Выделять совпадающие части элементов списка завершения; Показывать фильтры элементов завершения; +Автоматически завершать предложение при вводе точки с запятой; Поведение фрагментов кода; Никогда не включать фрагменты кода; Всегда включать фрагменты кода; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf index ea80c53793231..20dda4691f5cf 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - Satır içi ipuçlarını göster; + Satır içi ipuçlarını göster; Kapatılan dosyalara ilişkin tanılamaları göster; Normal ifadeyi renklendir; İmlecin altındaki ilgili bileşenleri vurgula; @@ -290,22 +291,23 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - Tamamlanma listesi ayarlarını değiştir;Son kullanılan üyeyi önceden seç; Tamamlanma Listeleri; -Bir karakter yazıldıktan sonra tamamlanma listesini göster; -Bir karakter silindikten sonra tamamlanma listesini göster; -Bağımsız değişken listelerinde tamamlanma listelerini otomatik olarak göster (deneysel); -Tamamlanma listesi öğelerinin eşleşen kısımlarını vurgula; -Tamamlanma öğesi filtrelerini göster; + Tamamlama listesi ayarlarını değiştir;Son kullanılan üyeyi önceden seç; Tamamlama Listeleri; +Bir karakter yazıldıktan sonra tamamlama listesini göster; +Bir karakter silindikten sonra tamamlama listesini göster; +Bağımsız değişken listelerinde tamamlama listelerini otomatik olarak göster (deneysel); +Tamamlama listesi öğelerinin eşleşen kısımlarını vurgula; +Tamamlama öğesi filtrelerini göster; +Deyimi noktalı virgülle otomatik olarak tamamla; Kod parçacığı davranışı; Kod parçacıklarını hiçbir zaman dahil etme; Kod parçacıklarını her zaman dahil et; -Bir tanımlayıcıdan sonra ?-Tab yazılırsa kod parçacıklarını dahil et; +Bir tanımlayıcıdan sonra ?- yazılıp Sekme tuşuna basılırsa kod parçacıklarını dahil et; Enter tuşu davranışı; Enter tuşuna basıldığında hiçbir zaman yeni satır ekleme; Enter tuşuna basıldığında yalnızca tam bir kelime yazılmışsa sonuna yeni satır ekle; Enter tuşuna basıldığında her zaman yeni satır ekle; Ad önerilerini göster; -İçeri aktarılmamış ad alanlarından öğeleri göster (deneysel); +İçeri aktarılmamış ad alanlarındaki öğeleri göster (deneysel); C# IntelliSense options page keywords diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf index 4d6cf7fb186a1..7192bb466f6d9 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - 显示内联提示; + 显示内联提示; 显示对已关闭文件的诊断; 对正则表达式着色; 突出显示游标下的相关组件; @@ -290,12 +291,13 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - 更改完成列表设置;预先选择最近使用过的成员;完成列表; + 更改完成列表设置;预先选择最近使用过的成员;完成列表; 在键入字符后显示完成列表; 在删除字符后显示完成列表; 自动显示参数列表中的完成列表(实验性); 突出显示完成列表项的匹配部分; 显示完成项筛选器; +以分号自动补全语句; 片段行为; 永不包含片段; 总是包含片段; diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf index 44e1312245deb..44ee0c2b86226 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -33,7 +33,8 @@ "C#" node help text in profile Import/Export. - Display inline hints; + Underline reassigned variables; +Display inline hints; Show diagnostics for closed files; Colorize regular expression; Highlight related components under cursor; @@ -81,7 +82,7 @@ regex; regular expression; Use enhanced colors; Editor Color Scheme; - 顯示內嵌提示; + 顯示內嵌提示; 顯示已關閉檔案的診斷; 為規則運算式著色; 醒目提示游標下的相關元件; @@ -290,20 +291,21 @@ Only add new line on enter after end of fully typed word; Always add new line on enter; Show name suggestions; Show items from unimported namespaces (experimental); - 變更自動完成清單設定;預先選取最近使用的成員; 自動完成清單; + 變更自動完成清單設定;預先選取最近使用的成員; 自動完成清單; 在鍵入字元後顯示自動完成清單; 在刪除字元後顯示自動完成清單; 自動在引數清單中顯示自動完成清單 (實驗性); 醒目提示自動完成清單項目的相符部分; 顯示完成項目篩選; +在遇到分號時自動完成陳述式; 程式碼片段行為; -永不包含程式碼片段; -永遠包含程式碼片段; +一律不包含程式碼片段; +一律包含程式碼片段; 在識別碼後鍵入 ?-Tab 時包含程式碼片段; Enter 鍵行為; -永不在按下 enter 鍵時新增一行程式碼; -只在按下 enter 鍵時,於完整鍵入的文字結尾後新增一行程式碼; -永遠在按下 enter 鍵時新增一行程式碼; +一律不在按下 Enter 鍵時新增一行程式碼; +只在按下 Enter 鍵時,於完整鍵入的文字結尾後新增一行程式碼; +一律在按下 Enter 鍵時新增一行程式碼; 顯示名稱建議; 顯示未匯入命名空間中的項目 (實驗性); C# IntelliSense options page keywords diff --git a/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs b/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs index 1f63e7c6a3b9a..37c6513339b0b 100644 --- a/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs +++ b/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs @@ -25,7 +25,7 @@ public abstract class AbstractFileCodeElementTests : IDisposable private readonly string _contents; private (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDisposeButNotUse, FileCodeModel fileCodeModel)? _workspaceAndCodeModel; - public AbstractFileCodeElementTests(string contents) + protected AbstractFileCodeElementTests(string contents) { _contents = contents; } diff --git a/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs b/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs index 9882123bc1152..a5a104ec42953 100644 --- a/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs +++ b/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs @@ -41,7 +41,6 @@ public static (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDi var visualStudioWorkspaceMock = new MockVisualStudioWorkspace(workspace); var threadingContext = workspace.ExportProvider.GetExportedValue(); - var notificationService = workspace.ExportProvider.GetExportedValue(); var listenerProvider = workspace.ExportProvider.GetExportedValue(); var state = new CodeModelState( @@ -53,7 +52,6 @@ public static (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDi visualStudioWorkspaceMock, serviceProvider, threadingContext, - notificationService, listenerProvider)); var codeModel = FileCodeModel.Create(state, null, document, new MockTextManagerAdapter()).Handle; diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveCommandHandlerTests.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveCommandHandlerTests.cs index 5e15f38ca1531..bb1d833befdcc 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveCommandHandlerTests.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/InteractiveCommandHandlerTests.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.Composition; using Roslyn.Test.Utilities; using Xunit; diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs index 199717cac706a..39e463281d2cc 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs @@ -20,6 +20,7 @@ using InteractiveHost::Microsoft.CodeAnalysis.Interactive; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.VisualStudio.InteractiveWindow; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands { @@ -80,13 +81,13 @@ private async Task AssertResetInteractiveAsync( void executeSubmission(object _, string code) => executedSubmissionCalls.Add(code); testHost.Evaluator.OnExecute += executeSubmission; - var waitIndicator = workspace.GetService(); + var uiThreadOperationExecutor = workspace.GetService(); var editorOptionsFactoryService = workspace.GetService(); var editorOptions = editorOptionsFactoryService.GetOptions(testHost.Window.CurrentLanguageBuffer); var newLineCharacter = editorOptions.GetNewLineCharacter(); var resetInteractive = new TestResetInteractive( - waitIndicator, + uiThreadOperationExecutor, editorOptionsFactoryService, CreateReplReferenceCommand, CreateImport, diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs index 600a5b071f55d..30ae70228355a 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs @@ -15,12 +15,14 @@ using Microsoft.VisualStudio.InteractiveWindow; using System.Collections.Generic; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Language.Intellisense.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands { internal class TestResetInteractive : ResetInteractive { - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly bool _buildSucceeds; @@ -43,14 +45,14 @@ internal class TestResetInteractive : ResetInteractive internal string ProjectDirectory { get; set; } public TestResetInteractive( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, IEditorOptionsFactoryService editorOptionsFactoryService, Func createReference, Func createImport, bool buildSucceeds) : base(editorOptionsFactoryService, createReference, createImport) { - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _buildSucceeds = buildSucceeds; } @@ -82,9 +84,9 @@ protected override bool GetProjectProperties( return true; } - protected override IWaitIndicator GetWaitIndicator() + protected override IUIThreadOperationExecutor GetUIThreadOperationExecutor() { - return _waitIndicator; + return _uiThreadOperationExecutor; } protected override Task> GetNamespacesToImportAsync(IEnumerable namespacesToImport, IInteractiveWindow interactiveWindow) diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index fcdff9c634dcb..3f6bde5d966f7 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -52,6 +52,7 @@ + diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index 4c54f7cca1358..09ffc4b65775b 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -12,9 +12,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.LanguageServices.UnitTests; using Roslyn.Test.Utilities; using Xunit; @@ -74,6 +76,9 @@ protected AbstractPersistentStorageTests() ThreadPool.SetMinThreads(Math.Max(workerThreads, NumThreads), completionPortThreads); } + internal abstract AbstractPersistentStorageService GetStorageService( + OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string rootFolder); + public void Dispose() { // This should cause the service to release the cached connection it maintains for the primary workspace @@ -248,24 +253,6 @@ public async Task PersistentService_Document_SimultaneousWrites() Assert.True(value < NumThreads); } - private void DoSimultaneousWrites(Func write) - { - var barrier = new Barrier(NumThreads); - var countdown = new CountdownEvent(NumThreads); - for (var i = 0; i < NumThreads; i++) - { - ThreadPool.QueueUserWorkItem(s => - { - var id = (int)s; - barrier.SignalAndWait(); - write(id + "").Wait(); - countdown.Signal(); - }, i); - } - - countdown.Wait(); - } - [Theory] [CombinatorialData] public async Task PersistentService_Solution_SimultaneousReads(Size size, bool withChecksum) @@ -799,6 +786,26 @@ public void CacheDirectoryShouldNotBeAtRoot() Assert.False(location?.StartsWith("/") ?? false); } + [Theory] + [CombinatorialData] + public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum) + { + var solution = CreateOrOpenSolution(); + var streamName1 = "PersistentService_ReadByteTwice"; + + await using (var storage = await GetStorageAsync(solution)) + { + Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); + } + + await using (var storage = await GetStorageAsync(solution)) + { + using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)); + stream.ReadByte(); + stream.ReadByte(); + } + } + [PartNotDiscoverable] [ExportWorkspaceService(typeof(IPersistentStorageLocationService), layer: ServiceLayer.Test), Shared] private class TestPersistentStorageLocationService : DefaultPersistentStorageLocationService @@ -843,13 +850,45 @@ private void DoSimultaneousReads(Func> read, string expectedValue) Assert.Equal(new List(), exceptions); } + private void DoSimultaneousWrites(Func write) + { + var barrier = new Barrier(NumThreads); + var countdown = new CountdownEvent(NumThreads); + + var exceptions = new List(); + for (var i = 0; i < NumThreads; i++) + { + ThreadPool.QueueUserWorkItem(s => + { + var id = (int)s; + barrier.SignalAndWait(); + try + { + write(id + "").Wait(); + } + catch (Exception ex) + { + lock (exceptions) + { + exceptions.Add(ex); + } + } + countdown.Signal(); + }, i); + } + + countdown.Wait(); + + Assert.Empty(exceptions); + } + protected Solution CreateOrOpenSolution(bool nullPaths = false) { var solutionFile = _persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path); - var workspace = new AdhocWorkspace(); + var workspace = new AdhocWorkspace(VisualStudioTestCompositions.LanguageServices.GetHostServices()); workspace.AddSolution(info); var solution = workspace.CurrentSolution; @@ -876,13 +915,15 @@ internal async Task GetStorageAsync( _storageService?.GetTestAccessor().Shutdown(); var locationService = new MockPersistentStorageLocationService(solution.Id, _persistentFolder.Path); - _storageService = GetStorageService((IMefHostExportProvider)solution.Workspace.Services.HostServices, locationService, faultInjector); + _storageService = GetStorageService( + solution.Options, (IMefHostExportProvider)solution.Workspace.Services.HostServices, + locationService, faultInjector, _persistentFolder.Path); var storage = await _storageService.GetStorageAsync(solution, checkBranchId: true, CancellationToken.None); // If we're injecting faults, we expect things to be strange if (faultInjector == null) { - Assert.NotEqual(NoOpPersistentStorage.Instance, storage); + Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage); } return storage; @@ -895,13 +936,14 @@ internal async Task GetStorageFromKeyAsync( _storageService?.GetTestAccessor().Shutdown(); var locationService = new MockPersistentStorageLocationService(solutionKey.Id, _persistentFolder.Path); - _storageService = GetStorageService((IMefHostExportProvider)workspace.Services.HostServices, locationService, faultInjector); + _storageService = GetStorageService( + workspace.Options, (IMefHostExportProvider)workspace.Services.HostServices, locationService, faultInjector, _persistentFolder.Path); var storage = await _storageService.GetStorageAsync(workspace, solutionKey, checkBranchId: true, CancellationToken.None); // If we're injecting faults, we expect things to be strange if (faultInjector == null) { - Assert.NotEqual(NoOpPersistentStorage.Instance, storage); + Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage); } return storage; @@ -927,8 +969,6 @@ public MockPersistentStorageLocationService(SolutionId solutionId, string storag => solutionKey.Id == _solutionId ? _storageLocation : null; } - internal abstract AbstractPersistentStorageService GetStorageService(IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector); - protected Stream EncodeString(string text) { var bytes = _encoding.GetBytes(text); @@ -940,14 +980,9 @@ private string ReadStringToEnd(Stream stream) { using (stream) { - var bytes = new byte[stream.Length]; - var count = 0; - while (count < stream.Length) - { - count = stream.Read(bytes, count, (int)stream.Length - count); - } - - return _encoding.GetString(bytes); + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + return _encoding.GetString(memoryStream.ToArray()); } } } diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs new file mode 100644 index 0000000000000..feb9fbd8b2fdd --- /dev/null +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks; + +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices +{ + public class CloudCachePersistentStorageTests : AbstractPersistentStorageTests + { + internal override AbstractPersistentStorageService GetStorageService( + OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string relativePathBase) + { + var threadingContext = exportProvider.GetExports().Single().Value; + return new MockCloudCachePersistentStorageService( + locationService, + relativePathBase, + cs => + { + if (cs is IAsyncDisposable asyncDisposable) + { + threadingContext.JoinableTaskFactory.Run( + () => asyncDisposable.DisposeAsync().AsTask()); + } + else if (cs is IDisposable disposable) + { + disposable.Dispose(); + } + }); + } + } +} diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/AuthorizationServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/AuthorizationServiceMock.cs new file mode 100644 index 0000000000000..405d6af003f63 --- /dev/null +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/AuthorizationServiceMock.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. + +// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain +// Try to keep in sync and avoid unnecessary changes here. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.ServiceHub.Framework.Services; + +#pragma warning disable CS0067 // events that are never used + +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks +{ + internal class AuthorizationServiceMock : IAuthorizationService + { + public event EventHandler? CredentialsChanged; + + public event EventHandler? AuthorizationChanged; + + internal bool Allow { get; set; } = true; + + public ValueTask CheckAuthorizationAsync(ProtectedOperation operation, CancellationToken cancellationToken = default) + { + return new ValueTask(this.Allow); + } + + public ValueTask> GetCredentialsAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/FileSystemServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/FileSystemServiceMock.cs new file mode 100644 index 0000000000000..58009779502fb --- /dev/null +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/FileSystemServiceMock.cs @@ -0,0 +1,89 @@ +// 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. + +// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain +// Try to keep in sync and avoid unnecessary changes here. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.ServiceHub.Framework; +using Microsoft.VisualStudio.RpcContracts.FileSystem; + +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks +{ + internal class FileSystemServiceMock : IFileSystem + { + public event EventHandler? DirectoryEntryChanged; + + public event EventHandler? RootEntriesChanged; + + public Task ConvertLocalFileNameToRemoteUriAsync(string fileName, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ConvertLocalFileNameToRemoteUriAsync(string fileName, string remoteScheme, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ConvertLocalUriToRemoteUriAsync(Uri localUri, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ConvertLocalUriToRemoteUriAsync(Uri localUri, string remoteScheme, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ConvertRemoteFileNameToRemoteUriAsync(string _1, CancellationToken _2) => throw new NotImplementedException(); + + public Task ConvertRemoteFileNameToRemoteUriAsync(string _1, string _2, CancellationToken _3) => throw new NotImplementedException(); + + public Task ConvertRemoteUriToLocalUriAsync(Uri remoteUri, CancellationToken cancellationToken) => Task.FromResult(remoteUri); + + public Task ConvertRemoteUriToRemoteFileNameAsync(Uri _1, CancellationToken _2) => throw new NotImplementedException(); + + public Task CopyAsync(Uri sourceUri, Uri destinationUri, bool overwrite, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task CreateDirectoryAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task DeleteAsync(Uri uri, bool recursive, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task DownloadFileAsync(Uri remoteUri, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public IAsyncEnumerable EnumerateDirectoriesAsync(Uri uri, string searchPattern, SearchOption searchOption, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public IAsyncEnumerable EnumerateDirectoryEntriesAsync(Uri uri, string searchPattern, SearchOption searchOption, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public IAsyncEnumerable EnumerateFilesAsync(Uri uri, string searchPattern, SearchOption searchOption, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task GetDefaultRemoteUriSchemeAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task GetDisplayInfoAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task GetDisplayInfoAsync(string fileName, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task GetInfoAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task GetMonikerForFileSystemProviderAsync(string scheme, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task GetMonikerForRemoteFileSystemProviderAsync(string scheme, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task> GetRootEntriesAsync(string scheme, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task> GetRootEntriesAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task> GetSupportedSchemesAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task MoveAsync(Uri oldUri, Uri newUri, bool overwrite, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ReadFileAsync(Uri uri, PipeWriter writer, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask UnwatchAsync(WatchResult watchResult, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask WatchDirectoryAsync(Uri uri, bool recursive, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask WatchFileAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task WriteFileAsync(Uri uri, PipeReader reader, bool overwrite, CancellationToken cancellationToken) => throw new NotImplementedException(); + + protected virtual void OnDirectoryEntryChanged(DirectoryEntryChangedEventArgs args) => this.DirectoryEntryChanged?.Invoke(this, args); + + protected virtual void OnRootEntriesChanged(RootEntriesChangedEventArgs args) => this.RootEntriesChanged?.Invoke(this, args); + } +} diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/MockCloudCachePersistentStorageService.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/MockCloudCachePersistentStorageService.cs new file mode 100644 index 0000000000000..894eb5b9f9f26 --- /dev/null +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/MockCloudCachePersistentStorageService.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.ServiceHub.Framework; +using Microsoft.ServiceHub.Framework.Services; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Cache; +using Microsoft.VisualStudio.Cache.SQLite; +using Microsoft.VisualStudio.LanguageServices.Storage; +using Microsoft.VisualStudio.RpcContracts.Caching; + +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks +{ + internal class MockCloudCachePersistentStorageService : AbstractCloudCachePersistentStorageService + { + private readonly string _relativePathBase; + private readonly Action _disposeCacheService; + + public MockCloudCachePersistentStorageService( + IPersistentStorageLocationService locationService, + string relativePathBase, + Action disposeCacheService) + : base(locationService) + { + _relativePathBase = relativePathBase; + _disposeCacheService = disposeCacheService; + } + + protected override void DisposeCacheService(ICacheService cacheService) + => _disposeCacheService(cacheService); + + protected override async ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken) + { + // Directly access VS' CacheService through their library and not as a brokered service. Then create our + // wrapper CloudCacheService directly on that instance. + var authorizationServiceClient = new AuthorizationServiceClient(new AuthorizationServiceMock()); + var solutionService = new SolutionServiceMock(); + var fileSystem = new FileSystemServiceMock(); + var serviceBroker = new ServiceBrokerMock() + { + BrokeredServices = + { + { VisualStudioServices.VS2019_10.SolutionService.Moniker, solutionService }, + { VisualStudioServices.VS2019_10.FileSystem.Moniker, fileSystem }, + { FrameworkServices.Authorization.Moniker, new AuthorizationServiceMock() }, + }, + }; + + var someContext = new CacheContext { RelativePathBase = _relativePathBase }; + var pool = new SqliteConnectionPool(); + var activeContext = await pool.ActivateContextAsync(someContext, default); + var cacheService = new CacheService(activeContext, serviceBroker, authorizationServiceClient, pool); + return cacheService; + } + } +} diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/OptionServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs similarity index 97% rename from src/VisualStudio/CSharp/Test/PersistentStorage/OptionServiceMock.cs rename to src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs index 0af88b3beef93..caa140bd3db5b 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/OptionServiceMock.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks { internal class OptionServiceMock : IOptionService { diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/ServiceBrokerMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/ServiceBrokerMock.cs new file mode 100644 index 0000000000000..8a32ef1b221b9 --- /dev/null +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/ServiceBrokerMock.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain +// Try to keep in sync and avoid unnecessary changes here. + +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks +{ + internal class ServiceBrokerMock : IServiceBroker + { + public event EventHandler? AvailabilityChanged; + + internal Dictionary BrokeredServices { get; } = new(); + + public ValueTask GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options = default, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask GetProxyAsync(ServiceRpcDescriptor serviceDescriptor, ServiceActivationOptions options = default, CancellationToken cancellationToken = default) + where T : class + { + if (this.BrokeredServices.TryGetValue(serviceDescriptor.Moniker, out var service)) + { + return new((T?)service); + } + + return default; + } + + internal void OnAvailabilityChanged(BrokeredServicesChangedEventArgs args) => this.AvailabilityChanged?.Invoke(this, args); + } +} diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/SolutionServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/SolutionServiceMock.cs new file mode 100644 index 0000000000000..0819ce75e36df --- /dev/null +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/SolutionServiceMock.cs @@ -0,0 +1,104 @@ +// 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. + +// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain +// Try to keep in sync and avoid unnecessary changes here. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Microsoft.VisualStudio.RpcContracts.Solution; +using Microsoft.VisualStudio.Threading; + +#pragma warning disable CS0067 // events that are never used + +namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks +{ + internal class SolutionServiceMock : ISolutionService + { + private readonly BroadcastObservable openContainerObservable = new BroadcastObservable(new OpenCodeContainersState()); + + public event EventHandler? ProjectsLoaded; + + public event EventHandler? ProjectsUnloaded; + + public event EventHandler? ProjectLoadProgressChanged; + + internal Uri? SolutionFilePath + { + get => this.openContainerObservable.Value.SolutionFilePath; + set => this.openContainerObservable.Value = this.openContainerObservable.Value with { SolutionFilePath = value }; + } + + public ValueTask AreProjectsLoadedAsync(Guid[] projectIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task SubscribeToOpenCodeContainersStateAsync(IObserver observer, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(this.openContainerObservable.Subscribe(observer)); + } + + public Task GetOpenCodeContainersStateAsync(CancellationToken cancellationToken) => Task.FromResult(this.openContainerObservable.Value); + + public Task CloseSolutionAndFolderAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask> GetPropertyValuesAsync(IReadOnlyList propertyIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask> GetSolutionTelemetryContextPropertyValuesAsync(IReadOnlyList propertyNames, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask LoadProjectsAsync(Guid[] projectIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask LoadProjectsWithResultAsync(Guid[] projectIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask RemoveProjectsAsync(IReadOnlyList projectIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task RequestProjectEventsAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task SaveSolutionFilterFileAsync(string filterFileDirectory, string filterFileName, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public ValueTask UnloadProjectsAsync(Guid[] projectIds, ProjectUnloadReason unloadReason, CancellationToken cancellationToken) => throw new NotImplementedException(); + + internal void SimulateFolderChange(IReadOnlyList folderPaths) => this.openContainerObservable.Value = this.openContainerObservable.Value with { OpenFolderPaths = folderPaths }; + + private class BroadcastObservable : IObservable + { + private readonly BroadcastBlock sourceBlock = new(v => v); + private T value; + + internal BroadcastObservable(T initialValue) + { + this.sourceBlock.Post(this.value = initialValue); + } + + internal T Value + { + get => this.value; + set => this.sourceBlock.Post(this.value = value); + } + + public IDisposable Subscribe(IObserver observer) + { + var actionBlock = new ActionBlock(observer.OnNext); + actionBlock.Completion.ContinueWith( + static (t, s) => + { + var observer = (IObserver)s!; + if (t.Exception is object) + { + observer.OnError(t.Exception); + } + else + { + observer.OnCompleted(); + } + }, + observer, + TaskScheduler.Default).Forget(); + return this.sourceBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true }); + } + } + } +} diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index f05948bfd630a..06976afc94576 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SQLite.v2; using Microsoft.CodeAnalysis.Storage; using Xunit; @@ -21,8 +22,8 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices /// public class SQLiteV2PersistentStorageTests : AbstractPersistentStorageTests { - internal override AbstractPersistentStorageService GetStorageService(IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector) - => new SQLitePersistentStorageService(exportProvider.GetExports().Single().Value, locationService, faultInjector); + internal override AbstractPersistentStorageService GetStorageService(OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string relativePathBase) + => new SQLitePersistentStorageService(options, exportProvider.GetExports().Single().Value, locationService, faultInjector); [Fact] public async Task TestCrashInNewConnection() diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs index 41bc80f793c64..6bfdd9c096465 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs @@ -86,6 +86,7 @@ public static async Task CreateCSharpCPSProjectAsync(TestEnvironment projectGuid, hierarchy, binOutputPath, + assemblyName: null, CancellationToken.None); cpsProject.SetOptions(ImmutableArray.Create(commandLineArguments)); @@ -105,6 +106,7 @@ public static async Task CreateNonCompilableProjectAsync(TestEnviron Guid.NewGuid(), hierarchy, binOutputPath: null, + assemblyName: null, CancellationToken.None); } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/ViewModel/NewLineViewModel.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/ViewModel/NewLineViewModel.cs index 0258467dc7ff6..dc9a007825d5f 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/ViewModel/NewLineViewModel.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/Formatting/ViewModel/NewLineViewModel.cs @@ -38,8 +38,8 @@ public IEnumSettingViewModel CreateViewModel(FormattingSetting setting) internal enum NewLineSetting { Newline, - CarrageReturn, - CarrageReturnNewline, + CarriageReturn, + CarriageReturnNewline, NotSet } @@ -59,10 +59,10 @@ protected override void ChangePropertyTo(NewLineSetting newValue) case NewLineSetting.Newline: _setting.SetValue("\n"); break; - case NewLineSetting.CarrageReturn: + case NewLineSetting.CarriageReturn: _setting.SetValue("\r"); break; - case NewLineSetting.CarrageReturnNewline: + case NewLineSetting.CarriageReturnNewline: _setting.SetValue("\r\n"); break; case NewLineSetting.NotSet: @@ -76,8 +76,8 @@ protected override NewLineSetting GetCurrentValue() return _setting.GetValue() switch { "\n" => NewLineSetting.Newline, - "\r" => NewLineSetting.CarrageReturn, - "\r\n" => NewLineSetting.CarrageReturnNewline, + "\r" => NewLineSetting.CarriageReturn, + "\r\n" => NewLineSetting.CarriageReturnNewline, _ => NewLineSetting.NotSet, }; } @@ -89,8 +89,8 @@ protected override IReadOnlyDictionary GetValuesAndDescr static IEnumerable<(string description, NewLineSetting value)> EnumerateOptions() { yield return (ServicesVSResources.Newline_n, NewLineSetting.Newline); - yield return (ServicesVSResources.Carrage_Return_r, NewLineSetting.CarrageReturn); - yield return (ServicesVSResources.Carrage_Return_Newline_rn, NewLineSetting.CarrageReturnNewline); + yield return (ServicesVSResources.Carriage_Return_r, NewLineSetting.CarriageReturn); + yield return (ServicesVSResources.Carriage_Return_Newline_rn, NewLineSetting.CarriageReturnNewline); } } } diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs index 9d5c0f56592f0..8b8bb0770e07b 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs @@ -4,7 +4,9 @@ using System; using System.ComponentModel.Composition; +using System.Linq; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.EditorConfigSettings; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; @@ -79,6 +81,13 @@ public int CreateEditorInstance(uint grfCreateDoc, pgrfCDW = 0; pbstrEditorCaption = null; + if (!_workspace.CurrentSolution.Projects.Any(p => p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic)) + { + // If there are no VB or C# projects loaded in the solution (so an editorconfig file in a C++ project) then we want their + // editorfactory to present the file instead of use showing ours + return VSConstants.VS_E_UNSUPPORTEDFORMAT; + } + // Validate inputs if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0) { diff --git a/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs b/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs index 3339f4f70204b..ca88104a03209 100644 --- a/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs +++ b/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs @@ -2,12 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Composition; +using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.ExternalAccess.LegacyCodeAnalysis.Api; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.LanguageServices.Implementation.Suppression; using Microsoft.VisualStudio.Shell.Interop; @@ -18,20 +18,77 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.LegacyCodeAnalysis internal sealed class LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor : ILegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor { + private readonly VisualStudioWorkspace _workspace; private readonly IVisualStudioSuppressionFixService _implementation; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor(IVisualStudioSuppressionFixService implementation) - => _implementation = implementation; + public LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor( + VisualStudioWorkspace workspace, + IVisualStudioSuppressionFixService implementation) + { + _workspace = workspace; + _implementation = implementation; + } + + public bool AddSuppressions(IVsHierarchy? projectHierarchy) + { + var errorReportingService = _workspace.Services.GetRequiredService(); + + try + { + return _implementation.AddSuppressions(projectHierarchy); + } + catch (Exception ex) + { + errorReportingService.ShowGlobalErrorInfo( + string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + new InfoBarUI( + WorkspacesResources.Show_Stack_Trace, + InfoBarUI.UIKind.HyperLink, + () => errorReportingService.ShowDetailedErrorInfo(ex), closeAfterAction: true)); + return false; + } + } + + public bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy? projectHierarchy) + { + var errorReportingService = _workspace.Services.GetRequiredService(); - public bool AddSuppressions(IVsHierarchy projectHierarchyOpt) - => _implementation.AddSuppressions(projectHierarchyOpt); + try + { + return _implementation.AddSuppressions(selectedErrorListEntriesOnly, suppressInSource, projectHierarchy); + } + catch (Exception ex) + { + errorReportingService.ShowGlobalErrorInfo( + string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + new InfoBarUI( + WorkspacesResources.Show_Stack_Trace, + InfoBarUI.UIKind.HyperLink, + () => errorReportingService.ShowDetailedErrorInfo(ex), closeAfterAction: true)); + return false; + } + } - public bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt) - => _implementation.AddSuppressions(selectedErrorListEntriesOnly, suppressInSource, projectHierarchyOpt); + public bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy? projectHierarchy) + { + var errorReportingService = _workspace.Services.GetRequiredService(); - public bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt) - => _implementation.RemoveSuppressions(selectedErrorListEntriesOnly, projectHierarchyOpt); + try + { + return _implementation.RemoveSuppressions(selectedErrorListEntriesOnly, projectHierarchy); + } + catch (Exception ex) + { + errorReportingService.ShowGlobalErrorInfo( + string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + new InfoBarUI( + WorkspacesResources.Show_Stack_Trace, + InfoBarUI.UIKind.HyperLink, + () => errorReportingService.ShowDetailedErrorInfo(ex), closeAfterAction: true)); + return false; + } + } } } diff --git a/src/VisualStudio/Core/Def/ExternalAccess/ProjectSystem/Api/IProjectSystemReferenceCleanupService2.cs b/src/VisualStudio/Core/Def/ExternalAccess/ProjectSystem/Api/IProjectSystemReferenceCleanupService2.cs new file mode 100644 index 0000000000000..15b950a19b955 --- /dev/null +++ b/src/VisualStudio/Core/Def/ExternalAccess/ProjectSystem/Api/IProjectSystemReferenceCleanupService2.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.LanguageServices.ExternalAccess.ProjectSystem.Api +{ + // Interface to be implemented and MEF exported by Project System + internal interface IProjectSystemReferenceCleanupService2 : IProjectSystemReferenceCleanupService + { + /// + /// Gets an operation that can update the project’s references by removing or marking references as + /// TreatAsUsed in the project file. + /// + Task GetUpdateReferenceOperationAsync( + string projectPath, + ProjectSystemReferenceUpdate referenceUpdate, + CancellationToken canellationToken); + } +} diff --git a/src/VisualStudio/Core/Def/ExternalAccess/ProjectSystem/Api/IProjectSystemUpdateReferenceOperation.cs b/src/VisualStudio/Core/Def/ExternalAccess/ProjectSystem/Api/IProjectSystemUpdateReferenceOperation.cs new file mode 100644 index 0000000000000..0d685d5f0f51e --- /dev/null +++ b/src/VisualStudio/Core/Def/ExternalAccess/ProjectSystem/Api/IProjectSystemUpdateReferenceOperation.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.LanguageServices.ExternalAccess.ProjectSystem.Api +{ + internal interface IProjectSystemUpdateReferenceOperation + { + /// + /// Applies a reference update operation to the project file. + /// + /// A boolean indicating success. + /// Throws if operation has already been applied. + Task ApplyAsync(CancellationToken cancellationToken); + + /// + /// Reverts a reference update operation to the project file. + /// + /// A boolean indicating success. + /// Throws if operation has not been applied. + Task RevertAsync(CancellationToken cancellationToken); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs b/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs index 79afe2a0db2a6..52840f2001a98 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs @@ -11,8 +11,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.FileHeaders; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -275,12 +273,14 @@ int IVsEditorFactoryNotify.NotifyItemAdded(uint grfEFN, IVsHierarchy pHier, uint // Is this being added from a template? if (((__EFNFLAGS)grfEFN & __EFNFLAGS.EFN_ClonedFromTemplate) != 0) { - var waitIndicator = _componentModel.GetService(); + var uiThreadOperationExecutor = _componentModel.GetService(); // TODO(cyrusn): Can this be cancellable? - waitIndicator.Wait( + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, - action: c => FormatDocumentCreatedFromTemplate(pHier, itemid, pszMkDocument, c.CancellationToken)); + defaultDescription: "", + allowCancellation: false, + showProgress: false, + action: c => FormatDocumentCreatedFromTemplate(pHier, itemid, pszMkDocument, c.UserCancellationToken)); } return VSConstants.S_OK; diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs b/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs index 4cd19fa954375..afc4b032d4ebd 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs @@ -22,6 +22,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; @@ -89,19 +90,19 @@ protected int GetDataTipTextImpl(ITextBuffer subjectBuffer, TextSpan[] pSpan, ou var result = VSConstants.E_FAIL; string pbstrTextInternal = null; - var waitIndicator = ComponentModel.GetService(); - - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( title: ServicesVSResources.Debugger, - message: ServicesVSResources.Getting_DataTip_text, - allowCancel: true, - action: waitContext => + defaultDescription: ServicesVSResources.Getting_DataTip_text, + allowCancellation: true, + showProgress: false, + action: context => { IServiceProvider serviceProvider = ComponentModel.GetService(); var debugger = (IVsDebugger)serviceProvider.GetService(typeof(SVsShellDebugger)); var debugMode = new DBGMODE[1]; - var cancellationToken = waitContext.CancellationToken; + var cancellationToken = context.UserCancellationToken; if (ErrorHandler.Succeeded(debugger.GetMode(debugMode)) && debugMode[0] != DBGMODE.DBGMODE_Design) { var textSpan = pSpan[0]; @@ -142,10 +143,12 @@ int IVsTextViewFilter.GetPairExtents(int iLine, int iIndex, TextSpan[] pSpan) try { var result = VSConstants.S_OK; - ComponentModel.GetService().Wait( + ComponentModel.GetService().Execute( "Intellisense", - allowCancel: true, - action: c => result = GetPairExtentsWorker(iLine, iIndex, pSpan, c.CancellationToken)); + defaultDescription: "", + allowCancellation: true, + showProgress: false, + action: c => result = GetPairExtentsWorker(iLine, iIndex, pSpan, c.UserCancellationToken)); return result; } diff --git a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs index 84518aadbb130..1cac2751613af 100644 --- a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs +++ b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs @@ -114,12 +114,7 @@ private IImmutableSet IncludeDocuments(CallHierarchySearchScope scope, { if (scope == CallHierarchySearchScope.CurrentDocument || scope == CallHierarchySearchScope.CurrentProject) { - var documentTrackingService = project.Solution.Workspace.Services.GetService(); - if (documentTrackingService == null) - { - return null; - } - + var documentTrackingService = project.Solution.Workspace.Services.GetRequiredService(); var activeDocument = documentTrackingService.TryGetActiveDocument(); if (activeDocument != null) { diff --git a/src/VisualStudio/Core/Def/Implementation/CommonControls/MemberSelectionViewModel.cs b/src/VisualStudio/Core/Def/Implementation/CommonControls/MemberSelectionViewModel.cs index 80a937f925d46..f87317a6c46a7 100644 --- a/src/VisualStudio/Core/Def/Implementation/CommonControls/MemberSelectionViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/CommonControls/MemberSelectionViewModel.cs @@ -4,7 +4,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; @@ -15,23 +14,24 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.CommonControls { internal class MemberSelectionViewModel : AbstractNotifyPropertyChanged { - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly ImmutableDictionary>> _symbolToDependentsMap; private readonly ImmutableDictionary _symbolToMemberViewMap; public MemberSelectionViewModel( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ImmutableArray members, ImmutableDictionary>> dependentsMap, TypeKind destinationTypeKind = TypeKind.Class) { - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; // Use public property to hook property change events up Members = members; _symbolToDependentsMap = dependentsMap; @@ -92,20 +92,20 @@ public void SelectDependents() var checkedMembers = Members .WhereAsArray(member => member.IsChecked && member.IsCheckable); - var waitResult = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: ServicesVSResources.Pull_Members_Up, - message: ServicesVSResources.Calculating_dependents, - allowCancel: true, + defaultDescription: ServicesVSResources.Calculating_dependents, + allowCancellation: true, showProgress: true, context => { foreach (var member in Members) { - _symbolToDependentsMap[member.Symbol].Wait(context.CancellationToken); + _symbolToDependentsMap[member.Symbol].Wait(context.UserCancellationToken); } }); - if (waitResult == WaitIndicatorResult.Completed) + if (result == UIThreadOperationStatus.Completed) { foreach (var member in checkedMembers) { diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs index 23438f00e9fbc..560d58f054f1e 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; @@ -52,6 +53,9 @@ public ManagedEditAndContinueLanguageService( _diagnosticUpdateSource = diagnosticUpdateSource; } + private Solution GetCurrentCompileTimeSolution() + => _proxy.Workspace.Services.GetRequiredService().GetCompileTimeSolution(_proxy.Workspace.CurrentSolution); + /// /// Called by the debugger when a debugging session starts and managed debugging is being used. /// @@ -67,7 +71,7 @@ public async Task StartDebuggingAsync(DebugSessionFlags flags, CancellationToken try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); _debuggingSessionConnection = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) @@ -85,7 +89,7 @@ public async Task EnterBreakStateAsync(CancellationToken cancellationToken) return; } - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); try { @@ -165,8 +169,8 @@ public async Task StopDebuggingAsync(CancellationToken cancellationToken) } } - private SolutionActiveStatementSpanProvider GetActiveStatementSpanProvider(Solution solution) - => new((documentId, cancellationToken) => _activeStatementTrackingService.GetSpansAsync(solution.GetRequiredDocument(documentId), cancellationToken)); + private ActiveStatementSpanProvider GetActiveStatementSpanProvider(Solution solution) + => new((documentId, filePath, cancellationToken) => _activeStatementTrackingService.GetSpansAsync(solution, documentId, filePath, cancellationToken)); /// /// Returns true if any changes have been made to the source since the last changes had been applied. @@ -175,7 +179,7 @@ public async Task HasChangesAsync(string? sourceFilePath, CancellationToke { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); return await _proxy.HasChangesAsync(solution, activeStatementSpanProvider, sourceFilePath, cancellationToken).ConfigureAwait(false); } @@ -189,9 +193,10 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); - return await _proxy.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + var (updates, _, _) = await _proxy.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + return updates; } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -203,13 +208,10 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); - var activeStatementSpanProvider = new SolutionActiveStatementSpanProvider(async (documentId, cancellationToken) => - { - var document = solution.GetRequiredDocument(documentId); - return await _activeStatementTrackingService.GetSpansAsync(document, cancellationToken).ConfigureAwait(false); - }); + var activeStatementSpanProvider = new ActiveStatementSpanProvider((documentId, filePath, cancellationToken) => + _activeStatementTrackingService.GetSpansAsync(solution, documentId, filePath, cancellationToken)); var span = await _proxy.GetCurrentActiveStatementPositionAsync(solution, activeStatementSpanProvider, instruction, cancellationToken).ConfigureAwait(false); return span?.ToSourceSpan(); @@ -224,7 +226,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); return await _proxy.IsActiveStatementInExceptionRegionAsync(solution, instruction, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs new file mode 100644 index 0000000000000..6004bc91566ed --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.EditAndContinue +{ + [Shared] + [Export(typeof(IManagedHotReloadLanguageService))] + [ExportMetadata("UIContext", Guids.EncCapableProjectExistsInWorkspaceUIContextString)] + internal sealed class ManagedHotReloadLanguageService : IManagedHotReloadLanguageService + { + private sealed class DebuggerService : IManagedEditAndContinueDebuggerService + { + private readonly IManagedHotReloadService _hotReloadService; + + public DebuggerService(IManagedHotReloadService hotReloadService) + { + _hotReloadService = hotReloadService; + } + + public Task> GetActiveStatementsAsync(CancellationToken cancellationToken) + => Task.FromResult(ImmutableArray.Empty); + + public Task GetAvailabilityAsync(Guid module, CancellationToken cancellationToken) + => Task.FromResult(new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.Available)); + + public Task> GetCapabilitiesAsync(CancellationToken cancellationToken) + => _hotReloadService.GetCapabilitiesAsync(cancellationToken).AsTask(); + + public Task PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) + => Task.CompletedTask; + } + + private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = + (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); + + private readonly RemoteEditAndContinueServiceProxy _proxy; + private readonly IDiagnosticAnalyzerService _diagnosticService; + private readonly EditAndContinueDiagnosticUpdateSource _diagnosticUpdateSource; + private readonly DebuggerService _debuggerService; + + private IDisposable? _debuggingSessionConnection; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ManagedHotReloadLanguageService( + VisualStudioWorkspace workspace, + IManagedHotReloadService hotReloadService, + IDiagnosticAnalyzerService diagnosticService, + EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource) + { + _proxy = new RemoteEditAndContinueServiceProxy(workspace); + _debuggerService = new DebuggerService(hotReloadService); + _diagnosticService = diagnosticService; + _diagnosticUpdateSource = diagnosticUpdateSource; + } + + public async ValueTask StartSessionAsync(CancellationToken cancellationToken) + { + try + { + var solution = _proxy.Workspace.CurrentSolution; + _debuggingSessionConnection = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask GetUpdatesAsync(CancellationToken cancellationToken) + { + try + { + var solution = _proxy.Workspace.CurrentSolution; + var (moduleUpdates, diagnosticData, rudeEdits) = await _proxy.EmitSolutionUpdateAsync(solution, s_solutionActiveStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + + var updates = moduleUpdates.Updates.SelectAsArray( + update => new ManagedHotReloadUpdate(update.Module, update.ILDelta, update.MetadataDelta)); + + var diagnostics = await EmitSolutionUpdateResults.GetHotReloadDiagnosticsAsync(solution, diagnosticData, rudeEdits, cancellationToken).ConfigureAwait(false); + + return new ManagedHotReloadUpdates(updates, diagnostics); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(RudeEditKind.InternalError); + + // TODO: better error + var diagnostic = new ManagedHotReloadDiagnostic( + descriptor.Id, + string.Format(descriptor.MessageFormat.ToString(), "", e.Message), + ManagedHotReloadDiagnosticSeverity.Error, + filePath: "", + span: default); + + return new ManagedHotReloadUpdates(ImmutableArray.Empty, ImmutableArray.Create(diagnostic)); + } + } + + public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) + { + try + { + await _proxy.CommitSolutionUpdateAsync(_diagnosticService, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) + { + try + { + await _proxy.DiscardSolutionUpdateAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + public async ValueTask EndSessionAsync(CancellationToken cancellationToken) + { + try + { + await _proxy.EndDebuggingSessionAsync(_diagnosticUpdateSource, _diagnosticService, cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfNull(_debuggingSessionConnection); + _debuggingSessionConnection.Dispose(); + _debuggingSessionConnection = null; + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ExtractClass/ExtractClassViewModel.cs b/src/VisualStudio/Core/Def/Implementation/ExtractClass/ExtractClassViewModel.cs index f19f67ddbc8c6..14feee4805406 100644 --- a/src/VisualStudio/Core/Def/Implementation/ExtractClass/ExtractClassViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/ExtractClass/ExtractClassViewModel.cs @@ -5,11 +5,11 @@ using System.Collections.Immutable; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Notification; using Microsoft.VisualStudio.LanguageServices.Implementation.CommonControls; using Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ExtractClass { @@ -18,7 +18,7 @@ internal class ExtractClassViewModel private readonly INotificationService _notificationService; public ExtractClassViewModel( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, INotificationService notificationService, ImmutableArray memberViewModels, ImmutableDictionary>> memberToDependentsMap, @@ -32,7 +32,7 @@ public ExtractClassViewModel( _notificationService = notificationService; MemberSelectionViewModel = new MemberSelectionViewModel( - waitIndicator, + uiThreadOperationExecutor, memberViewModels, memberToDependentsMap, destinationTypeKind: TypeKind.Class); diff --git a/src/VisualStudio/Core/Def/Implementation/ExtractClass/VisualStudioExtractClassOptionsService.cs b/src/VisualStudio/Core/Def/Implementation/ExtractClass/VisualStudioExtractClassOptionsService.cs index e3241def19a09..47956b2589d6f 100644 --- a/src/VisualStudio/Core/Def/Implementation/ExtractClass/VisualStudioExtractClassOptionsService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ExtractClass/VisualStudioExtractClassOptionsService.cs @@ -9,11 +9,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ExtractClass; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Notification; @@ -23,6 +20,7 @@ using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp; using Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ExtractClass @@ -32,18 +30,18 @@ internal class VisualStudioExtractClassOptionsService : IExtractClassOptionsServ { private readonly IThreadingContext _threadingContext; private readonly IGlyphService _glyphService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioExtractClassOptionsService( IThreadingContext threadingContext, IGlyphService glyphService, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uiThreadOperationExecutor) { _threadingContext = threadingContext; _glyphService = glyphService; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public async Task GetExtractClassOptionsAsync(Document document, INamedTypeSymbol selectedType, ISymbol? selectedMember) @@ -78,7 +76,7 @@ public VisualStudioExtractClassOptionsService( var generatedNameTypeParameterSuffix = ExtractTypeHelpers.GetTypeParameterSuffix(document, selectedType, membersInType); var viewModel = new ExtractClassViewModel( - _waitIndicator, + _uiThreadOperationExecutor, notificationService, memberViewModels, memberToDependentsMap, diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index 962439e04a93c..1ec25628cadec 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.DocumentHighlighting; +using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; @@ -29,7 +30,23 @@ internal partial class StreamingFindUsagesPresenter private abstract class AbstractTableDataSourceFindUsagesContext : FindUsagesContext, ITableDataSource, ITableEntriesSnapshotFactory { - private CancellationTokenSource? _cancellationTokenSource; + /// + /// Cancellation token we own that we will trigger if the presenter for this particular + /// search is either closed, or repurposed to show results from another search. Clients + /// using the should use this token if they + /// are populating the presenter in a fire-and-forget manner. In other words if they kick + /// off work to compute the results that they themselves are not waiting on. If they are + /// *not* kickign off work in a fire-and-forget manner, and are instead populating the + /// presenter on their own thread, they should have their own cancellation token (for example + /// backed by a threaded-wait-dialog or CommandExecutionContext) that controls their scenario + /// which a client can use to cancel that work. + /// + /// + /// Importantly, no code in this context or the presenter should actually examine this token + /// to see if their work is cancelled. Instead, any cancellable work should have a cancellation + /// token passed in from the caller that should be used instead. + /// + public readonly CancellationTokenSource CancellationTokenSource = new(); private ITableDataSink _tableDataSink; @@ -85,23 +102,15 @@ private abstract class AbstractTableDataSourceFindUsagesContext : #endregion - public sealed override CancellationToken CancellationToken { get; } - protected AbstractTableDataSourceFindUsagesContext( StreamingFindUsagesPresenter presenter, IFindAllReferencesWindow findReferencesWindow, ImmutableArray customColumns, bool includeContainingTypeAndMemberColumns, - bool includeKindColumn, - CancellationToken cancellationToken) + bool includeKindColumn) { presenter.AssertIsForeground(); - // Wrap the passed in CT with our own CTS that we can control cancellation over. This way either our - // caller can cancel our work or we can cancel the work. - _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - CancellationToken = _cancellationTokenSource.Token; - Presenter = presenter; _findReferencesWindow = findReferencesWindow; TableControl = (IWpfTableControl2)findReferencesWindow.TableControl; @@ -134,7 +143,7 @@ protected AbstractTableDataSourceFindUsagesContext( _progressQueue = new AsyncBatchingWorkQueue<(int current, int maximum)>( TimeSpan.FromMilliseconds(250), this.UpdateTableProgressAsync, - this.CancellationToken); + CancellationTokenSource.Token); } private static ImmutableArray SelectCustomColumnsToInclude(ImmutableArray customColumns, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) @@ -227,13 +236,9 @@ private void CancelSearch() { Presenter.AssertIsForeground(); - // Cancel any in flight find work that is going on. - if (_cancellationTokenSource != null) - { - _cancellationTokenSource.Cancel(); - _cancellationTokenSource.Dispose(); - _cancellationTokenSource = null; - } + // Cancel any in flight find work that is going on. Note: disposal happens in our own + // implementation of IDisposable.Dispose. + CancellationTokenSource.Cancel(); } public void Clear() @@ -289,75 +294,57 @@ public IDisposable Subscribe(ITableDataSink sink) #region FindUsagesContext overrides. - public sealed override ValueTask SetSearchTitleAsync(string title) + public sealed override ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) { // Note: IFindAllReferenceWindow.Title is safe to set from any thread. _findReferencesWindow.Title = title; return default; } - public sealed override async ValueTask OnCompletedAsync() + public sealed override async ValueTask OnCompletedAsync(CancellationToken cancellationToken) { - await OnCompletedAsyncWorkerAsync().ConfigureAwait(false); + await OnCompletedAsyncWorkerAsync(cancellationToken).ConfigureAwait(false); _tableDataSink.IsStable = true; } - protected abstract Task OnCompletedAsyncWorkerAsync(); + protected abstract Task OnCompletedAsyncWorkerAsync(CancellationToken cancellationToken); - public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definition) + public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { lock (Gate) { Definitions.Add(definition); } - return OnDefinitionFoundWorkerAsync(definition); + return OnDefinitionFoundWorkerAsync(definition, cancellationToken); } - protected abstract ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition); - - protected async Task<(Guid, string projectName, SourceText)> GetGuidAndProjectNameAndSourceTextAsync(Document document) - { - // The FAR system needs to know the guid for the project that a def/reference is - // from (to support features like filtering). Normally that would mean we could - // only support this from a VisualStudioWorkspace. However, we want till work - // in cases like Any-Code (which does not use a VSWorkspace). So we are tolerant - // when we have another type of workspace. This means we will show results, but - // certain features (like filtering) may not work in that context. - var vsWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspace; - - var projectName = document.Project.Name; - var guid = vsWorkspace?.GetProjectGuid(document.Project.Id) ?? Guid.Empty; - - var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false); - return (guid, projectName, sourceText); - } + protected abstract ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition, CancellationToken cancellationToken); protected async Task TryCreateDocumentSpanEntryAsync( RoslynDefinitionBucket definitionBucket, DocumentSpan documentSpan, HighlightSpanKind spanKind, SymbolUsageInfo symbolUsageInfo, - ImmutableDictionary additionalProperties) + ImmutableDictionary additionalProperties, + CancellationToken cancellationToken) { - var document = documentSpan.Document; - var (guid, projectName, sourceText) = await GetGuidAndProjectNameAndSourceTextAsync(document).ConfigureAwait(false); - var (excerptResult, lineText) = await ExcerptAsync(sourceText, documentSpan).ConfigureAwait(false); + var sourceText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var (excerptResult, lineText) = await ExcerptAsync(sourceText, documentSpan, cancellationToken).ConfigureAwait(false); - var mappedDocumentSpan = await AbstractDocumentSpanEntry.TryMapAndGetFirstAsync(documentSpan, sourceText, CancellationToken).ConfigureAwait(false); + var mappedDocumentSpan = await AbstractDocumentSpanEntry.TryMapAndGetFirstAsync(documentSpan, sourceText, cancellationToken).ConfigureAwait(false); if (mappedDocumentSpan == null) { // this will be removed from the result return null; } - return new DocumentSpanEntry( + return DocumentSpanEntry.TryCreate( this, definitionBucket, + documentSpan, spanKind, - projectName, - guid, mappedDocumentSpan.Value, excerptResult, lineText, @@ -365,19 +352,20 @@ public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definitio additionalProperties); } - private async Task<(ExcerptResult, SourceText)> ExcerptAsync(SourceText sourceText, DocumentSpan documentSpan) + private async Task<(ExcerptResult, SourceText)> ExcerptAsync( + SourceText sourceText, DocumentSpan documentSpan, CancellationToken cancellationToken) { var excerptService = documentSpan.Document.Services.GetService(); if (excerptService != null) { - var result = await excerptService.TryExcerptAsync(documentSpan.Document, documentSpan.SourceSpan, ExcerptMode.SingleLine, CancellationToken).ConfigureAwait(false); + var result = await excerptService.TryExcerptAsync(documentSpan.Document, documentSpan.SourceSpan, ExcerptMode.SingleLine, cancellationToken).ConfigureAwait(false); if (result != null) { return (result.Value, AbstractDocumentSpanEntry.GetLineContainingPosition(result.Value.Content, result.Value.MappedSpan.Start)); } } - var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, CancellationToken).ConfigureAwait(false); + var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, cancellationToken).ConfigureAwait(false); // need to fix the span issue tracking here - https://github.com/dotnet/roslyn/issues/31001 var excerptResult = new ExcerptResult( @@ -390,10 +378,10 @@ public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definitio return (excerptResult, AbstractDocumentSpanEntry.GetLineContainingPosition(sourceText, documentSpan.SourceSpan.Start)); } - public sealed override ValueTask OnReferenceFoundAsync(SourceReferenceItem reference) - => OnReferenceFoundWorkerAsync(reference); + public sealed override ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) + => OnReferenceFoundWorkerAsync(reference, cancellationToken); - protected abstract ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem reference); + protected abstract ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem reference, CancellationToken cancellationToken); protected RoslynDefinitionBucket GetOrCreateDefinitionBucket(DefinitionItem definition, bool expandedByDefault) { @@ -409,16 +397,16 @@ protected RoslynDefinitionBucket GetOrCreateDefinitionBucket(DefinitionItem defi } } - public sealed override ValueTask ReportMessageAsync(string message) + public sealed override ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken) => throw new InvalidOperationException("This should never be called in the streaming case."); - protected sealed override ValueTask ReportProgressAsync(int current, int maximum) + protected sealed override ValueTask ReportProgressAsync(int current, int maximum, CancellationToken cancellationToken) { _progressQueue.AddWork((current, maximum)); return default; } - private Task UpdateTableProgressAsync(ImmutableArray<(int current, int maximum)> nextBatch, CancellationToken cancellationToken) + private Task UpdateTableProgressAsync(ImmutableArray<(int current, int maximum)> nextBatch, CancellationToken _) { if (!nextBatch.IsEmpty) { @@ -503,10 +491,11 @@ void IDisposable.Dispose() _findReferencesWindow.Manager.RemoveSource(this); - CancelSearch(); - // Remove ourselves from the list of contexts that are currently active. Presenter._currentContexts.Remove(this); + + CancelSearch(); + CancellationTokenSource.Dispose(); } #endregion diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs index 1ce447931d99b..d4b3be1456835 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; using Roslyn.Utilities; @@ -32,24 +33,23 @@ public WithReferencesFindUsagesContext( IFindAllReferencesWindow findReferencesWindow, ImmutableArray customColumns, bool includeContainingTypeAndMemberColumns, - bool includeKindColumn, - CancellationToken cancellationToken) - : base(presenter, findReferencesWindow, customColumns, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken) + bool includeKindColumn) + : base(presenter, findReferencesWindow, customColumns, includeContainingTypeAndMemberColumns, includeKindColumn) { } - protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition) + protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition, CancellationToken cancellationToken) { // If this is a definition we always want to show, then create entries for all the declaration locations // immediately. Otherwise, we'll create them on demand when we hear about references for this // definition. if (definition.DisplayIfNoReferences) - await AddDeclarationEntriesAsync(definition, expandedByDefault: true).ConfigureAwait(false); + await AddDeclarationEntriesAsync(definition, expandedByDefault: true, cancellationToken).ConfigureAwait(false); } - private async Task AddDeclarationEntriesAsync(DefinitionItem definition, bool expandedByDefault) + private async Task AddDeclarationEntriesAsync(DefinitionItem definition, bool expandedByDefault, CancellationToken cancellationToken) { - CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); // Don't do anything if we already have declaration entries for this definition // (i.e. another thread beat us to this). @@ -64,11 +64,14 @@ private async Task AddDeclarationEntriesAsync(DefinitionItem definition, bool ex // lock, and I'd like to avoid that. That does mean that we might do extra // work if multiple threads end up down this path. But only one of them will // win when we access the lock below. - using var _ = ArrayBuilder.GetInstance(out var declarations); + + using var _1 = ArrayBuilder.GetInstance(out var declarations); + using var _2 = PooledHashSet<(string? filePath, TextSpan span)>.GetInstance(out var seenLocations); foreach (var declarationLocation in definition.SourceSpans) { var definitionEntry = await TryCreateDocumentSpanEntryAsync( - definitionBucket, declarationLocation, HighlightSpanKind.Definition, SymbolUsageInfo.None, additionalProperties: definition.DisplayableProperties).ConfigureAwait(false); + definitionBucket, declarationLocation, HighlightSpanKind.Definition, SymbolUsageInfo.None, + additionalProperties: definition.DisplayableProperties, cancellationToken).ConfigureAwait(false); declarations.AddIfNotNull(definitionEntry); } @@ -102,7 +105,7 @@ private bool HasDeclarationEntries(DefinitionItem definition) } } - protected override ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem reference) + protected override ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem reference, CancellationToken cancellationToken) { // Normal references go into both sets of entries. We ensure an entry for the definition, and an entry // for the reference itself. @@ -112,46 +115,48 @@ protected override ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem ref bucket, reference.SourceSpan, reference.IsWrittenTo ? HighlightSpanKind.WrittenReference : HighlightSpanKind.Reference, reference.SymbolUsageInfo, - reference.AdditionalProperties), + reference.AdditionalProperties, + cancellationToken), addToEntriesWhenGroupingByDefinition: true, - addToEntriesWhenNotGroupingByDefinition: true); + addToEntriesWhenNotGroupingByDefinition: true, + expandedByDefault: true, + cancellationToken); } - protected async ValueTask OnEntryFoundAsync( + private async ValueTask OnEntryFoundAsync( DefinitionItem definition, Func> createEntryAsync, bool addToEntriesWhenGroupingByDefinition, bool addToEntriesWhenNotGroupingByDefinition, - bool expandedByDefault = true) + bool expandedByDefault, + CancellationToken cancellationToken) { Debug.Assert(addToEntriesWhenGroupingByDefinition || addToEntriesWhenNotGroupingByDefinition); - CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); // OK, we got a *reference* to some definition item. This may have been a reference for some definition // that we haven't created any declaration entries for (i.e. because it had DisplayIfNoReferences = // false). Because we've now found a reference, we want to make sure all its declaration entries are // added. - await AddDeclarationEntriesAsync(definition, expandedByDefault).ConfigureAwait(false); + await AddDeclarationEntriesAsync(definition, expandedByDefault, cancellationToken).ConfigureAwait(false); // First find the bucket corresponding to our definition. var definitionBucket = GetOrCreateDefinitionBucket(definition, expandedByDefault); var entry = await createEntryAsync(definitionBucket).ConfigureAwait(false); - if (entry == null) - { - return; - } + + // Proceed, even if we didn't create an entry. It's possible that we augmented + // an existing entry and we want the UI to refresh to show the results of that. lock (Gate) { - // Once we can make the new entry, add it to the appropriate list. - if (addToEntriesWhenGroupingByDefinition) + if (entry != null) { - EntriesWhenGroupingByDefinition = EntriesWhenGroupingByDefinition.Add(entry); - } + // Once we can make the new entry, add it to the appropriate list. + if (addToEntriesWhenGroupingByDefinition) + EntriesWhenGroupingByDefinition = EntriesWhenGroupingByDefinition.Add(entry); - if (addToEntriesWhenNotGroupingByDefinition) - { - EntriesWhenNotGroupingByDefinition = EntriesWhenNotGroupingByDefinition.Add(entry); + if (addToEntriesWhenNotGroupingByDefinition) + EntriesWhenNotGroupingByDefinition = EntriesWhenNotGroupingByDefinition.Add(entry); } CurrentVersionNumber++; @@ -161,22 +166,22 @@ protected async ValueTask OnEntryFoundAsync( NotifyChange(); } - protected override async Task OnCompletedAsyncWorkerAsync() + protected override async Task OnCompletedAsyncWorkerAsync(CancellationToken cancellationToken) { // Now that we know the search is over, create and display any error messages // for definitions that were not found. - await CreateMissingReferenceEntriesIfNecessaryAsync().ConfigureAwait(false); - await CreateNoResultsFoundEntryIfNecessaryAsync().ConfigureAwait(false); + await CreateMissingReferenceEntriesIfNecessaryAsync(cancellationToken).ConfigureAwait(false); + await CreateNoResultsFoundEntryIfNecessaryAsync(cancellationToken).ConfigureAwait(false); } - private async Task CreateMissingReferenceEntriesIfNecessaryAsync() + private async Task CreateMissingReferenceEntriesIfNecessaryAsync(CancellationToken cancellationToken) { - await CreateMissingReferenceEntriesIfNecessaryAsync(whenGroupingByDefinition: true).ConfigureAwait(false); - await CreateMissingReferenceEntriesIfNecessaryAsync(whenGroupingByDefinition: false).ConfigureAwait(false); + await CreateMissingReferenceEntriesIfNecessaryAsync(whenGroupingByDefinition: true, cancellationToken).ConfigureAwait(false); + await CreateMissingReferenceEntriesIfNecessaryAsync(whenGroupingByDefinition: false, cancellationToken).ConfigureAwait(false); } private async Task CreateMissingReferenceEntriesIfNecessaryAsync( - bool whenGroupingByDefinition) + bool whenGroupingByDefinition, CancellationToken cancellationToken) { // Go through and add dummy entries for any definitions that // that we didn't find any references for. @@ -189,7 +194,9 @@ private async Task CreateMissingReferenceEntriesIfNecessaryAsync( await OnEntryFoundAsync(definition, bucket => SimpleMessageEntry.CreateAsync(bucket, bucket, ServicesVSResources.External_reference_found)!, addToEntriesWhenGroupingByDefinition: whenGroupingByDefinition, - addToEntriesWhenNotGroupingByDefinition: !whenGroupingByDefinition).ConfigureAwait(false); + addToEntriesWhenNotGroupingByDefinition: !whenGroupingByDefinition, + expandedByDefault: true, + cancellationToken).ConfigureAwait(false); } else { @@ -204,7 +211,8 @@ await OnEntryFoundAsync(SymbolsWithoutReferencesDefinitionItem, string.Format(ServicesVSResources.No_references_found_to_0, definition.NameDisplayParts.JoinText()))!, addToEntriesWhenGroupingByDefinition: whenGroupingByDefinition, addToEntriesWhenNotGroupingByDefinition: !whenGroupingByDefinition, - expandedByDefault: false).ConfigureAwait(false); + expandedByDefault: false, + cancellationToken).ConfigureAwait(false); } } } @@ -247,7 +255,7 @@ private ImmutableArray GetDefinitionsToCreateMissingReferenceIte } } - private async Task CreateNoResultsFoundEntryIfNecessaryAsync() + private async Task CreateNoResultsFoundEntryIfNecessaryAsync(CancellationToken cancellationToken) { bool noDefinitions; lock (Gate) @@ -261,7 +269,9 @@ private async Task CreateNoResultsFoundEntryIfNecessaryAsync() await OnEntryFoundAsync(NoResultsDefinitionItem, bucket => SimpleMessageEntry.CreateAsync(bucket, null, ServicesVSResources.Search_found_no_results)!, addToEntriesWhenGroupingByDefinition: true, - addToEntriesWhenNotGroupingByDefinition: true).ConfigureAwait(false); + addToEntriesWhenNotGroupingByDefinition: true, + expandedByDefault: true, + cancellationToken).ConfigureAwait(false); } } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs index 8380f1718b23e..af994b360b8a7 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs @@ -29,21 +29,20 @@ public WithoutReferencesFindUsagesContext( IFindAllReferencesWindow findReferencesWindow, ImmutableArray customColumns, bool includeContainingTypeAndMemberColumns, - bool includeKindColumn, - CancellationToken cancellationToken) - : base(presenter, findReferencesWindow, customColumns, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken) + bool includeKindColumn) + : base(presenter, findReferencesWindow, customColumns, includeContainingTypeAndMemberColumns, includeKindColumn) { } // We should never be called in a context where we get references. - protected override ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem reference) + protected override ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem reference, CancellationToken cancellationToken) => throw new InvalidOperationException(); // Nothing to do on completion. - protected override Task OnCompletedAsyncWorkerAsync() + protected override Task OnCompletedAsyncWorkerAsync(CancellationToken cancellationToken) => Task.CompletedTask; - protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition) + protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition, CancellationToken cancellationToken) { var definitionBucket = GetOrCreateDefinitionBucket(definition, expandedByDefault: true); @@ -55,7 +54,7 @@ protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem d // definition as what to show. That way we show enough information for things // methods. i.e. we'll show "void TypeName.MethodName(args...)" allowing // the user to see the type the method was created in. - var entry = await TryCreateEntryAsync(definitionBucket, definition).ConfigureAwait(false); + var entry = await TryCreateEntryAsync(definitionBucket, definition, cancellationToken).ConfigureAwait(false); entries.AddIfNotNull(entry); } else if (definition.SourceSpans.Length == 0) @@ -77,8 +76,8 @@ protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem d sourceSpan, HighlightSpanKind.Definition, symbolUsageInfo: SymbolUsageInfo.None, - additionalProperties: definition.DisplayableProperties) - .ConfigureAwait(false); + additionalProperties: definition.DisplayableProperties, + cancellationToken).ConfigureAwait(false); entries.AddIfNotNull(entry); } } @@ -96,13 +95,14 @@ protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem d } private async Task TryCreateEntryAsync( - RoslynDefinitionBucket definitionBucket, DefinitionItem definition) + RoslynDefinitionBucket definitionBucket, DefinitionItem definition, CancellationToken cancellationToken) { var documentSpan = definition.SourceSpans[0]; - var (guid, projectName, sourceText) = await GetGuidAndProjectNameAndSourceTextAsync(documentSpan.Document).ConfigureAwait(false); + var (guid, projectName, _) = GetGuidAndProjectInfo(documentSpan.Document); + var sourceText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); var lineText = AbstractDocumentSpanEntry.GetLineContainingPosition(sourceText, documentSpan.SourceSpan.Start); - var mappedDocumentSpan = await AbstractDocumentSpanEntry.TryMapAndGetFirstAsync(documentSpan, sourceText, CancellationToken).ConfigureAwait(false); + var mappedDocumentSpan = await AbstractDocumentSpanEntry.TryMapAndGetFirstAsync(documentSpan, sourceText, cancellationToken).ConfigureAwait(false); if (mappedDocumentSpan == null) { // this will be removed from the result diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs index 39f065ff9731a..b047a5fa182fe 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs @@ -17,12 +17,11 @@ internal partial class StreamingFindUsagesPresenter { /// /// Base type of all s that represent some source location in - /// a . Navigation to that location is provided by this type. + /// a . Navigation to that location is provided by this type. /// Subclasses can be used to provide customized line text to display in the entry. /// private abstract class AbstractDocumentSpanEntry : AbstractItemEntry { - private readonly string _projectName; private readonly object _boxedProjectGuid; private readonly SourceText _lineText; @@ -31,26 +30,26 @@ private abstract class AbstractDocumentSpanEntry : AbstractItemEntry protected AbstractDocumentSpanEntry( AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, - string projectName, Guid projectGuid, SourceText lineText, MappedSpanResult mappedSpanResult) : base(definitionBucket, context.Presenter) { - _projectName = projectName; _boxedProjectGuid = projectGuid; _lineText = lineText; _mappedSpanResult = mappedSpanResult; } + protected abstract string GetProjectName(); + protected override object? GetValueWorker(string keyName) => keyName switch { StandardTableKeyNames.DocumentName => _mappedSpanResult.FilePath, StandardTableKeyNames.Line => _mappedSpanResult.LinePositionSpan.Start.Line, StandardTableKeyNames.Column => _mappedSpanResult.LinePositionSpan.Start.Character, - StandardTableKeyNames.ProjectName => _projectName, + StandardTableKeyNames.ProjectName => GetProjectName(), StandardTableKeyNames.ProjectGuid => _boxedProjectGuid, StandardTableKeyNames.Text => _lineText.ToString().Trim(), _ => null, diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs index 972c15de286f0..109a9fd0db4c5 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs @@ -20,17 +20,23 @@ internal partial class StreamingFindUsagesPresenter /// private class DefinitionItemEntry : AbstractDocumentSpanEntry { + private readonly string _projectName; + public DefinitionItemEntry( AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, - string documentName, + string projectName, Guid projectGuid, SourceText lineText, MappedSpanResult mappedSpanResult) - : base(context, definitionBucket, documentName, projectGuid, lineText, mappedSpanResult) + : base(context, definitionBucket, projectGuid, lineText, mappedSpanResult) { + _projectName = projectName; } + protected override string GetProjectName() + => _projectName; + protected override IList CreateLineTextInlines() => DefinitionBucket.DefinitionItem.DisplayParts.ToInlines(Presenter.ClassificationFormatMap, Presenter.TypeMap); } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index bd869293347ba..71a1bf7ab6966 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -41,28 +41,102 @@ private class DocumentSpanEntry : AbstractDocumentSpanEntry, ISupportsNavigation private readonly SymbolReferenceKinds _symbolReferenceKinds; private readonly ImmutableDictionary _customColumnsData; - public DocumentSpanEntry( + private readonly string _rawProjectName; + private readonly List _projectFlavors = new(); + + private string? _cachedProjectName; + + private DocumentSpanEntry( AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, - HighlightSpanKind spanKind, - string documentName, + string rawProjectName, + string? projectFlavor, Guid projectGuid, + HighlightSpanKind spanKind, MappedSpanResult mappedSpanResult, ExcerptResult excerptResult, SourceText lineText, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary customColumnsData) - : base(context, - definitionBucket, - documentName, - projectGuid, - lineText, - mappedSpanResult) + : base(context, definitionBucket, projectGuid, lineText, mappedSpanResult) { _spanKind = spanKind; _excerptResult = excerptResult; _symbolReferenceKinds = symbolUsageInfo.ToSymbolReferenceKinds(); _customColumnsData = customColumnsData; + + _rawProjectName = rawProjectName; + this.AddFlavor(projectFlavor); + } + + protected override string GetProjectName() + { + // Check if we have any flavors. If we have at least 2, combine with the project name + // so the user can know htat in the UI. + lock (_projectFlavors) + { + if (_cachedProjectName == null) + { + _cachedProjectName = _projectFlavors.Count < 2 + ? _rawProjectName + : $"{_rawProjectName} ({string.Join(", ", _projectFlavors)})"; + } + + return _cachedProjectName; + } + } + + private void AddFlavor(string? projectFlavor) + { + if (projectFlavor == null) + return; + + lock (_projectFlavors) + { + if (_projectFlavors.Contains(projectFlavor)) + return; + + _projectFlavors.Add(projectFlavor); + _projectFlavors.Sort(); + _cachedProjectName = null; + } + } + + public static DocumentSpanEntry? TryCreate( + AbstractTableDataSourceFindUsagesContext context, + RoslynDefinitionBucket definitionBucket, + DocumentSpan documentSpan, + HighlightSpanKind spanKind, + MappedSpanResult mappedSpanResult, + ExcerptResult excerptResult, + SourceText lineText, + SymbolUsageInfo symbolUsageInfo, + ImmutableDictionary customColumnsData) + { + var document = documentSpan.Document; + var (guid, projectName, projectFlavor) = GetGuidAndProjectInfo(document); + var entry = new DocumentSpanEntry( + context, definitionBucket, + projectName, projectFlavor, guid, + spanKind, mappedSpanResult, excerptResult, + lineText, symbolUsageInfo, customColumnsData); + + // Because of things like linked files, we may have a reference up in multiple + // different locations that are effectively at the exact same navigation location + // for the user. i.e. they're the same file/span. Showing multiple entries for these + // is just noisy and gets worse and worse with shared projects and whatnot. So, we + // collapse things down to only show a single entry for each unique file/span pair. + var winningEntry = definitionBucket.GetOrAddEntry(documentSpan, entry); + + // If we were the one that successfully added this entry to the bucket, then pass us + // back out to be put in the ui. + if (winningEntry == entry) + return entry; + + // We were not the winner. Add our flavor to the entry that already exists, but throw + // away the item we created as we do not want to add it to the ui. + winningEntry.AddFlavor(projectFlavor); + return null; } protected override IList CreateLineTextInlines() diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs index 59c9464dfce9d..f0f66726823b5 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Shell.TableManager; @@ -24,6 +25,13 @@ private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation public readonly DefinitionItem DefinitionItem; + /// + /// Due to linked files, we may have results for several locations that are all effectively + /// the same file/span. So we represent this as one entry with several project flavors. If + /// we get more than one flavor, we'll show that the user in the UI. + /// + private readonly Dictionary<(string? filePath, TextSpan span), DocumentSpanEntry> _locationToEntry = new(); + public RoslynDefinitionBucket( string name, bool expandedByDefault, @@ -105,6 +113,15 @@ public override bool TryCreateStringContent(out string? content) return null; } + + public DocumentSpanEntry GetOrAddEntry(DocumentSpan documentSpan, DocumentSpanEntry entry) + { + var key = (documentSpan.Document.FilePath, documentSpan.SourceSpan); + lock (_locationToEntry) + { + return _locationToEntry.GetOrAdd(key, entry); + } + } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs index ca0c054e18dba..63db39ae31c06 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs @@ -137,38 +137,28 @@ public void ClearAll() /// /// /// - public FindUsagesContext StartSearch(string title, bool supportsReferences, CancellationToken cancellationToken) - { - this.AssertIsForeground(); - var context = StartSearchWorker(title, supportsReferences, includeContainingTypeAndMemberColumns: false, includeKindColumn: false, cancellationToken); - - // Keep track of this context object as long as it is being displayed in the UI. - // That way we can Clear it out if requested by a client. When the context is - // no longer being displayed, VS will dispose it and it will remove itself from - // this set. - _currentContexts.Add(context); - return context; - } + public (FindUsagesContext context, CancellationToken cancellationToken) StartSearch(string title, bool supportsReferences) + => StartSearchWithCustomColumns(title, supportsReferences, includeContainingTypeAndMemberColumns: false, includeKindColumn: false); /// /// Start a search that may include Containing Type, Containing Member, or Kind information about the reference /// - public FindUsagesContext StartSearchWithCustomColumns( - string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken) + public (FindUsagesContext context, CancellationToken cancellationToken) StartSearchWithCustomColumns( + string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) { this.AssertIsForeground(); - var context = StartSearchWorker(title, supportsReferences, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken); + var context = StartSearchWorker(title, supportsReferences, includeContainingTypeAndMemberColumns, includeKindColumn); // Keep track of this context object as long as it is being displayed in the UI. // That way we can Clear it out if requested by a client. When the context is // no longer being displayed, VS will dispose it and it will remove itself from // this set. _currentContexts.Add(context); - return context; + return (context, context.CancellationTokenSource!.Token); } private AbstractTableDataSourceFindUsagesContext StartSearchWorker( - string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken) + string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) { this.AssertIsForeground(); @@ -187,12 +177,12 @@ private AbstractTableDataSourceFindUsagesContext StartSearchWorker( } return supportsReferences - ? StartSearchWithReferences(window, desiredGroupingPriority, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken) - : StartSearchWithoutReferences(window, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken); + ? StartSearchWithReferences(window, desiredGroupingPriority, includeContainingTypeAndMemberColumns, includeKindColumn) + : StartSearchWithoutReferences(window, includeContainingTypeAndMemberColumns, includeKindColumn); } private AbstractTableDataSourceFindUsagesContext StartSearchWithReferences( - IFindAllReferencesWindow window, int desiredGroupingPriority, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken) + IFindAllReferencesWindow window, int desiredGroupingPriority, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) { // Ensure that the window's definition-grouping reflects what the user wants. // i.e. we may have disabled this column for a previous GoToImplementation call. @@ -207,17 +197,17 @@ private AbstractTableDataSourceFindUsagesContext StartSearchWithReferences( var tableControl = (IWpfTableControl2)window.TableControl; tableControl.GroupingsChanged += (s, e) => StoreCurrentGroupingPriority(window); - return new WithReferencesFindUsagesContext(this, window, _customColumns, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken); + return new WithReferencesFindUsagesContext(this, window, _customColumns, includeContainingTypeAndMemberColumns, includeKindColumn); } private AbstractTableDataSourceFindUsagesContext StartSearchWithoutReferences( - IFindAllReferencesWindow window, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken) + IFindAllReferencesWindow window, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) { // If we're not showing references, then disable grouping by definition, as that will // just lead to a poor experience. i.e. we'll have the definition entry buckets, // with the same items showing underneath them. SetDefinitionGroupingPriority(window, 0); - return new WithoutReferencesFindUsagesContext(this, window, _customColumns, includeContainingTypeAndMemberColumns, includeKindColumn, cancellationToken); + return new WithoutReferencesFindUsagesContext(this, window, _customColumns, includeContainingTypeAndMemberColumns, includeKindColumn); } private void StoreCurrentGroupingPriority(IFindAllReferencesWindow window) @@ -255,5 +245,23 @@ private void SetDefinitionGroupingPriority(IFindAllReferencesWindow window, int tableControl.SetColumnStates(newColumns); } + + protected static (Guid, string projectName, string? projectFlavor) GetGuidAndProjectInfo(Document document) + { + // The FAR system needs to know the guid for the project that a def/reference is + // from (to support features like filtering). Normally that would mean we could + // only support this from a VisualStudioWorkspace. However, we want till work + // in cases like Any-Code (which does not use a VSWorkspace). So we are tolerant + // when we have another type of workspace. This means we will show results, but + // certain features (like filtering) may not work in that context. + var vsWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspace; + + var (projectName, projectFlavor) = document.Project.State.NameAndFlavor; + projectName ??= document.Project.Name; + + var guid = vsWorkspace?.GetProjectGuid(document.Project.Id) ?? Guid.Empty; + + return (guid, projectName, projectFlavor); + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/InfoBar/VisualStudioInfoBarService.cs b/src/VisualStudio/Core/Def/Implementation/InfoBar/VisualStudioInfoBarService.cs index ac21ff0268e4a..75e41d36b4b26 100644 --- a/src/VisualStudio/Core/Def/Implementation/InfoBar/VisualStudioInfoBarService.cs +++ b/src/VisualStudio/Core/Def/Implementation/InfoBar/VisualStudioInfoBarService.cs @@ -24,20 +24,27 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation internal class VisualStudioInfoBarService : ForegroundThreadAffinitizedObject, IInfoBarService { private readonly SVsServiceProvider _serviceProvider; - private readonly IForegroundNotificationService _foregroundNotificationService; private readonly IAsynchronousOperationListener _listener; + /// + /// Keep track of the messages that are currently being shown to the user. If we would + /// show the same message again, block that from happening so we don't spam the user with + /// the same message. When the info bar item is dismissed though, we then may show the + /// same message in the future. This is important for user clarity as it's possible for + /// a feature to fail for some reason, then work fine for a while, then fail again. We want + /// the second failure message to be reported to ensure the user is not confused. + /// + private readonly HashSet _currentlyShowingMessages = new(); + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioInfoBarService( IThreadingContext threadingContext, SVsServiceProvider serviceProvider, - IForegroundNotificationService foregroundNotificationService, IAsynchronousOperationListenerProvider listenerProvider) : base(threadingContext) { _serviceProvider = serviceProvider; - _foregroundNotificationService = foregroundNotificationService; _listener = listenerProvider.GetListener(FeatureAttribute.InfoBar); } @@ -46,13 +53,13 @@ public void ShowInfoBar(string message, params InfoBarUI[] items) ThisCanBeCalledOnAnyThread(); // We can be called from any thread since errors can occur anywhere, however we can only construct and InfoBar from the UI thread. - _foregroundNotificationService.RegisterNotification(() => + this.ThreadingContext.JoinableTaskFactory.RunAsync(async () => { + using var _ = _listener.BeginAsyncOperation(nameof(ShowInfoBar)); + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(ThreadingContext.DisposalToken); if (TryGetInfoBarData(out var infoBarHost)) - { CreateInfoBar(infoBarHost, message, items); - } - }, _listener.BeginAsyncOperation(nameof(ShowInfoBar)), ThreadingContext.DisposalToken); + }); } private bool TryGetInfoBarData(out IVsInfoBarHost infoBarHost) @@ -74,12 +81,19 @@ private bool TryGetInfoBarData(out IVsInfoBarHost infoBarHost) private void CreateInfoBar(IVsInfoBarHost infoBarHost, string message, InfoBarUI[] items) { + this.AssertIsForeground(); + if (!(_serviceProvider.GetService(typeof(SVsInfoBarUIFactory)) is IVsInfoBarUIFactory factory)) { // no info bar factory, don't do anything return; } + // If we're already shown this same message to the user, then do not bother showing it + // to them again. It will just be noisy. + if (_currentlyShowingMessages.Contains(message)) + return; + var textSpans = new List() { new InfoBarTextSpan(message) @@ -107,19 +121,22 @@ private void CreateInfoBar(IVsInfoBarHost infoBarHost, string message, InfoBarUI var infoBarModel = new InfoBarModel( textSpans, - actionItems.ToArray(), + actionItems, KnownMonikers.StatusInformation, isCloseButtonVisible: true); - if (!TryCreateInfoBarUI(factory, infoBarModel, out var infoBarUI)) - { + var infoBarUI = factory.CreateInfoBar(infoBarModel); + if (infoBarUI == null) return; - } uint? infoBarCookie = null; - var eventSink = new InfoBarEvents(items, () => + var eventSink = new InfoBarEvents(items, onClose: () => { - // run given onClose action if there is one. + // Remove the message from the list that we're keeping track of. Future identical + // messages can now be shown. + _currentlyShowingMessages.Remove(message); + + // Run given onClose action if there is one. items.FirstOrDefault(i => i.Kind == InfoBarUI.UIKind.Close).Action?.Invoke(); if (infoBarCookie.HasValue) @@ -132,6 +149,7 @@ private void CreateInfoBar(IVsInfoBarHost infoBarHost, string message, InfoBarUI infoBarCookie = cookie; infoBarHost.AddInfoBar(infoBarUI); + _currentlyShowingMessages.Add(message); } private class InfoBarEvents : IVsInfoBarUIEvents @@ -168,11 +186,5 @@ public void OnActionItemClicked(IVsInfoBarUIElement infoBarUIElement, IVsInfoBar public void OnClosed(IVsInfoBarUIElement infoBarUIElement) => _onClose(); } - - private static bool TryCreateInfoBarUI(IVsInfoBarUIFactory infoBarUIFactory, IVsInfoBar infoBar, out IVsInfoBarUIElement uiElement) - { - uiElement = infoBarUIFactory.CreateInfoBar(infoBar); - return uiElement != null; - } } } diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs new file mode 100644 index 0000000000000..f1184deabce3d --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs @@ -0,0 +1,64 @@ +// 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.Windows; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +{ + internal sealed class InheritanceGlyphFactory : IGlyphFactory + { + private readonly IThreadingContext _threadingContext; + private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter; + private readonly ClassificationTypeMap _classificationTypeMap; + private readonly IClassificationFormatMap _classificationFormatMap; + private readonly IUIThreadOperationExecutor _operationExecutor; + private readonly IWpfTextView _textView; + + public InheritanceGlyphFactory( + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingFindUsagesPresenter, + ClassificationTypeMap classificationTypeMap, + IClassificationFormatMap classificationFormatMap, + IUIThreadOperationExecutor operationExecutor, + IWpfTextView textView) + { + _threadingContext = threadingContext; + _streamingFindUsagesPresenter = streamingFindUsagesPresenter; + _classificationTypeMap = classificationTypeMap; + _classificationFormatMap = classificationFormatMap; + _operationExecutor = operationExecutor; + _textView = textView; + } + + public UIElement? GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) + { + if (tag is InheritanceMarginTag inheritanceMarginTag) + { + var membersOnLine = inheritanceMarginTag.MembersOnLine; + Contract.ThrowIfTrue(membersOnLine.IsEmpty); + + // ZoomLevel of textView is percentage based. (e.g. 20 -> 400 means 20% -> 400%) + // and the scaleFactor of CrispImage is 1 based. (e.g 1 means 100%) + var scaleFactor = _textView.ZoomLevel / 100; + return new MarginGlyph.InheritanceMargin( + _threadingContext, + _streamingFindUsagesPresenter, + _classificationTypeMap, + _classificationFormatMap, + _operationExecutor, + inheritanceMarginTag, + scaleFactor); + } + + return null; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs new file mode 100644 index 0000000000000..7988c9c9683bd --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +{ + [Export(typeof(IGlyphFactoryProvider))] + [Name(nameof(InheritanceGlyphFactoryProvider))] + [ContentType(ContentTypeNames.RoslynContentType)] + [TagType(typeof(InheritanceMarginTag))] + // This would ensure the margin is clickable. + [Order(After = "VsTextMarker")] + internal class InheritanceGlyphFactoryProvider : IGlyphFactoryProvider + { + private readonly IThreadingContext _threadingContext; + private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter; + private readonly ClassificationTypeMap _classificationTypeMap; + private readonly IClassificationFormatMapService _classificationFormatMapService; + private readonly IUIThreadOperationExecutor _operationExecutor; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public InheritanceGlyphFactoryProvider( + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingFindUsagesPresenter, + ClassificationTypeMap classificationTypeMap, + IClassificationFormatMapService classificationFormatMapService, + IUIThreadOperationExecutor operationExecutor) + { + _threadingContext = threadingContext; + _streamingFindUsagesPresenter = streamingFindUsagesPresenter; + _classificationTypeMap = classificationTypeMap; + _classificationFormatMapService = classificationFormatMapService; + _operationExecutor = operationExecutor; + } + + public IGlyphFactory GetGlyphFactory(IWpfTextView view, IWpfTextViewMargin margin) + { + return new InheritanceGlyphFactory( + _threadingContext, + _streamingFindUsagesPresenter, + _classificationTypeMap, + _classificationFormatMapService.GetClassificationFormatMap("tooltip"), + _operationExecutor, + view); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs new file mode 100644 index 0000000000000..34a15b3a44a30 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +{ + internal static class InheritanceMarginHelpers + { + /// + /// Decide which moniker should be shown. + /// + public static ImageMoniker GetMoniker(InheritanceRelationship inheritanceRelationship) + { + // If there are multiple targets and we have the corresponding compound image, use it + if (inheritanceRelationship.HasFlag(InheritanceRelationship.ImplementingOverriding)) + { + return KnownMonikers.ImplementingOverriding; + } + + if (inheritanceRelationship.HasFlag(InheritanceRelationship.ImplementingOverridden)) + { + return KnownMonikers.ImplementingOverridden; + } + + // Otherwise, show the image based on this preference + if (inheritanceRelationship.HasFlag(InheritanceRelationship.Implemented)) + { + return KnownMonikers.Implemented; + } + + if (inheritanceRelationship.HasFlag(InheritanceRelationship.Implementing)) + { + return KnownMonikers.Implementing; + } + + if (inheritanceRelationship.HasFlag(InheritanceRelationship.Overridden)) + { + return KnownMonikers.Overridden; + } + + if (inheritanceRelationship.HasFlag(InheritanceRelationship.Overriding)) + { + return KnownMonikers.Overriding; + } + + // The relationship is None. Don't know what image should be shown, throws + throw ExceptionUtilities.UnexpectedValue(inheritanceRelationship); + } + + /// + /// Create the view models for the inheritance targets of a single member. + /// There are two cases: + /// 1. If all the targets have the same inheritance relationship. It would be an array of TargetViewModel + /// e.g. + /// Target1ViewModel + /// Target2ViewModel + /// Target3ViewModel + /// + /// 2. If targets belongs to different inheritance group. It would be grouped. + /// e.g. + /// Header1ViewModel + /// Target1ViewModel + /// Target2ViewModel + /// Header2ViewModel + /// Target1ViewModel + /// Target2ViewModel + /// + public static ImmutableArray CreateMenuItemViewModelsForSingleMember(ImmutableArray targets) + { + var targetsByRelationship = targets.OrderBy(target => target.DisplayName).GroupBy(target => target.RelationToMember) + .ToImmutableDictionary( + keySelector: grouping => grouping.Key, + elementSelector: grouping => grouping); + if (targetsByRelationship.Count == 1) + { + // If all targets have one relationship. + // e.g. interface IBar { void Bar(); } + // class A : IBar { void Bar() {} } + // class B : IBar { void Bar() {} } + // for 'IBar', the margin would be I↓. So header is not needed. + var (_, targetItems) = targetsByRelationship.Single(); + return targetItems.SelectAsArray(target => TargetMenuItemViewModel.Create(target, indent: false)).CastArray(); + } + else + { + // Otherwise, it means these targets has different relationship, + // these targets would be shown in group, and a header should be shown as the first item to indicate the relationship to user. + return targetsByRelationship.SelectMany(kvp => CreateMenuItemsWithHeader(kvp.Key, kvp.Value)).ToImmutableArray(); + } + } + + /// + /// Create the view models for the inheritance targets of multiple members + /// There are two cases: + /// 1. If all the targets have the same inheritance relationship. It would have this structure: + /// e.g. + /// MemberViewModel1 -> Target1ViewModel + /// Target2ViewModel + /// MemberViewModel2 -> Target4ViewModel + /// Target5ViewModel + /// + /// 2. If targets belongs to different inheritance group. It would be grouped. + /// e.g. + /// MemberViewModel1 -> HeaderViewModel + /// Target1ViewModel + /// HeaderViewModel + /// Target2ViewModel + /// MemberViewModel2 -> HeaderViewModel + /// Target4ViewModel + /// HeaderViewModel + /// Target5ViewModel + /// + public static ImmutableArray CreateMenuItemViewModelsForMultipleMembers(ImmutableArray members) + { + Contract.ThrowIfTrue(members.Length <= 1); + // For multiple members, check if all the targets have the same inheritance relationship. + // If so, then don't add the header, because it is already indicated by the margin. + // Otherwise, add the Header. + var set = members + .SelectMany(member => member.TargetItems.Select(item => item.RelationToMember)) + .ToImmutableHashSet(); + if (set.Count == 1) + { + return members.SelectAsArray(MemberMenuItemViewModel.CreateWithNoHeaderInTargets).CastArray(); + } + else + { + return members.SelectAsArray(MemberMenuItemViewModel.CreateWithHeaderInTargets).CastArray(); + } + } + + public static ImmutableArray CreateMenuItemsWithHeader( + InheritanceRelationship relationship, + IEnumerable targets) + { + using var _ = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var builder); + var displayContent = relationship switch + { + InheritanceRelationship.Implemented => ServicesVSResources.Implemented_members, + InheritanceRelationship.Implementing => ServicesVSResources.Implementing_members, + InheritanceRelationship.Overriding => ServicesVSResources.Overriding_members, + InheritanceRelationship.Overridden => ServicesVSResources.Overridden_members, + _ => throw ExceptionUtilities.UnexpectedValue(relationship) + }; + + var headerViewModel = new HeaderMenuItemViewModel(displayContent, GetMoniker(relationship), displayContent); + builder.Add(headerViewModel); + foreach (var targetItem in targets) + { + builder.Add(TargetMenuItemViewModel.Create(targetItem, indent: true)); + } + + return builder.ToImmutable(); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTag.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTag.cs new file mode 100644 index 0000000000000..3636b6baf7fb5 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTag.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +{ + internal class InheritanceMarginTag : IGlyphTag + { + /// + /// Margin moniker. + /// + public ImageMoniker Moniker { get; } + + /// + /// Members needs to be shown on this line. There might be multiple members. + /// For example: + /// interface IBar { void Foo1(); void Foo2(); } + /// class Bar : IBar { void Foo1() { } void Foo2() { } } + /// + public readonly ImmutableArray MembersOnLine; + + /// + /// Used for accessibility purpose. + /// + public readonly int LineNumber; + + public readonly Workspace Workspace; + + public InheritanceMarginTag(Workspace workspace, int lineNumber, ImmutableArray membersOnLine) + { + Contract.ThrowIfTrue(membersOnLine.IsEmpty); + + Workspace = workspace; + LineNumber = lineNumber; + MembersOnLine = membersOnLine; + // The common case, one line has one member, avoid to use select & aggregate + if (membersOnLine.Length == 1) + { + var member = membersOnLine[0]; + var targets = member.TargetItems; + var relationship = targets[0].RelationToMember; + foreach (var target in targets.Skip(1)) + { + relationship |= target.RelationToMember; + } + + Moniker = InheritanceMarginHelpers.GetMoniker(relationship); + } + else + { + // Multiple members on same line. + var aggregateRelationship = membersOnLine + .SelectMany(member => member.TargetItems.Select(target => target.RelationToMember)) + .Aggregate((r1, r2) => r1 | r2); + Moniker = InheritanceMarginHelpers.GetMoniker(aggregateRelationship); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs new file mode 100644 index 0000000000000..1a8be7aeef315 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Implementation.Classification; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor.Shared.Tagging; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Tagging; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +{ + [Export(typeof(IViewTaggerProvider))] + [TagType(typeof(InheritanceMarginTag))] + [ContentType(ContentTypeNames.RoslynContentType)] + [Name(nameof(InheritanceMarginTaggerProvider))] + internal sealed class InheritanceMarginTaggerProvider : AsynchronousViewTaggerProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public InheritanceMarginTaggerProvider( + IThreadingContext threadingContext, + IAsynchronousOperationListenerProvider listenerProvider) : base( + threadingContext, + listenerProvider.GetListener(FeatureAttribute.InheritanceMargin)) + { + } + + protected override TaggerDelay EventChangeDelay => TaggerDelay.OnIdle; + + protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) + // Because we use frozen-partial documents for semantic classification, we may end up with incomplete + // semantics (esp. during solution load). Because of this, we also register to hear when the full + // compilation is available so that reclassify and bring ourselves up to date. + => new CompilationAvailableTaggerEventSource( + subjectBuffer, + AsyncListener, + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, AsyncListener), + TaggerEventSources.OnViewSpanChanged(ThreadingContext, textViewOpt), + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer), + TaggerEventSources.OnOptionChanged(subjectBuffer, FeatureOnOffOptions.ShowInheritanceMargin)); + + protected override IEnumerable GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer) + { + this.AssertIsForeground(); + + var visibleSpan = textView.GetVisibleLinesSpan(subjectBuffer, extraLines: 100); + if (visibleSpan == null) + { + return base.GetSpansToTag(textView, subjectBuffer); + } + + return SpecializedCollections.SingletonEnumerable(visibleSpan.Value); + } + + protected override async Task ProduceTagsAsync( + TaggerContext context, + DocumentSnapshotSpan spanToTag, + int? caretPosition) + { + var document = spanToTag.Document; + if (document == null) + { + return; + } + + var cancellationToken = context.CancellationToken; + + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + var featureEnabled = options.GetOption(FeatureOnOffOptions.ShowInheritanceMargin); + if (!featureEnabled) + { + return; + } + + // Use FrozenSemantics Version of document to get the semantics ready, therefore we could have faster + // response. (Since the full load might take a long time) + // We also subscribe to CompilationAvailableTaggerEventSource, so this will finally reach the correct state. + var inheritanceMarginInfoService = document.WithFrozenPartialSemantics(cancellationToken).GetLanguageService(); + if (inheritanceMarginInfoService == null) + { + return; + } + + var inheritanceMemberItems = await inheritanceMarginInfoService.GetInheritanceMemberItemsAsync( + document, + spanToTag.SnapshotSpan.Span.ToTextSpan(), + cancellationToken).ConfigureAwait(false); + + if (inheritanceMemberItems.IsEmpty) + { + return; + } + + // One line might have multiple members to show, so group them. + // For example: + // interface IBar { void Foo1(); void Foo2(); } + // class Bar : IBar { void Foo1() { } void Foo2() { } } + var lineToMembers = inheritanceMemberItems + .GroupBy(item => item.LineNumber); + + var snapshot = spanToTag.SnapshotSpan.Snapshot; + + foreach (var (lineNumber, membersOnTheLine) in lineToMembers) + { + var membersOnTheLineArray = membersOnTheLine.ToImmutableArray(); + + // One line should at least have one member on it. + Contract.ThrowIfTrue(membersOnTheLineArray.IsEmpty); + + var line = snapshot.GetLineFromLineNumber(lineNumber); + // We only care about the line, so just tag the start. + context.AddTag(new TagSpan( + new SnapshotSpan(snapshot, line.Start, length: 0), + new InheritanceMarginTag(document.Project.Solution.Workspace, lineNumber, membersOnTheLineArray))); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs new file mode 100644 index 0000000000000..c1cc072ae084f --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.Imaging.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + /// + /// The view model used for the header of TargetMenuItemViewModel. + /// It is used when the context menu contains targets having multiple inheritance relationship. + /// In such case, this would be shown as a header for a group of targets. + /// e.g. + /// 'I↓ Implemented members' + /// Method 'Bar' + /// 'I↑ Implementing members' + /// Method 'Foo' + /// + internal class HeaderMenuItemViewModel : InheritanceMenuItemViewModel + { + public HeaderMenuItemViewModel(string displayContent, ImageMoniker imageMoniker, string automationName) + : base(displayContent, imageMoniker, automationName) + { + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceContextMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceContextMenuItemViewModel.cs new file mode 100644 index 0000000000000..a6b9f5e179d4c --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceContextMenuItemViewModel.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 Microsoft.VisualStudio.Imaging.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + internal abstract class InheritanceMenuItemViewModel + { + /// + /// Display content for the target. + /// + public string DisplayContent { get; } + + /// + /// ImageMoniker shown in the menu. + /// + public ImageMoniker ImageMoniker { get; } + + /// + /// AutomationName for the MenuItem. + /// + public string AutomationName { get; } + + protected InheritanceMenuItemViewModel(string displayContent, ImageMoniker imageMoniker, string automationName) + { + ImageMoniker = imageMoniker; + DisplayContent = displayContent; + AutomationName = automationName; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml new file mode 100644 index 0000000000000..c93bf9692e9eb --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml @@ -0,0 +1,255 @@ + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs new file mode 100644 index 0000000000000..d2003b6644329 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.GoToDefinition; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + internal partial class InheritanceMargin + { + private readonly IThreadingContext _threadingContext; + private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter; + private readonly IUIThreadOperationExecutor _operationExecutor; + private readonly Workspace _workspace; + + public InheritanceMargin( + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingFindUsagesPresenter, + ClassificationTypeMap classificationTypeMap, + IClassificationFormatMap classificationFormatMap, + IUIThreadOperationExecutor operationExecutor, + InheritanceMarginTag tag, + double scaleFactor) + { + _threadingContext = threadingContext; + _streamingFindUsagesPresenter = streamingFindUsagesPresenter; + _workspace = tag.Workspace; + _operationExecutor = operationExecutor; + InitializeComponent(); + + var viewModel = InheritanceMarginViewModel.Create(classificationTypeMap, classificationFormatMap, tag, scaleFactor); + DataContext = viewModel; + ContextMenu.DataContext = viewModel; + ToolTip = new ToolTip { Content = viewModel.ToolTipTextBlock, Style = (Style)FindResource("ToolTipStyle") }; + } + + private void InheritanceMargin_OnClick(object sender, RoutedEventArgs e) + { + if (this.ContextMenu != null) + { + this.ContextMenu.IsOpen = true; + e.Handled = true; + } + } + + private void TargetMenuItem_OnClick(object sender, RoutedEventArgs e) + { + if (e.OriginalSource is MenuItem { DataContext: TargetMenuItemViewModel viewModel }) + { + Logger.Log(FunctionId.InheritanceMargin_NavigateToTarget, KeyValueLogMessage.Create(LogType.UserAction)); + _operationExecutor.Execute( + new UIThreadOperationExecutionOptions( + title: EditorFeaturesResources.Navigating, + defaultDescription: string.Format(ServicesVSResources.Navigate_to_0, viewModel.DisplayContent), + allowCancellation: true, + showProgress: false), + context => GoToDefinitionHelpers.TryGoToDefinition( + ImmutableArray.Create(viewModel.DefinitionItem), + _workspace, + string.Format(EditorFeaturesResources._0_declarations, viewModel.DisplayContent), + _threadingContext, + _streamingFindUsagesPresenter, + context.UserCancellationToken)); + } + } + + private void ChangeBorderToHoveringColor() + { + SetResourceReference(BackgroundProperty, VsBrushes.CommandBarMenuBackgroundGradientKey); + SetResourceReference(BorderBrushProperty, VsBrushes.CommandBarMenuBorderKey); + } + + private void InheritanceMargin_OnMouseEnter(object sender, MouseEventArgs e) + { + ChangeBorderToHoveringColor(); + } + + private void InheritanceMargin_OnMouseLeave(object sender, MouseEventArgs e) + { + // If the context menu is open, then don't reset the color of the button because we need + // the margin looks like being pressed. + if (!ContextMenu.IsOpen) + { + ResetBorderToInitialColor(); + } + } + + private void ContextMenu_OnClose(object sender, RoutedEventArgs e) + { + ResetBorderToInitialColor(); + } + + private void ContextMenu_OnOpen(object sender, RoutedEventArgs e) + { + if (e.OriginalSource is ContextMenu { DataContext: InheritanceMarginViewModel inheritanceMarginViewModel } + && inheritanceMarginViewModel.MenuItemViewModels.Any(vm => vm is TargetMenuItemViewModel)) + { + // We have two kinds of context menu. e.g. + // 1. [margin] -> Target1 + // Target2 + // Target3 + // + // 2. [margin] -> method Bar -> Target1 + // -> Target2 + // -> method Foo -> Target3 + // -> Target4 + // If the first level of the context menu contains a TargetMenuItemViewModel, it means here it is case 1, + // user is viewing the targets menu. + Logger.Log(FunctionId.InheritanceMargin_TargetsMenuOpen, KeyValueLogMessage.Create(LogType.UserAction)); + } + } + + private void TargetsSubmenu_OnOpen(object sender, RoutedEventArgs e) + { + Logger.Log(FunctionId.InheritanceMargin_TargetsMenuOpen, KeyValueLogMessage.Create(LogType.UserAction)); + } + + private void ResetBorderToInitialColor() + { + this.Background = Brushes.Transparent; + this.BorderBrush = Brushes.Transparent; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs new file mode 100644 index 0000000000000..3a466c45183fe --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Text.Classification; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + internal class InheritanceMarginViewModel + { + /// + /// ImageMoniker used for the margin. + /// + public ImageMoniker ImageMoniker { get; } + + /// + /// Tooltip for the margin. + /// + public TextBlock ToolTipTextBlock { get; } + + /// + /// Text used for automation. + /// + public string AutomationName { get; } + + /// + /// ViewModels for the context menu items. + /// + public ImmutableArray MenuItemViewModels { get; } + + /// + /// Scale factor for the margin. + /// + public double ScaleFactor { get; } + + // Internal for testing purpose + internal InheritanceMarginViewModel( + ImageMoniker imageMoniker, + TextBlock toolTipTextBlock, + string automationName, + double scaleFactor, + ImmutableArray menuItemViewModels) + { + ImageMoniker = imageMoniker; + ToolTipTextBlock = toolTipTextBlock; + AutomationName = automationName; + MenuItemViewModels = menuItemViewModels; + ScaleFactor = scaleFactor; + } + + public static InheritanceMarginViewModel Create( + ClassificationTypeMap classificationTypeMap, + IClassificationFormatMap classificationFormatMap, + InheritanceMarginTag tag, + double scaleFactor) + { + var members = tag.MembersOnLine; + if (members.Length == 1) + { + var member = tag.MembersOnLine[0]; + + // Here we want to show a classified text with loc text, + // e.g. 'Bar' is inherited. + // But the classified text are inlines, so can't directly use string.format to generate the string + var inlines = member.DisplayTexts.ToInlines(classificationFormatMap, classificationTypeMap); + var startOfThePlaceholder = ServicesVSResources._0_is_inherited.IndexOf("{0}", StringComparison.Ordinal); + var prefixString = ServicesVSResources._0_is_inherited[..startOfThePlaceholder]; + var suffixString = ServicesVSResources._0_is_inherited[(startOfThePlaceholder + "{0}".Length)..]; + inlines.Insert(0, new Run(prefixString)); + inlines.Add(new Run(suffixString)); + var toolTipTextBlock = inlines.ToTextBlock(classificationFormatMap); + toolTipTextBlock.FlowDirection = FlowDirection.LeftToRight; + + var automationName = string.Format(ServicesVSResources._0_is_inherited, member.DisplayTexts.JoinText()); + var menuItemViewModels = InheritanceMarginHelpers.CreateMenuItemViewModelsForSingleMember(member.TargetItems); + return new InheritanceMarginViewModel(tag.Moniker, toolTipTextBlock, automationName, scaleFactor, menuItemViewModels); + } + else + { + var textBlock = new TextBlock + { + Text = ServicesVSResources.Multiple_members_are_inherited + }; + + // Same automation name can't be set for control for accessibility purpose. So add the line number info. + var automationName = string.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, tag.LineNumber); + var menuItemViewModels = InheritanceMarginHelpers.CreateMenuItemViewModelsForMultipleMembers(tag.MembersOnLine); + return new InheritanceMarginViewModel(tag.Moniker, textBlock, automationName, scaleFactor, menuItemViewModels); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs new file mode 100644 index 0000000000000..b3c55621dda71 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Wpf; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.VisualStudio.Imaging.Interop; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + /// + /// View model used to display a member in MenuItem. Only used when there are multiple members on the same line. + /// e.g. + /// interface IBar + /// { + /// event EventHandler e1, e2 + /// } + /// public class Bar : IBar + /// { + /// public event EventHandler e1, e2 + /// } + /// And this view model is used to show the first level entry to let the user choose member. + /// + internal class MemberMenuItemViewModel : InheritanceMenuItemViewModel + { + /// + /// Inheritance Targets for this member. + /// + public ImmutableArray Targets { get; } + + public MemberMenuItemViewModel( + string displayContent, + ImageMoniker imageMoniker, + string automationName, + ImmutableArray targets) : base(displayContent, imageMoniker, automationName) + { + Targets = targets; + } + + public static MemberMenuItemViewModel CreateWithNoHeaderInTargets(InheritanceMarginItem member) + { + var displayName = member.DisplayTexts.JoinText(); + return new MemberMenuItemViewModel( + displayName, + member.Glyph.GetImageMoniker(), + displayName, + member.TargetItems + .OrderBy(item => item.DisplayName) + .SelectAsArray(item => TargetMenuItemViewModel.Create(item, indent: false)) + .CastArray()); + } + + public static MemberMenuItemViewModel CreateWithHeaderInTargets(InheritanceMarginItem member) + { + var displayName = member.DisplayTexts.JoinText(); + var targetsByRelationship = member.TargetItems + .OrderBy(item => item.DisplayName) + .GroupBy(target => target.RelationToMember) + .SelectMany(grouping => InheritanceMarginHelpers.CreateMenuItemsWithHeader(grouping.Key, grouping)) + .ToImmutableArray(); + + return new MemberMenuItemViewModel( + displayName, + member.Glyph.GetImageMoniker(), + displayName, + targetsByRelationship); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs new file mode 100644 index 0000000000000..ababfe920b829 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; +using System.Windows.Controls; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + internal class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector + { + // By default, ContextMenu would create same MenuItem for each ViewModel from ItemSource, + // this would override the default behavior, and let contextMenu create different MenuItem + // based on the ViewModel's type + public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl) + { + if (item is HeaderMenuItemViewModel) + { + // Template for Header + return (DataTemplate)parentItemsControl.FindResource("HeaderMenuItemTemplate"); + } + + if (item is TargetMenuItemViewModel) + { + // Template for Target + return (DataTemplate)parentItemsControl.FindResource("TargetMenuItemTemplate"); + } + + if (item is MemberMenuItemViewModel) + { + // Template for member + return (DataTemplate)parentItemsControl.FindResource("MemberMenuItemTemplate"); + } + + throw ExceptionUtilities.Unreachable; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs new file mode 100644 index 0000000000000..d8b553509c76a --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs @@ -0,0 +1,68 @@ +// 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.Windows; +using Microsoft.CodeAnalysis.Editor.Wpf; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.VisualStudio.Imaging.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +{ + /// + /// View model used to show the MenuItem for inheritance target. + /// + internal class TargetMenuItemViewModel : InheritanceMenuItemViewModel + { + /// + /// The margin for the default case. + /// + private static readonly Thickness s_defaultMargin = new Thickness(4, 1, 4, 1); + + /// + /// The margin used when this target item needs to be indented when the target is shown with the header. + /// e.g. + /// 'I↓ Implemented members' + /// Method 'Bar' + /// 'I↑ Implementing members' + /// Method 'Foo' + /// It is 22 because the default left margin is 4, and we want to keep the same indentation margin same as solution explorer, which is 18. + /// + private static readonly Thickness s_indentMargin = new Thickness(22, 1, 4, 1); + + /// + /// DefinitionItem used for navigation. + /// + public DefinitionItem DefinitionItem { get; } + + /// + /// Margin for the image moniker. + /// + public Thickness Margin { get; } + + // Internal for testing purpose + internal TargetMenuItemViewModel( + string displayContent, + ImageMoniker imageMoniker, + string automationName, + DefinitionItem definitionItem, + Thickness margin) : base(displayContent, imageMoniker, automationName) + { + DefinitionItem = definitionItem; + Margin = margin; + } + + public static TargetMenuItemViewModel Create(InheritanceTargetItem target, bool indent) + { + var displayContent = target.DisplayName; + var imageMoniker = target.Glyph.GetImageMoniker(); + return new TargetMenuItemViewModel( + displayContent, + imageMoniker, + displayContent, + target.DefinitionItem, + indent ? s_indentMargin : s_defaultMargin); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Interop/CleanableWeakComHandleTable.cs b/src/VisualStudio/Core/Def/Implementation/Interop/CleanableWeakComHandleTable.cs index 57711f5830669..7bc1ff3f56f79 100644 --- a/src/VisualStudio/Core/Def/Implementation/Interop/CleanableWeakComHandleTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/Interop/CleanableWeakComHandleTable.cs @@ -8,7 +8,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Interop @@ -39,10 +41,6 @@ internal class CleanableWeakComHandleTable : ForegroundThreadAffin private int _itemsAddedSinceLastCleanUp; private bool _needsCleanUp; - private IEnumerator>> _cleanUpEnumerator; - - private enum CleanUpState { Initial, CollectingDeadKeys, RemovingDeadKeys } - private CleanUpState _cleanUpState; public bool NeedsCleanUp => _needsCleanUp; @@ -54,120 +52,93 @@ public CleanableWeakComHandleTable(IThreadingContext threadingContext, int? clea CleanUpThreshold = cleanUpThreshold ?? DefaultCleanUpThreshold; CleanUpTimeSlice = cleanUpTimeSlice ?? s_defaultCleanUpTimeSlice; - _cleanUpState = CleanUpState.Initial; - } - - private void InvalidateEnumerator() - { - if (_cleanUpEnumerator != null) - { - _cleanUpEnumerator.Dispose(); - _cleanUpEnumerator = null; - } - } - - private bool CollectDeadKeys(TimeSlice timeSlice) - { - Debug.Assert(_cleanUpState == CleanUpState.CollectingDeadKeys); - Debug.Assert(_cleanUpEnumerator != null); - - while (_cleanUpEnumerator.MoveNext()) - { - var pair = _cleanUpEnumerator.Current; - - if (!pair.Value.IsAlive()) - { - _deadKeySet.Add(pair.Key); - } - - if (timeSlice.IsOver) - { - return false; - } - } - - return true; - } - - private bool RemoveDeadKeys(TimeSlice timeSlice) - { - Debug.Assert(_cleanUpEnumerator == null); - - while (_deadKeySet.Count > 0) - { - var key = _deadKeySet.First(); - - _deadKeySet.Remove(key); - - Debug.Assert(_table.ContainsKey(key), "Key not found in table."); - _table.Remove(key); - - if (timeSlice.IsOver) - { - return false; - } - } - - return true; } /// - /// Cleans up references to dead objects in the table. This operation will return if it takes - /// longer than . Calling further - /// times will continue the process. + /// Cleans up references to dead objects in the table. This operation will yield to other foreground operations + /// any time execution exceeds . /// - public void CleanUpDeadObjects() + public async Task CleanUpDeadObjectsAsync(IAsynchronousOperationListener listener) { - this.AssertIsForeground(); + using var _ = listener.BeginAsyncOperation(nameof(CleanUpDeadObjectsAsync)); + + Debug.Assert(ThreadingContext.JoinableTaskContext.IsOnMainThread, "This method is optimized for cases where calls do not yield before checking _needsCleanUp."); + + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(ThreadingContext.DisposalToken); if (!_needsCleanUp) { return; } + // Immediately mark as not needing cleanup; this operation will clean up the table by the time it returns. + _needsCleanUp = false; + var timeSlice = new TimeSlice(CleanUpTimeSlice); - if (_cleanUpState == CleanUpState.Initial) - { - _cleanUpEnumerator = _table.GetEnumerator(); - _cleanUpState = CleanUpState.CollectingDeadKeys; - } + await CollectDeadKeysAsync().ConfigureAwait(true); + await RemoveDeadKeysAsync().ConfigureAwait(true); + return; - if (_cleanUpState == CleanUpState.CollectingDeadKeys) + // Local functions + async Task CollectDeadKeysAsync() { - if (_cleanUpEnumerator == null) + // This method returns after making a complete pass enumerating the elements of _table without finding + // any entries that are not alive. If a pass exceeds the allowed time slice after finding one or more + // dead entries, the pass yields before processing the elements found so far and restarting the + // enumeration. + // + // ⚠ This method may interleave with other asynchronous calls to CleanUpDeadObjectsAsync. + var cleanUpEnumerator = _table.GetEnumerator(); + while (cleanUpEnumerator.MoveNext()) { - // The enumerator got reset while we were collecting dead keys. - // Go ahead and remove the dead keys that were already collected before - // collecting more. - if (!RemoveDeadKeys(timeSlice)) + var pair = cleanUpEnumerator.Current; + if (!pair.Value.IsAlive()) { - return; + _deadKeySet.Add(pair.Key); + + if (timeSlice.IsOver) + { + // Yield before processing items found so far. + await ResetTimeSliceAsync().ConfigureAwait(true); + + // Process items found prior to exceeding the time slice. Due to interleaving, it is + // possible for this call to process items found by another asynchronous call to + // CollectDeadKeysAsync, or for another asynchronous call to RemoveDeadKeysAsync to process + // all items prior to this call. + await RemoveDeadKeysAsync().ConfigureAwait(true); + + // Obtain a new enumerator since the previous one may be invalidated. + cleanUpEnumerator = _table.GetEnumerator(); + } } - - _cleanUpEnumerator = _table.GetEnumerator(); - } - - if (!CollectDeadKeys(timeSlice)) - { - return; } - - InvalidateEnumerator(); - _cleanUpState = CleanUpState.RemovingDeadKeys; } - if (_cleanUpState == CleanUpState.RemovingDeadKeys) + async Task RemoveDeadKeysAsync() { - if (!RemoveDeadKeys(timeSlice)) + while (_deadKeySet.Count > 0) { - return; - } + // Fully process one item from _deadKeySet before the possibility of yielding + var key = _deadKeySet.First(); - _cleanUpState = CleanUpState.Initial; + _deadKeySet.Remove(key); + + Debug.Assert(_table.ContainsKey(key), "Key not found in table."); + _table.Remove(key); + + if (timeSlice.IsOver) + { + await ResetTimeSliceAsync().ConfigureAwait(true); + } + } } - _needsCleanUp = false; + async Task ResetTimeSliceAsync() + { + await listener.Delay(TimeSpan.FromMilliseconds(50), ThreadingContext.DisposalToken).ConfigureAwait(true); + timeSlice = new TimeSlice(CleanUpTimeSlice); + } } public void Add(TKey key, TValue value) @@ -191,8 +162,6 @@ public void Add(TKey key, TValue value) _itemsAddedSinceLastCleanUp = 0; } - InvalidateEnumerator(); - _table.Add(key, new WeakComHandle(value)); } @@ -200,8 +169,6 @@ public TValue Remove(TKey key) { this.AssertIsForeground(); - InvalidateEnumerator(); - if (_deadKeySet.Contains(key)) { _deadKeySet.Remove(key); diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs index 8959dd15627ab..aedcb21c1a2a6 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -11,17 +14,28 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.LogHub; +using Microsoft.VisualStudio.RpcContracts.Logging; +using Microsoft.VisualStudio.Shell.ServiceBroker; using Microsoft.VisualStudio.Threading; using Nerdbank.Streams; using Roslyn.Utilities; +using StreamJsonRpc; using VSShell = Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient { - internal abstract class AbstractInProcLanguageClient : ILanguageClient + internal abstract partial class AbstractInProcLanguageClient : ILanguageClient, ILanguageServerFactory, ICapabilitiesProvider { + /// + /// A unique, always increasing, ID we use to identify this server in our loghub logs. Needed so that if our + /// server is restarted that we can have a new logstream for the new server. + /// + private static int s_logHubSessionId; + private readonly string? _diagnosticsClientName; private readonly VSShell.IAsyncServiceProvider _asyncServiceProvider; private readonly IThreadingContext _threadingContext; @@ -39,7 +53,7 @@ internal abstract class AbstractInProcLanguageClient : ILanguageClient /// /// Created when is called. /// - private InProcLanguageServer? _languageServer; + private LanguageServerTarget? _languageServer; /// /// Gets the name of the language client (displayed to the user). @@ -92,11 +106,6 @@ public AbstractInProcLanguageClient( _threadingContext = threadingContext; } - /// - /// Can be overridden by subclasses to control what capabilities this language client has. - /// - protected internal abstract VSServerCapabilities GetCapabilities(); - public async Task ActivateAsync(CancellationToken cancellationToken) { // HACK HACK HACK: prevent potential crashes/state corruption during load. Fixes @@ -122,7 +131,7 @@ public AbstractInProcLanguageClient( // https://github.com/dotnet/roslyn/issues/29602 will track removing this hack // since that's the primary offending persister that needs to be addressed. await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - _ = GetCapabilities(); + _ = GetCapabilities(new VSClientCapabilities { SupportsVisualStudioExtensions = true }); if (_languageServer is not null) { @@ -132,14 +141,11 @@ public AbstractInProcLanguageClient( } var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - _languageServer = await InProcLanguageServer.CreateAsync( + + _languageServer = (LanguageServerTarget)await CreateAsync( this, serverStream, serverStream, - _requestDispatcherFactory.CreateRequestDispatcher(), - Workspace, - _diagnosticService, - _listenerProvider, _lspWorkspaceRegistrationService, _asyncServiceProvider, _diagnosticsClientName, @@ -176,5 +182,84 @@ public Task OnServerInitializeFailedAsync(Exception e) // We don't need to provide additional exception handling here, liveshare already handles failure cases for this server. return Task.CompletedTask; } + + internal static async Task CreateAsync( + AbstractInProcLanguageClient languageClient, + Stream inputStream, + Stream outputStream, + ILspWorkspaceRegistrationService lspWorkspaceRegistrationService, + VSShell.IAsyncServiceProvider? asyncServiceProvider, + string? clientName, + CancellationToken cancellationToken) + { + var jsonMessageFormatter = new JsonMessageFormatter(); + VSExtensionUtilities.AddVSExtensionConverters(jsonMessageFormatter.JsonSerializer); + + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + var serverTypeName = languageClient.GetType().Name; + + var logger = await CreateLoggerAsync(asyncServiceProvider, serverTypeName, clientName, jsonRpc, cancellationToken).ConfigureAwait(false); + + var server = languageClient.Create( + jsonRpc, + languageClient, + lspWorkspaceRegistrationService, + logger ?? NoOpLspLogger.Instance); + + jsonRpc.StartListening(); + return server; + } + + private static async Task CreateLoggerAsync( + VSShell.IAsyncServiceProvider? asyncServiceProvider, + string serverTypeName, + string? clientName, + JsonRpc jsonRpc, + CancellationToken cancellationToken) + { + if (asyncServiceProvider == null) + return null; + + var logName = $"Roslyn.{serverTypeName}.{clientName ?? "Default"}.{Interlocked.Increment(ref s_logHubSessionId)}"; + var logId = new LogId(logName, new ServiceMoniker(typeof(LanguageServerTarget).FullName)); + + var serviceContainer = await VSShell.ServiceExtensions.GetServiceAsync(asyncServiceProvider).ConfigureAwait(false); + var service = serviceContainer.GetFullAccessServiceBroker(); + + var configuration = await TraceConfiguration.CreateTraceConfigurationInstanceAsync(service, cancellationToken).ConfigureAwait(false); + var logOptions = new RpcContracts.Logging.LoggerOptions(new LoggingLevelSettings(SourceLevels.ActivityTracing | SourceLevels.Information)); + var traceSource = await configuration.RegisterLogSourceAsync(logId, logOptions, cancellationToken).ConfigureAwait(false); + + // Associate this trace source with the jsonrpc conduit. This ensures that we can associate logs we report + // with our callers and the operations they are performing. + jsonRpc.ActivityTracingStrategy = new CorrelationManagerTracingStrategy { TraceSource = traceSource }; + + return new LogHubLspLogger(configuration, traceSource); + } + + public ILanguageServerTarget Create( + JsonRpc jsonRpc, + ICapabilitiesProvider capabilitiesProvider, + ILspWorkspaceRegistrationService workspaceRegistrationService, + ILspLogger logger) + { + return new VisualStudioInProcLanguageServer( + _requestDispatcherFactory, + jsonRpc, + capabilitiesProvider, + workspaceRegistrationService, + _listenerProvider, + logger, + _diagnosticService, + clientName: _diagnosticsClientName, + userVisibleServerName: this.Name, + telemetryServerTypeName: this.GetType().Name); + } + + public abstract ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities); } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs index c42dc94c6957e..e083b252a16d8 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -32,12 +31,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient internal class AlwaysActivateInProcLanguageClient : AbstractInProcLanguageClient { private readonly DefaultCapabilitiesProvider _defaultCapabilitiesProvider; - private readonly IGlobalOptionService _globalOptionService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, true)] public AlwaysActivateInProcLanguageClient( - IGlobalOptionService globalOptionService, CSharpVisualBasicRequestDispatcherFactory csharpVBRequestDispatcherFactory, VisualStudioWorkspace workspace, IAsynchronousOperationListenerProvider listenerProvider, @@ -47,13 +44,12 @@ public AlwaysActivateInProcLanguageClient( IThreadingContext threadingContext) : base(csharpVBRequestDispatcherFactory, workspace, diagnosticService: null, listenerProvider, lspWorkspaceRegistrationService, asyncServiceProvider, threadingContext, diagnosticsClientName: null) { - _globalOptionService = globalOptionService; _defaultCapabilitiesProvider = defaultCapabilitiesProvider; } - public override string Name => "C#/Visual Basic Language Server Client"; + public override string Name => CSharpVisualBasicLanguageServerFactory.UserVisibleName; - protected internal override VSServerCapabilities GetCapabilities() + public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) { var serverCapabilities = new VSServerCapabilities(); @@ -61,7 +57,7 @@ protected internal override VSServerCapabilities GetCapabilities() var isLspEditorEnabled = Workspace.Services.GetRequiredService().IsExperimentEnabled(VisualStudioWorkspaceContextService.LspEditorFeatureFlagName); if (isLspEditorEnabled) { - serverCapabilities = _defaultCapabilitiesProvider.GetCapabilities(); + serverCapabilities = (VSServerCapabilities)_defaultCapabilitiesProvider.GetCapabilities(clientCapabilities); } else { diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActiveLanguageClientEventListener.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActiveLanguageClientEventListener.cs index 91317c83d8b1f..e5e7568918135 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActiveLanguageClientEventListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActiveLanguageClientEventListener.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; using System.Composition; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -47,13 +49,26 @@ public AlwaysActiveLanguageClientEventListener( /// public void StartListening(Workspace workspace, object serviceOpt) { - var token = this._asynchronousOperationListener.BeginAsyncOperation("LoadAsync"); // Trigger a fire and forget request to the VS LSP client to load our ILanguageClient. - // This needs to be done with .Forget() as the LoadAsync (VS LSP client) synchronously stores the result task of OnLoadedAsync. - // The synchronous execution happens under the sln load threaded wait dialog, so user actions cannot be made in between triggering LoadAsync and storing the result task from OnLoadedAsync. - // The result task from OnLoadedAsync is waited on before invoking LSP requests to the ILanguageClient. - this._languageClientBroker.Value.LoadAsync(new LanguageClientMetadata(new[] { ContentTypeNames.CSharpContentType, ContentTypeNames.VisualBasicContentType }), _languageClient) - .CompletesAsyncOperation(token).Forget(); + _ = LoadAsync(); + } + + private async Task LoadAsync() + { + try + { + using var token = this._asynchronousOperationListener.BeginAsyncOperation(nameof(LoadAsync)); + + // Explicitly switch to the bg so that if this causes any expensive work (like mef loads) it + // doesn't block the UI thread. + await TaskScheduler.Default; + + await _languageClientBroker.Value.LoadAsync( + new LanguageClientMetadata(new[] { ContentTypeNames.CSharpContentType, ContentTypeNames.VisualBasicContentType }), _languageClient).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } } /// diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/DefaultCapabilitiesProvider.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/DefaultCapabilitiesProvider.cs deleted file mode 100644 index 3e88ccbee2050..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/DefaultCapabilitiesProvider.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Completion.Providers; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; -using Microsoft.VisualStudio.LanguageServer.Protocol; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient -{ - [Export, Shared] - internal class DefaultCapabilitiesProvider - { - private readonly ImmutableArray> _completionProviders; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultCapabilitiesProvider( - [ImportMany] IEnumerable> completionProviders) - { - _completionProviders = completionProviders - .Where(lz => lz.Metadata.Language == LanguageNames.CSharp || lz.Metadata.Language == LanguageNames.VisualBasic) - .ToImmutableArray(); - } - - public VSServerCapabilities GetCapabilities() - { - var commitCharacters = CompletionRules.Default.DefaultCommitCharacters.Select(c => c.ToString()).ToArray(); - var triggerCharacters = _completionProviders.SelectMany( - lz => CompletionHandler.GetTriggerCharacters(lz.Value)).Distinct().Select(c => c.ToString()).ToArray(); - - return new VSServerCapabilities - { - DefinitionProvider = true, - RenameProvider = true, - ImplementationProvider = true, - CodeActionProvider = new CodeActionOptions { CodeActionKinds = new[] { CodeActionKind.QuickFix, CodeActionKind.Refactor } }, - CodeActionsResolveProvider = true, - CompletionProvider = new LanguageServer.Protocol.CompletionOptions - { - ResolveProvider = true, - AllCommitCharacters = commitCharacters, - TriggerCharacters = triggerCharacters - }, - SignatureHelpProvider = new SignatureHelpOptions { TriggerCharacters = new[] { "(", "," } }, - DocumentSymbolProvider = true, - WorkspaceSymbolProvider = true, - DocumentFormattingProvider = true, - DocumentRangeFormattingProvider = true, - DocumentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions { FirstTriggerCharacter = "}", MoreTriggerCharacter = new[] { ";", "\n" } }, - OnAutoInsertProvider = new DocumentOnAutoInsertOptions { TriggerCharacters = new[] { "'", "/", "\n" } }, - DocumentHighlightProvider = true, - ReferencesProvider = true, - ProjectContextProvider = true, - FoldingRangeProvider = true, - SemanticTokensOptions = new SemanticTokensOptions - { - DocumentProvider = new SemanticTokensDocumentProviderOptions { Edits = true }, - RangeProvider = true, - Legend = new SemanticTokensLegend - { - TokenTypes = SemanticTokenTypes.AllTypes.Concat(SemanticTokensHelpers.RoslynCustomTokenTypes).ToArray(), - TokenModifiers = new string[] { SemanticTokenModifiers.Static } - } - }, - ExecuteCommandProvider = new ExecuteCommandOptions(), - TextDocumentSync = new TextDocumentSyncOptions - { - Change = TextDocumentSyncKind.Incremental, - OpenClose = true - }, - - // Always support hover - if any LSP client for a content type advertises support, - // then the liveshare provider is disabled. So we must provide for both C# and razor - // until https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1106064/ is fixed - // or we have different content types. - HoverProvider = true, - - // Diagnostic requests are only supported from PullDiagnosticsInProcLanguageClient. - SupportsDiagnosticRequests = false, - }; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs deleted file mode 100644 index 81a4d73803cc1..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs +++ /dev/null @@ -1,828 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Text; -using Microsoft.ServiceHub.Framework; -using Microsoft.VisualStudio.LanguageServer.Client; -using Microsoft.VisualStudio.LanguageServer.Protocol; -using Microsoft.VisualStudio.LogHub; -using Microsoft.VisualStudio.Shell.ServiceBroker; -using Roslyn.Utilities; -using StreamJsonRpc; - -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; -using VSShell = Microsoft.VisualStudio.Shell; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient -{ - /// - /// Defines the language server to be hooked up to an using StreamJsonRpc. This runs - /// in proc as not all features provided by this server are available out of proc (e.g. some diagnostics). - /// - internal partial class InProcLanguageServer : IAsyncDisposable - { - /// - /// A unique, always increasing, ID we use to identify this server in our loghub logs. Needed so that if our - /// server is restarted that we can have a new logstream for the new server. - /// - private static int s_logHubSessionId; - - /// - /// Legacy support for LSP push diagnostics. - /// - /// - private readonly IDiagnosticService? _diagnosticService; - private readonly IAsynchronousOperationListener _listener; - private readonly string? _clientName; - private readonly JsonRpc _jsonRpc; - private readonly AbstractInProcLanguageClient _languageClient; - private readonly RequestDispatcher _requestDispatcher; - private readonly Workspace _workspace; - private readonly RequestExecutionQueue _queue; - private readonly LogHubLspLogger? _logger; - - /// - /// Legacy support for LSP push diagnostics. - /// Work queue responsible for receiving notifications about diagnostic updates and publishing those to - /// interested parties. - /// - private readonly AsyncBatchingWorkQueue _diagnosticsWorkQueue; - - private VSClientCapabilities? _clientCapabilities; - private bool _shuttingDown; - private Task? _errorShutdownTask; - - private InProcLanguageServer( - AbstractInProcLanguageClient languageClient, - RequestDispatcher requestDispatcher, - Workspace workspace, - IDiagnosticService? diagnosticService, - IAsynchronousOperationListenerProvider listenerProvider, - ILspWorkspaceRegistrationService lspWorkspaceRegistrationService, - string serverTypeName, - string? clientName, - JsonRpc jsonRpc, - LogHubLspLogger? logger) - { - _languageClient = languageClient; - _requestDispatcher = requestDispatcher; - _workspace = workspace; - _logger = logger; - - _jsonRpc = jsonRpc; - _jsonRpc.AddLocalRpcTarget(this); - _jsonRpc.StartListening(); - - _diagnosticService = diagnosticService; - _listener = listenerProvider.GetListener(FeatureAttribute.LanguageServer); - _clientName = clientName; - - _queue = new RequestExecutionQueue(logger ?? NoOpLspLogger.Instance, lspWorkspaceRegistrationService, languageClient.Name, serverTypeName); - _queue.RequestServerShutdown += RequestExecutionQueue_Errored; - - // Dedupe on DocumentId. If we hear about the same document multiple times, we only need to process that id once. - _diagnosticsWorkQueue = new AsyncBatchingWorkQueue( - TimeSpan.FromMilliseconds(250), - ProcessDiagnosticUpdatedBatchAsync, - EqualityComparer.Default, - _listener, - _queue.CancellationToken); - - if (_diagnosticService != null) - _diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated; - } - - public static async Task CreateAsync( - AbstractInProcLanguageClient languageClient, - Stream inputStream, - Stream outputStream, - RequestDispatcher requestDispatcher, - Workspace workspace, - IDiagnosticService? diagnosticService, - IAsynchronousOperationListenerProvider listenerProvider, - ILspWorkspaceRegistrationService lspWorkspaceRegistrationService, - VSShell.IAsyncServiceProvider? asyncServiceProvider, - string? clientName, - CancellationToken cancellationToken) - { - var jsonMessageFormatter = new JsonMessageFormatter(); - VSExtensionUtilities.AddVSExtensionConverters(jsonMessageFormatter.JsonSerializer); - - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)); - var serverTypeName = languageClient.GetType().Name; - var logger = await CreateLoggerAsync(asyncServiceProvider, serverTypeName, clientName, jsonRpc, cancellationToken).ConfigureAwait(false); - - return new InProcLanguageServer( - languageClient, - requestDispatcher, - workspace, - diagnosticService, - listenerProvider, - lspWorkspaceRegistrationService, - serverTypeName, - clientName, - jsonRpc, - logger); - } - - private static async Task CreateLoggerAsync( - VSShell.IAsyncServiceProvider? asyncServiceProvider, - string serverTypeName, - string? clientName, - JsonRpc jsonRpc, - CancellationToken cancellationToken) - { - if (asyncServiceProvider == null) - return null; - - var logName = $"Roslyn.{serverTypeName}.{clientName ?? "Default"}.{Interlocked.Increment(ref s_logHubSessionId)}"; - var logId = new LogId(logName, new ServiceMoniker(typeof(InProcLanguageServer).FullName)); - - var serviceContainer = await VSShell.ServiceExtensions.GetServiceAsync(asyncServiceProvider).ConfigureAwait(false); - var service = serviceContainer.GetFullAccessServiceBroker(); - - var configuration = await TraceConfiguration.CreateTraceConfigurationInstanceAsync(service, cancellationToken).ConfigureAwait(false); - var traceSource = await configuration.RegisterLogSourceAsync(logId, new LogHub.LoggerOptions(), cancellationToken).ConfigureAwait(false); - - traceSource.Switch.Level = SourceLevels.ActivityTracing | SourceLevels.Information; - - // Associate this trace source with the jsonrpc conduit. This ensures that we can associate logs we report - // with our callers and the operations they are performing. - jsonRpc.ActivityTracingStrategy = new CorrelationManagerTracingStrategy { TraceSource = traceSource }; - - return new LogHubLspLogger(configuration, traceSource); - } - - public bool HasShutdownStarted => _shuttingDown; - - /// - /// Handle the LSP initialize request by storing the client capabilities and responding with the server - /// capabilities. The specification assures that the initialize request is sent only once. - /// - [JsonRpcMethod(Methods.InitializeName, UseSingleObjectParameterDeserialization = true)] - public Task InitializeAsync(InitializeParams initializeParams, CancellationToken cancellationToken) - { - try - { - _logger?.TraceStart("Initialize"); - - Contract.ThrowIfTrue(_clientCapabilities != null, $"{nameof(InitializeAsync)} called multiple times"); - _clientCapabilities = (VSClientCapabilities)initializeParams.Capabilities; - return Task.FromResult(new InitializeResult - { - Capabilities = _languageClient.GetCapabilities(), - }); - } - finally - { - _logger?.TraceStop("Initialize"); - } - } - - [JsonRpcMethod(Methods.InitializedName)] - public Task InitializedAsync() - { - try - { - _logger?.TraceStart("Initialized"); - - // Publish diagnostics for all open documents immediately following initialization. - var solution = _workspace.CurrentSolution; - var openDocuments = _workspace.GetOpenDocumentIds(); - foreach (var documentId in openDocuments) - DiagnosticService_DiagnosticsUpdated(solution, documentId); - - return Task.CompletedTask; - } - finally - { - _logger?.TraceStop("Initialized"); - } - } - - [JsonRpcMethod(Methods.ShutdownName)] - public Task ShutdownAsync(CancellationToken _) - { - try - { - _logger?.TraceStart("Shutdown"); - - ShutdownImpl(); - - return Task.CompletedTask; - } - finally - { - _logger?.TraceStop("Shutdown"); - } - } - - private void ShutdownImpl() - { - Contract.ThrowIfTrue(_shuttingDown, "Shutdown has already been called."); - - _shuttingDown = true; - - if (_diagnosticService != null) - _diagnosticService.DiagnosticsUpdated -= DiagnosticService_DiagnosticsUpdated; - - ShutdownRequestQueue(); - } - - [JsonRpcMethod(Methods.ExitName)] - public Task ExitAsync(CancellationToken _) - { - try - { - _logger?.TraceStart("Exit"); - - ExitImpl(); - - return Task.CompletedTask; - } - finally - { - _logger?.TraceStop("Exit"); - } - } - - private void ExitImpl() - { - try - { - ShutdownRequestQueue(); - _jsonRpc.Dispose(); - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - // Swallow exceptions thrown by disposing our JsonRpc object. Disconnected events can potentially throw their own exceptions so - // we purposefully ignore all of those exceptions in an effort to shutdown gracefully. - } - } - - [JsonRpcMethod(MSLSPMethods.DocumentPullDiagnosticName, UseSingleObjectParameterDeserialization = true)] - public Task GetDocumentPullDiagnosticsAsync(DocumentDiagnosticsParams diagnosticsParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync( - _queue, MSLSPMethods.DocumentPullDiagnosticName, - diagnosticsParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(MSLSPMethods.WorkspacePullDiagnosticName, UseSingleObjectParameterDeserialization = true)] - public Task GetWorkspacePullDiagnosticsAsync(WorkspaceDocumentDiagnosticsParams diagnosticsParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync( - _queue, MSLSPMethods.WorkspacePullDiagnosticName, - diagnosticsParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentDefinitionName, - textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentRenameName, - renameParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentReferencesName, - referencesParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentCodeActionName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentCodeActionsAsync(CodeActionParams codeActionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentCodeActionName, codeActionParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(MSLSPMethods.TextDocumentCodeActionResolveName, UseSingleObjectParameterDeserialization = true)] - public Task ResolveCodeActionAsync(VSCodeAction vsCodeAction, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, MSLSPMethods.TextDocumentCodeActionResolveName, - vsCodeAction, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)] - public async Task> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - // Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698 - return await _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentCompletionName, - completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false); - } - - [JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)] - public Task ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentCompletionResolveName, completionItem, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentFoldingRangeName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentFoldingRangeAsync(FoldingRangeParams textDocumentFoldingRangeParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentFoldingRangeName, textDocumentFoldingRangeParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentDocumentHighlightName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentHoverName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentDocumentSymbolName, documentSymbolParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentFormattingName, documentFormattingParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentOnTypeFormattingName, documentOnTypeFormattingParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentImplementationName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentRangeFormattingName, documentRangeFormattingParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.WorkspaceExecuteCommandName, UseSingleObjectParameterDeserialization = true)] - public Task ExecuteWorkspaceCommandAsync(ExecuteCommandParams executeCommandParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.WorkspaceExecuteCommandName, executeCommandParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)] - public Task GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.WorkspaceSymbolName, workspaceSymbolParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)] - public Task GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, MSLSPMethods.ProjectContextsName, - textDocumentWithContextParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentSemanticTokensAsync(SemanticTokensParams semanticTokensParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, SemanticTokensMethods.TextDocumentSemanticTokensName, - semanticTokensParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensEditsName, UseSingleObjectParameterDeserialization = true)] - public Task> GetTextDocumentSemanticTokensEditsAsync(SemanticTokensEditsParams semanticTokensEditsParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync>(_queue, SemanticTokensMethods.TextDocumentSemanticTokensEditsName, - semanticTokensEditsParams, _clientCapabilities, _clientName, cancellationToken); - } - - // Note: Since a range request is always received in conjunction with a whole document request, we don't need to cache range results. - [JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensRangeName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentSemanticTokensRangeAsync(SemanticTokensRangeParams semanticTokensRangeParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, SemanticTokensMethods.TextDocumentSemanticTokensRangeName, - semanticTokensRangeParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(MSLSPMethods.OnAutoInsertName, UseSingleObjectParameterDeserialization = true)] - public Task GetDocumentOnAutoInsertAsync(DocumentOnAutoInsertParams autoInsertParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, MSLSPMethods.OnAutoInsertName, - autoInsertParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(MSLSPMethods.OnTypeRenameName, UseSingleObjectParameterDeserialization = true)] - public Task GetTypeRenameAsync(DocumentOnTypeRenameParams renameParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, MSLSPMethods.OnTypeRenameName, - renameParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDidChangeName, UseSingleObjectParameterDeserialization = true)] - public Task HandleDocumentDidChangeAsync(DidChangeTextDocumentParams didChangeParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentDidChangeName, - didChangeParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDidOpenName, UseSingleObjectParameterDeserialization = true)] - public Task HandleDocumentDidOpenAsync(DidOpenTextDocumentParams didOpenParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentDidOpenName, - didOpenParams, _clientCapabilities, _clientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDidCloseName, UseSingleObjectParameterDeserialization = true)] - public Task HandleDocumentDidCloseAsync(DidCloseTextDocumentParams didCloseParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return _requestDispatcher.ExecuteRequestAsync(_queue, Methods.TextDocumentDidCloseName, - didCloseParams, _clientCapabilities, _clientName, cancellationToken); - } - - private void DiagnosticService_DiagnosticsUpdated(object _, DiagnosticsUpdatedArgs e) - => DiagnosticService_DiagnosticsUpdated(e.Solution, e.DocumentId); - - private void DiagnosticService_DiagnosticsUpdated(Solution? solution, DocumentId? documentId) - { - // LSP doesn't support diagnostics without a document. So if we get project level diagnostics without a document, ignore them. - if (documentId != null && solution != null) - { - var document = solution.GetDocument(documentId); - if (document == null || document.FilePath == null) - return; - - // Only publish document diagnostics for the languages this provider supports. - if (document.Project.Language != CodeAnalysis.LanguageNames.CSharp && document.Project.Language != CodeAnalysis.LanguageNames.VisualBasic) - return; - - _diagnosticsWorkQueue.AddWork(document.Id); - } - } - - private void ShutdownRequestQueue() - { - _queue.RequestServerShutdown -= RequestExecutionQueue_Errored; - // if the queue requested shutdown via its event, it will have already shut itself down, but this - // won't cause any problems calling it again - _queue.Shutdown(); - } - - private void RequestExecutionQueue_Errored(object sender, RequestShutdownEventArgs e) - { - // log message and shut down - _logger?.TraceWarning($"Request queue is requesting shutdown due to error: {e.Message}"); - - var message = new LogMessageParams() - { - MessageType = MessageType.Error, - Message = e.Message - }; - - var asyncToken = _listener.BeginAsyncOperation(nameof(RequestExecutionQueue_Errored)); - _errorShutdownTask = Task.Run(async () => - { - _logger?.TraceInformation("Shutting down language server."); - - await _jsonRpc.NotifyWithParameterObjectAsync(Methods.WindowLogMessageName, message).ConfigureAwait(false); - - ShutdownImpl(); - ExitImpl(); - }).CompletesAsyncOperation(asyncToken); - } - - /// - /// Stores the last published LSP diagnostics with the Roslyn document that they came from. - /// This is useful in the following scenario. Imagine we have documentA which has contributions to mapped files m1 and m2. - /// dA -> m1 - /// And m1 has contributions from documentB. - /// m1 -> dA, dB - /// When we query for diagnostic on dA, we get a subset of the diagnostics on m1 (missing the contributions from dB) - /// Since each publish diagnostics notification replaces diagnostics per document, - /// we must union the diagnostics contribution from dB and dA to produce all diagnostics for m1 and publish all at once. - /// - /// This dictionary stores the previously computed diagnostics for the published file so that we can - /// union the currently computed diagnostics (e.g. for dA) with previously computed diagnostics (e.g. from dB). - /// - private readonly Dictionary>> _publishedFileToDiagnostics = - new(); - - /// - /// Stores the mapping of a document to the uri(s) of diagnostics previously produced for this document. When - /// we get empty diagnostics for the document we need to find the uris we previously published for this - /// document. Then we can publish the updated diagnostics set for those uris (either empty or the diagnostic - /// contributions from other documents). We use a sorted set to ensure consistency in the order in which we - /// report URIs. While it's not necessary to publish a document's mapped file diagnostics in a particular - /// order, it does make it much easier to write tests and debug issues if we have a consistent ordering. - /// - private readonly Dictionary> _documentsToPublishedUris = new(); - - /// - /// Basic comparer for Uris used by when publishing notifications. - /// - private static readonly Comparer s_uriComparer = Comparer.Create((uri1, uri2) - => Uri.Compare(uri1, uri2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); - - private async Task ProcessDiagnosticUpdatedBatchAsync(ImmutableArray documentIds, CancellationToken cancellationToken) - { - if (_diagnosticService == null) - return; - - var solution = _workspace.CurrentSolution; - foreach (var documentId in documentIds) - { - cancellationToken.ThrowIfCancellationRequested(); - var document = solution.GetDocument(documentId); - if (document != null) - await PublishDiagnosticsAsync(_diagnosticService, document, cancellationToken).ConfigureAwait(false); - } - } - - // Internal for testing purposes. - internal async Task PublishDiagnosticsAsync(IDiagnosticService diagnosticService, Document document, CancellationToken cancellationToken) - { - // Retrieve all diagnostics for the current document grouped by their actual file uri. - var fileUriToDiagnostics = await GetDiagnosticsAsync(diagnosticService, document, cancellationToken).ConfigureAwait(false); - - // Get the list of file uris with diagnostics (for the document). - // We need to join the uris from current diagnostics with those previously published - // so that we clear out any diagnostics in mapped files that are no longer a part - // of the current diagnostics set (because the diagnostics were fixed). - // Use sorted set to have consistent publish ordering for tests and debugging. - var urisForCurrentDocument = _documentsToPublishedUris.GetValueOrDefault(document.Id, ImmutableSortedSet.Create(s_uriComparer)).Union(fileUriToDiagnostics.Keys); - - // Update the mapping for this document to be the uris we're about to publish diagnostics for. - _documentsToPublishedUris[document.Id] = urisForCurrentDocument; - - // Go through each uri and publish the updated set of diagnostics per uri. - foreach (var fileUri in urisForCurrentDocument) - { - // Get the updated diagnostics for a single uri that were contributed by the current document. - var diagnostics = fileUriToDiagnostics.GetValueOrDefault(fileUri, ImmutableArray.Empty); - - if (_publishedFileToDiagnostics.ContainsKey(fileUri)) - { - // Get all previously published diagnostics for this uri excluding those that were contributed from the current document. - // We don't need those since we just computed the updated values above. - var diagnosticsFromOtherDocuments = _publishedFileToDiagnostics[fileUri].Where(kvp => kvp.Key != document.Id).SelectMany(kvp => kvp.Value); - - // Since diagnostics are replaced per uri, we must publish both contributions from this document and any other document - // that has diagnostic contributions to this uri, so union the two sets. - diagnostics = diagnostics.AddRange(diagnosticsFromOtherDocuments); - } - - await SendDiagnosticsNotificationAsync(fileUri, diagnostics).ConfigureAwait(false); - - // There are three cases here -> - // 1. There are no diagnostics to publish for this fileUri. We no longer need to track the fileUri at all. - // 2. There are diagnostics from the current document. Store the diagnostics for the fileUri and document - // so they can be published along with contributions to the fileUri from other documents. - // 3. There are no diagnostics contributed by this document to the fileUri (could be some from other documents). - // We should clear out the diagnostics for this document for the fileUri. - if (diagnostics.IsEmpty) - { - // We published an empty set of diagnostics for this uri. We no longer need to keep track of this mapping - // since there will be no previous diagnostics that we need to clear out. - _documentsToPublishedUris.MultiRemove(document.Id, fileUri); - - // There are not any diagnostics to keep track of for this file, so we can stop. - _publishedFileToDiagnostics.Remove(fileUri); - } - else if (fileUriToDiagnostics.ContainsKey(fileUri)) - { - // We do have diagnostics from the current document - update the published diagnostics map - // to contain the new diagnostics contributed by this document for this uri. - var documentsToPublishedDiagnostics = _publishedFileToDiagnostics.GetOrAdd(fileUri, (_) => - new Dictionary>()); - documentsToPublishedDiagnostics[document.Id] = fileUriToDiagnostics[fileUri]; - } - else - { - // There were diagnostics from other documents, but none from the current document. - // If we're tracking the current document, we can stop. - IReadOnlyDictionaryExtensions.GetValueOrDefault(_publishedFileToDiagnostics, fileUri)?.Remove(document.Id); - _documentsToPublishedUris.MultiRemove(document.Id, fileUri); - } - } - } - - private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray diagnostics) - { - var publishDiagnosticsParams = new PublishDiagnosticParams { Diagnostics = diagnostics.ToArray(), Uri = uri }; - await _jsonRpc.NotifyWithParameterObjectAsync(Methods.TextDocumentPublishDiagnosticsName, publishDiagnosticsParams).ConfigureAwait(false); - } - - private async Task>> GetDiagnosticsAsync( - IDiagnosticService diagnosticService, Document document, CancellationToken cancellationToken) - { - var option = document.IsRazorDocument() - ? InternalDiagnosticsOptions.RazorDiagnosticMode - : InternalDiagnosticsOptions.NormalDiagnosticMode; - var pushDiagnostics = await diagnosticService.GetPushDiagnosticsAsync(document.Project.Solution.Workspace, document.Project.Id, document.Id, id: null, includeSuppressedDiagnostics: false, option, cancellationToken).ConfigureAwait(false); - var diagnostics = pushDiagnostics.WhereAsArray(IncludeDiagnostic); - - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - - // Retrieve diagnostics for the document. These diagnostics could be for the current document, or they could map - // to a different location in a different file. We need to publish the diagnostics for the mapped locations as well. - // An example of this is razor imports where the generated C# document maps to many razor documents. - // https://docs.microsoft.com/en-us/aspnet/core/mvc/views/layout?view=aspnetcore-3.1#importing-shared-directives - // https://docs.microsoft.com/en-us/aspnet/core/blazor/layouts?view=aspnetcore-3.1#centralized-layout-selection - // So we get the diagnostics and group them by the actual mapped path so we can publish notifications - // for each mapped file's diagnostics. - var fileUriToDiagnostics = diagnostics.GroupBy(diagnostic => GetDiagnosticUri(document, diagnostic)).ToDictionary( - group => group.Key, - group => group.Select(diagnostic => ConvertToLspDiagnostic(diagnostic, text)).ToImmutableArray()); - return fileUriToDiagnostics; - - static Uri GetDiagnosticUri(Document document, DiagnosticData diagnosticData) - { - Contract.ThrowIfNull(diagnosticData.DataLocation, "Diagnostic data location should not be null here"); - - // Razor wants to handle all span mapping themselves. So if we are in razor, return the raw doc spans, and - // do not map them. - var filePath = diagnosticData.DataLocation.MappedFilePath ?? diagnosticData.DataLocation.OriginalFilePath; - return ProtocolConversions.GetUriFromFilePath(filePath); - } - } - - private LSP.Diagnostic ConvertToLspDiagnostic(DiagnosticData diagnosticData, SourceText text) - { - Contract.ThrowIfNull(diagnosticData.DataLocation); - - var diagnostic = new LSP.Diagnostic - { - Source = _languageClient?.GetType().Name, - Code = diagnosticData.Id, - Severity = Convert(diagnosticData.Severity), - Range = GetDiagnosticRange(diagnosticData.DataLocation, text), - // Only the unnecessary diagnostic tag is currently supported via LSP. - Tags = diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary) - ? new DiagnosticTag[] { DiagnosticTag.Unnecessary } - : Array.Empty() - }; - - if (diagnosticData.Message != null) - diagnostic.Message = diagnosticData.Message; - - return diagnostic; - } - - private static LSP.DiagnosticSeverity Convert(CodeAnalysis.DiagnosticSeverity severity) - => severity switch - { - CodeAnalysis.DiagnosticSeverity.Hidden => LSP.DiagnosticSeverity.Hint, - CodeAnalysis.DiagnosticSeverity.Info => LSP.DiagnosticSeverity.Hint, - CodeAnalysis.DiagnosticSeverity.Warning => LSP.DiagnosticSeverity.Warning, - CodeAnalysis.DiagnosticSeverity.Error => LSP.DiagnosticSeverity.Error, - _ => throw ExceptionUtilities.UnexpectedValue(severity), - }; - - // Some diagnostics only apply to certain clients and document types, e.g. Razor. - // If the DocumentPropertiesService.DiagnosticsLspClientName property exists, we only include the - // diagnostic if it directly matches the client name. - // If the DocumentPropertiesService.DiagnosticsLspClientName property doesn't exist, - // we know that the diagnostic we're working with is contained in a C#/VB file, since - // if we were working with a non-C#/VB file, then the property should have been populated. - // In this case, unless we have a null client name, we don't want to publish the diagnostic - // (since a null client name represents the C#/VB language server). - private bool IncludeDiagnostic(DiagnosticData diagnostic) - => IReadOnlyDictionaryExtensions.GetValueOrDefault(diagnostic.Properties, nameof(DocumentPropertiesService.DiagnosticsLspClientName)) == _clientName; - - private static LSP.Range GetDiagnosticRange(DiagnosticDataLocation diagnosticDataLocation, SourceText text) - { - var linePositionSpan = DiagnosticData.GetLinePositionSpan(diagnosticDataLocation, text, useMapped: true); - return ProtocolConversions.LinePositionToRange(linePositionSpan); - } - - public async ValueTask DisposeAsync() - { - // if the server shut down due to error, we might not have finished cleaning up - if (_errorShutdownTask is not null) - { - await _errorShutdownTask.ConfigureAwait(false); - } - - _logger?.Dispose(); - } - - internal TestAccessor GetTestAccessor() => new(this); - - internal readonly struct TestAccessor - { - private readonly InProcLanguageServer _server; - - internal TestAccessor(InProcLanguageServer server) - { - _server = server; - } - - internal ImmutableArray GetFileUrisInPublishDiagnostics() - => _server._publishedFileToDiagnostics.Keys.ToImmutableArray(); - - internal ImmutableArray GetDocumentIdsInPublishedUris() - => _server._documentsToPublishedUris.Keys.ToImmutableArray(); - - internal IImmutableSet GetFileUrisForDocument(DocumentId documentId) - => _server._documentsToPublishedUris.GetValueOrDefault(documentId, ImmutableSortedSet.Empty); - - internal ImmutableArray GetDiagnosticsForUriAndDocument(DocumentId documentId, Uri uri) - { - if (_server._publishedFileToDiagnostics.TryGetValue(uri, out var dict) && dict.TryGetValue(documentId, out var diagnostics)) - { - return diagnostics; - } - - return ImmutableArray.Empty; - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/LiveShareInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/LiveShareInProcLanguageClient.cs index ebbf103ced6cf..abf4b240bdb10 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/LiveShareInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/LiveShareInProcLanguageClient.cs @@ -47,7 +47,7 @@ public LiveShareInProcLanguageClient( public override string Name => "Live Share C#/Visual Basic Language Server Client"; - protected internal override VSServerCapabilities GetCapabilities() + public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) { var experimentationService = Workspace.Services.GetRequiredService(); var isLspEditorEnabled = experimentationService.IsExperimentEnabled(VisualStudioWorkspaceContextService.LspEditorFeatureFlagName); @@ -66,7 +66,7 @@ protected internal override VSServerCapabilities GetCapabilities() }; } - return _defaultCapabilitiesProvider.GetCapabilities(); + return _defaultCapabilitiesProvider.GetCapabilities(clientCapabilities); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/LogHubLspLogger.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/LogHubLspLogger.cs index 6528fbcc02da0..2beba61617edf 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/LogHubLspLogger.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/LogHubLspLogger.cs @@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient { - internal partial class InProcLanguageServer + internal abstract partial class AbstractInProcLanguageClient { private class LogHubLspLogger : ILspLogger { diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs index 6922728b9dbfc..11624da7ca356 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -66,15 +65,19 @@ public RazorInProcLanguageClient( _defaultCapabilitiesProvider = defaultCapabilitiesProvider; } - protected internal override VSServerCapabilities GetCapabilities() + public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) { - var capabilities = _defaultCapabilitiesProvider.GetCapabilities(); - - capabilities.SupportsDiagnosticRequests = this.Workspace.IsPullDiagnostics(InternalDiagnosticsOptions.RazorDiagnosticMode); + var capabilities = _defaultCapabilitiesProvider.GetCapabilities(clientCapabilities); // Razor doesn't use workspace symbols, so disable to prevent duplicate results (with LiveshareLanguageClient) in liveshare. capabilities.WorkspaceSymbolProvider = false; + if (capabilities is VSServerCapabilities vsServerCapabilities) + { + vsServerCapabilities.SupportsDiagnosticRequests = this.Workspace.IsPullDiagnostics(InternalDiagnosticsOptions.RazorDiagnosticMode); + return vsServerCapabilities; + } + return capabilities; } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs new file mode 100644 index 0000000000000..ea964c8b3a819 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs @@ -0,0 +1,416 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Roslyn.Utilities; +using StreamJsonRpc; + +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient +{ + /// + /// Implementation of that also supports + /// VS LSP extension methods. + /// + internal class VisualStudioInProcLanguageServer : LanguageServerTarget + { + /// + /// Legacy support for LSP push diagnostics. + /// Work queue responsible for receiving notifications about diagnostic updates and publishing those to + /// interested parties. + /// + private readonly AsyncBatchingWorkQueue _diagnosticsWorkQueue; + private readonly IDiagnosticService? _diagnosticService; + + internal VisualStudioInProcLanguageServer( + AbstractRequestDispatcherFactory requestDispatcherFactory, + JsonRpc jsonRpc, + ICapabilitiesProvider capabilitiesProvider, + ILspWorkspaceRegistrationService workspaceRegistrationService, + IAsynchronousOperationListenerProvider listenerProvider, + ILspLogger logger, + IDiagnosticService? diagnosticService, + string? clientName, + string userVisibleServerName, + string telemetryServerTypeName) : base(requestDispatcherFactory, jsonRpc, capabilitiesProvider, workspaceRegistrationService, listenerProvider, logger, clientName, userVisibleServerName, telemetryServerTypeName) + { + _diagnosticService = diagnosticService; + // Dedupe on DocumentId. If we hear about the same document multiple times, we only need to process that id once. + _diagnosticsWorkQueue = new AsyncBatchingWorkQueue( + TimeSpan.FromMilliseconds(250), + (ids, ct) => ProcessDiagnosticUpdatedBatchAsync(_diagnosticService, ids, ct), + EqualityComparer.Default, + Listener, + Queue.CancellationToken); + + if (_diagnosticService != null) + _diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated; + } + + public override Task InitializedAsync() + { + try + { + Logger?.TraceStart("Initialized"); + + // Publish diagnostics for all open documents immediately following initialization. + PublishOpenFileDiagnostics(); + + return Task.CompletedTask; + } + finally + { + Logger?.TraceStop("Initialized"); + } + + void PublishOpenFileDiagnostics() + { + foreach (var workspace in WorkspaceRegistrationService.GetAllRegistrations()) + { + var solution = workspace.CurrentSolution; + var openDocuments = workspace.GetOpenDocumentIds(); + foreach (var documentId in openDocuments) + DiagnosticService_DiagnosticsUpdated(solution, documentId); + } + } + } + + [JsonRpcMethod(MSLSPMethods.TextDocumentCodeActionResolveName, UseSingleObjectParameterDeserialization = true)] + public Task ResolveCodeActionAsync(VSCodeAction vsCodeAction, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, MSLSPMethods.TextDocumentCodeActionResolveName, + vsCodeAction, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(MSLSPMethods.DocumentPullDiagnosticName, UseSingleObjectParameterDeserialization = true)] + public Task GetDocumentPullDiagnosticsAsync(DocumentDiagnosticsParams diagnosticsParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync( + Queue, MSLSPMethods.DocumentPullDiagnosticName, + diagnosticsParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(MSLSPMethods.WorkspacePullDiagnosticName, UseSingleObjectParameterDeserialization = true)] + public Task GetWorkspacePullDiagnosticsAsync(WorkspaceDocumentDiagnosticsParams diagnosticsParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync( + Queue, MSLSPMethods.WorkspacePullDiagnosticName, + diagnosticsParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)] + public Task GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, MSLSPMethods.ProjectContextsName, + textDocumentWithContextParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(MSLSPMethods.OnAutoInsertName, UseSingleObjectParameterDeserialization = true)] + public Task GetDocumentOnAutoInsertAsync(DocumentOnAutoInsertParams autoInsertParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, MSLSPMethods.OnAutoInsertName, + autoInsertParams, _clientCapabilities, ClientName, cancellationToken); + } + + [JsonRpcMethod(MSLSPMethods.OnTypeRenameName, UseSingleObjectParameterDeserialization = true)] + public Task GetTypeRenameAsync(DocumentOnTypeRenameParams renameParams, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + + return RequestDispatcher.ExecuteRequestAsync(Queue, MSLSPMethods.OnTypeRenameName, + renameParams, _clientCapabilities, ClientName, cancellationToken); + } + + protected override void ShutdownImpl() + { + base.ShutdownImpl(); + if (_diagnosticService != null) + _diagnosticService.DiagnosticsUpdated -= DiagnosticService_DiagnosticsUpdated; + } + + private void DiagnosticService_DiagnosticsUpdated(object _, DiagnosticsUpdatedArgs e) + => DiagnosticService_DiagnosticsUpdated(e.Solution, e.DocumentId); + + private void DiagnosticService_DiagnosticsUpdated(Solution? solution, DocumentId? documentId) + { + // LSP doesn't support diagnostics without a document. So if we get project level diagnostics without a document, ignore them. + if (documentId != null && solution != null) + { + var document = solution.GetDocument(documentId); + if (document == null || document.FilePath == null) + return; + + // Only publish document diagnostics for the languages this provider supports. + if (document.Project.Language != CodeAnalysis.LanguageNames.CSharp && document.Project.Language != CodeAnalysis.LanguageNames.VisualBasic) + return; + + _diagnosticsWorkQueue.AddWork(document.Id); + } + } + + /// + /// Stores the last published LSP diagnostics with the Roslyn document that they came from. + /// This is useful in the following scenario. Imagine we have documentA which has contributions to mapped files m1 and m2. + /// dA -> m1 + /// And m1 has contributions from documentB. + /// m1 -> dA, dB + /// When we query for diagnostic on dA, we get a subset of the diagnostics on m1 (missing the contributions from dB) + /// Since each publish diagnostics notification replaces diagnostics per document, + /// we must union the diagnostics contribution from dB and dA to produce all diagnostics for m1 and publish all at once. + /// + /// This dictionary stores the previously computed diagnostics for the published file so that we can + /// union the currently computed diagnostics (e.g. for dA) with previously computed diagnostics (e.g. from dB). + /// + private readonly Dictionary>> _publishedFileToDiagnostics = new(); + + /// + /// Stores the mapping of a document to the uri(s) of diagnostics previously produced for this document. When + /// we get empty diagnostics for the document we need to find the uris we previously published for this + /// document. Then we can publish the updated diagnostics set for those uris (either empty or the diagnostic + /// contributions from other documents). We use a sorted set to ensure consistency in the order in which we + /// report URIs. While it's not necessary to publish a document's mapped file diagnostics in a particular + /// order, it does make it much easier to write tests and debug issues if we have a consistent ordering. + /// + private readonly Dictionary> _documentsToPublishedUris = new(); + + /// + /// Basic comparer for Uris used by when publishing notifications. + /// + private static readonly Comparer s_uriComparer = Comparer.Create((uri1, uri2) + => Uri.Compare(uri1, uri2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); + + // internal for testing purposes + internal async Task ProcessDiagnosticUpdatedBatchAsync( + IDiagnosticService? diagnosticService, ImmutableArray documentIds, CancellationToken cancellationToken) + { + if (diagnosticService == null) + return; + + foreach (var documentId in documentIds) + { + cancellationToken.ThrowIfCancellationRequested(); + var document = WorkspaceRegistrationService.GetAllRegistrations().Select(w => w.CurrentSolution.GetDocument(documentId)).FirstOrDefault(); + + if (document != null) + { + // If this is a `pull` client, and `pull` diagnostics is on, then we should not `publish` (push) the + // diagnostics here. + var diagnosticMode = document.IsRazorDocument() + ? InternalDiagnosticsOptions.RazorDiagnosticMode + : InternalDiagnosticsOptions.NormalDiagnosticMode; + if (document.Project.Solution.Workspace.IsPushDiagnostics(diagnosticMode)) + await PublishDiagnosticsAsync(diagnosticService, document, cancellationToken).ConfigureAwait(false); + } + } + } + + private async Task PublishDiagnosticsAsync(IDiagnosticService diagnosticService, Document document, CancellationToken cancellationToken) + { + // Retrieve all diagnostics for the current document grouped by their actual file uri. + var fileUriToDiagnostics = await GetDiagnosticsAsync(diagnosticService, document, cancellationToken).ConfigureAwait(false); + + // Get the list of file uris with diagnostics (for the document). + // We need to join the uris from current diagnostics with those previously published + // so that we clear out any diagnostics in mapped files that are no longer a part + // of the current diagnostics set (because the diagnostics were fixed). + // Use sorted set to have consistent publish ordering for tests and debugging. + var urisForCurrentDocument = _documentsToPublishedUris.GetValueOrDefault(document.Id, ImmutableSortedSet.Create(s_uriComparer)).Union(fileUriToDiagnostics.Keys); + + // Update the mapping for this document to be the uris we're about to publish diagnostics for. + _documentsToPublishedUris[document.Id] = urisForCurrentDocument; + + // Go through each uri and publish the updated set of diagnostics per uri. + foreach (var fileUri in urisForCurrentDocument) + { + // Get the updated diagnostics for a single uri that were contributed by the current document. + var diagnostics = fileUriToDiagnostics.GetValueOrDefault(fileUri, ImmutableArray.Empty); + + if (_publishedFileToDiagnostics.ContainsKey(fileUri)) + { + // Get all previously published diagnostics for this uri excluding those that were contributed from the current document. + // We don't need those since we just computed the updated values above. + var diagnosticsFromOtherDocuments = _publishedFileToDiagnostics[fileUri].Where(kvp => kvp.Key != document.Id).SelectMany(kvp => kvp.Value); + + // Since diagnostics are replaced per uri, we must publish both contributions from this document and any other document + // that has diagnostic contributions to this uri, so union the two sets. + diagnostics = diagnostics.AddRange(diagnosticsFromOtherDocuments); + } + + await SendDiagnosticsNotificationAsync(fileUri, diagnostics).ConfigureAwait(false); + + // There are three cases here -> + // 1. There are no diagnostics to publish for this fileUri. We no longer need to track the fileUri at all. + // 2. There are diagnostics from the current document. Store the diagnostics for the fileUri and document + // so they can be published along with contributions to the fileUri from other documents. + // 3. There are no diagnostics contributed by this document to the fileUri (could be some from other documents). + // We should clear out the diagnostics for this document for the fileUri. + if (diagnostics.IsEmpty) + { + // We published an empty set of diagnostics for this uri. We no longer need to keep track of this mapping + // since there will be no previous diagnostics that we need to clear out. + _documentsToPublishedUris.MultiRemove(document.Id, fileUri); + + // There are not any diagnostics to keep track of for this file, so we can stop. + _publishedFileToDiagnostics.Remove(fileUri); + } + else if (fileUriToDiagnostics.ContainsKey(fileUri)) + { + // We do have diagnostics from the current document - update the published diagnostics map + // to contain the new diagnostics contributed by this document for this uri. + var documentsToPublishedDiagnostics = _publishedFileToDiagnostics.GetOrAdd(fileUri, (_) => + new Dictionary>()); + documentsToPublishedDiagnostics[document.Id] = fileUriToDiagnostics[fileUri]; + } + else + { + // There were diagnostics from other documents, but none from the current document. + // If we're tracking the current document, we can stop. + IReadOnlyDictionaryExtensions.GetValueOrDefault(_publishedFileToDiagnostics, fileUri)?.Remove(document.Id); + _documentsToPublishedUris.MultiRemove(document.Id, fileUri); + } + } + } + + private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray diagnostics) + { + var publishDiagnosticsParams = new PublishDiagnosticParams { Diagnostics = diagnostics.ToArray(), Uri = uri }; + await JsonRpc.NotifyWithParameterObjectAsync(Methods.TextDocumentPublishDiagnosticsName, publishDiagnosticsParams).ConfigureAwait(false); + } + + private async Task>> GetDiagnosticsAsync( + IDiagnosticService diagnosticService, Document document, CancellationToken cancellationToken) + { + var option = document.IsRazorDocument() + ? InternalDiagnosticsOptions.RazorDiagnosticMode + : InternalDiagnosticsOptions.NormalDiagnosticMode; + var pushDiagnostics = await diagnosticService.GetPushDiagnosticsAsync(document.Project.Solution.Workspace, document.Project.Id, document.Id, id: null, includeSuppressedDiagnostics: false, option, cancellationToken).ConfigureAwait(false); + var diagnostics = pushDiagnostics.WhereAsArray(IncludeDiagnostic); + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // Retrieve diagnostics for the document. These diagnostics could be for the current document, or they could map + // to a different location in a different file. We need to publish the diagnostics for the mapped locations as well. + // An example of this is razor imports where the generated C# document maps to many razor documents. + // https://docs.microsoft.com/en-us/aspnet/core/mvc/views/layout?view=aspnetcore-3.1#importing-shared-directives + // https://docs.microsoft.com/en-us/aspnet/core/blazor/layouts?view=aspnetcore-3.1#centralized-layout-selection + // So we get the diagnostics and group them by the actual mapped path so we can publish notifications + // for each mapped file's diagnostics. + var fileUriToDiagnostics = diagnostics.GroupBy(diagnostic => GetDiagnosticUri(document, diagnostic)).ToDictionary( + group => group.Key, + group => group.Select(diagnostic => ConvertToLspDiagnostic(diagnostic, text)).ToImmutableArray()); + return fileUriToDiagnostics; + + static Uri GetDiagnosticUri(Document document, DiagnosticData diagnosticData) + { + Contract.ThrowIfNull(diagnosticData.DataLocation, "Diagnostic data location should not be null here"); + + // Razor wants to handle all span mapping themselves. So if we are in razor, return the raw doc spans, and + // do not map them. + var filePath = diagnosticData.DataLocation.MappedFilePath ?? diagnosticData.DataLocation.OriginalFilePath; + return ProtocolConversions.GetUriFromFilePath(filePath); + } + } + + private LSP.Diagnostic ConvertToLspDiagnostic(DiagnosticData diagnosticData, SourceText text) + { + Contract.ThrowIfNull(diagnosticData.DataLocation); + + var diagnostic = new LSP.Diagnostic + { + Source = TelemetryServerName, + Code = diagnosticData.Id, + Severity = Convert(diagnosticData.Severity), + Range = GetDiagnosticRange(diagnosticData.DataLocation, text), + // Only the unnecessary diagnostic tag is currently supported via LSP. + Tags = diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary) + ? new DiagnosticTag[] { DiagnosticTag.Unnecessary } + : Array.Empty() + }; + + if (diagnosticData.Message != null) + diagnostic.Message = diagnosticData.Message; + + return diagnostic; + } + + private static LSP.DiagnosticSeverity Convert(CodeAnalysis.DiagnosticSeverity severity) + => severity switch + { + CodeAnalysis.DiagnosticSeverity.Hidden => LSP.DiagnosticSeverity.Hint, + CodeAnalysis.DiagnosticSeverity.Info => LSP.DiagnosticSeverity.Hint, + CodeAnalysis.DiagnosticSeverity.Warning => LSP.DiagnosticSeverity.Warning, + CodeAnalysis.DiagnosticSeverity.Error => LSP.DiagnosticSeverity.Error, + _ => throw ExceptionUtilities.UnexpectedValue(severity), + }; + + // Some diagnostics only apply to certain clients and document types, e.g. Razor. + // If the DocumentPropertiesService.DiagnosticsLspClientName property exists, we only include the + // diagnostic if it directly matches the client name. + // If the DocumentPropertiesService.DiagnosticsLspClientName property doesn't exist, + // we know that the diagnostic we're working with is contained in a C#/VB file, since + // if we were working with a non-C#/VB file, then the property should have been populated. + // In this case, unless we have a null client name, we don't want to publish the diagnostic + // (since a null client name represents the C#/VB language server). + private bool IncludeDiagnostic(DiagnosticData diagnostic) + => IReadOnlyDictionaryExtensions.GetValueOrDefault(diagnostic.Properties, nameof(DocumentPropertiesService.DiagnosticsLspClientName)) == ClientName; + + private static LSP.Range GetDiagnosticRange(DiagnosticDataLocation diagnosticDataLocation, SourceText text) + { + var linePositionSpan = DiagnosticData.GetLinePositionSpan(diagnosticDataLocation, text, useMapped: true); + return ProtocolConversions.LinePositionToRange(linePositionSpan); + } + + internal TestAccessor GetTestAccessor() => new(this); + + internal readonly struct TestAccessor + { + private readonly VisualStudioInProcLanguageServer _server; + + internal TestAccessor(VisualStudioInProcLanguageServer server) + { + _server = server; + } + + internal ImmutableArray GetFileUrisInPublishDiagnostics() + => _server._publishedFileToDiagnostics.Keys.ToImmutableArray(); + + internal ImmutableArray GetDocumentIdsInPublishedUris() + => _server._documentsToPublishedUris.Keys.ToImmutableArray(); + + internal IImmutableSet GetFileUrisForDocument(DocumentId documentId) + => _server._documentsToPublishedUris.GetValueOrDefault(documentId, ImmutableSortedSet.Empty); + + internal ImmutableArray GetDiagnosticsForUriAndDocument(DocumentId documentId, Uri uri) + { + if (_server._publishedFileToDiagnostics.TryGetValue(uri, out var dict) && dict.TryGetValue(documentId, out var diagnostics)) + return diagnostics; + + return ImmutableArray.Empty; + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLspWorkspaceRegistrationEventListener.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLspWorkspaceRegistrationEventListener.cs index acd4a7e2a8901..e3c2fa57d25b0 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLspWorkspaceRegistrationEventListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLspWorkspaceRegistrationEventListener.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient { diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageBlock.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageBlock.cs index 5f3f607176921..ad6f2bb83af6e 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageBlock.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageBlock.cs @@ -5,14 +5,13 @@ #nullable disable using System.Threading; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; using Microsoft.VisualStudio.Text; - +using Microsoft.VisualStudio.Utilities; using IVsLanguageBlock = Microsoft.VisualStudio.TextManager.Interop.IVsLanguageBlock; using IVsTextLines = Microsoft.VisualStudio.TextManager.Interop.IVsTextLines; using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; @@ -41,14 +40,15 @@ public int GetCurrentBlock( (string description, TextSpan span)? foundBlock = null; - var waitIndicator = this.Package.ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = this.Package.ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( ServicesVSResources.Current_block, ServicesVSResources.Determining_current_block, - allowCancel: true, + allowCancellation: true, + showProgress: false, action: context => { - foundBlock = VsLanguageBlock.GetCurrentBlock(snapshot, position.Value, context.CancellationToken); + foundBlock = VsLanguageBlock.GetCurrentBlock(snapshot, position.Value, context.UserCancellationToken); }); pfBlockAvailable = foundBlock != null ? 1 : 0; diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs index 8a98700481f13..62b22fb67ba6b 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs @@ -7,13 +7,13 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using RoslynTextSpan = Microsoft.CodeAnalysis.Text.TextSpan; using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; @@ -26,12 +26,14 @@ internal abstract partial class AbstractLanguageService(); var result = VSConstants.S_OK; - waitIndicator.Wait( + var uiThreadOperationExecutor = this.Package.ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: true, - action: c => result = FormatWorker(textLayer, selections, c.CancellationToken)); + defaultDescription: "", + allowCancellation: true, + showProgress: false, + action: c => result = FormatWorker(textLayer, selections, c.UserCancellationToken)); return result; } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs index cbf7f92fbe353..2ef98bcb14226 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using IVsDebugName = Microsoft.VisualStudio.TextManager.Interop.IVsDebugName; using IVsEnumBSTR = Microsoft.VisualStudio.TextManager.Interop.IVsEnumBSTR; @@ -32,13 +33,13 @@ internal sealed class VsLanguageDebugInfo : IVsLanguageDebugInfo private readonly ILanguageDebugInfoService? _languageDebugInfo; private readonly IBreakpointResolutionService? _breakpointService; private readonly IProximityExpressionsService? _proximityExpressionsService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; public VsLanguageDebugInfo( Guid languageId, TLanguageService languageService, HostLanguageServices languageServiceProvider, - IWaitIndicator waitIndicator) + IUIThreadOperationExecutor uiThreadOperationExecutor) { Contract.ThrowIfNull(languageService); Contract.ThrowIfNull(languageServiceProvider); @@ -48,7 +49,7 @@ public VsLanguageDebugInfo( _languageDebugInfo = languageServiceProvider.GetService(); _breakpointService = languageServiceProvider.GetService(); _proximityExpressionsService = languageServiceProvider.GetService(); - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public int GetLanguageID(IVsTextBuffer pBuffer, int iLine, int iCol, out Guid pguidLanguageID) @@ -73,38 +74,39 @@ public int GetNameOfLocation(IVsTextBuffer pBuffer, int iLine, int iCol, out str if (_languageDebugInfo != null) { - _waitIndicator.Wait( - title: ServicesVSResources.Debugger, - message: ServicesVSResources.Determining_breakpoint_location, - allowCancel: true, - action: waitContext => - { - var cancellationToken = waitContext.CancellationToken; - var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); - if (textBuffer != null) + _uiThreadOperationExecutor.Execute( + title: ServicesVSResources.Debugger, + defaultDescription: ServicesVSResources.Determining_breakpoint_location, + allowCancellation: true, + showProgress: false, + action: waitContext => { - var nullablePoint = textBuffer.CurrentSnapshot.TryGetPoint(iLine, iCol); - if (nullablePoint.HasValue) + var cancellationToken = waitContext.UserCancellationToken; + var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); + if (textBuffer != null) { - var point = nullablePoint.Value; - var document = point.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); - - if (document != null) + var nullablePoint = textBuffer.CurrentSnapshot.TryGetPoint(iLine, iCol); + if (nullablePoint.HasValue) { - // NOTE(cyrusn): We have to wait here because the debuggers' - // GetNameOfLocation is a blocking call. In the future, it - // would be nice if they could make it async. - var debugLocationInfo = _languageDebugInfo.GetLocationInfoAsync(document, point, cancellationToken).WaitAndGetResult(cancellationToken); + var point = nullablePoint.Value; + var document = point.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (!debugLocationInfo.IsDefault) + if (document != null) { - name = debugLocationInfo.Name; - lineOffset = debugLocationInfo.LineOffset; + // NOTE(cyrusn): We have to wait here because the debuggers' + // GetNameOfLocation is a blocking call. In the future, it + // would be nice if they could make it async. + var debugLocationInfo = _languageDebugInfo.GetLocationInfoAsync(document, point, cancellationToken).WaitAndGetResult(cancellationToken); + + if (!debugLocationInfo.IsDefault) + { + name = debugLocationInfo.Name; + lineOffset = debugLocationInfo.LineOffset; + } } } } - } - }); + }); if (name != null) { @@ -131,11 +133,12 @@ public int GetProximityExpressions(IVsTextBuffer pBuffer, int iLine, int iCol, i if (_proximityExpressionsService != null) { - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( title: ServicesVSResources.Debugger, - message: ServicesVSResources.Determining_autos, - allowCancel: true, - action: waitContext => + defaultDescription: ServicesVSResources.Determining_autos, + allowCancellation: true, + showProgress: false, + action: context => { var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); @@ -149,7 +152,7 @@ public int GetProximityExpressions(IVsTextBuffer pBuffer, int iLine, int iCol, i if (document != null) { var point = nullablePoint.Value; - var proximityExpressions = _proximityExpressionsService.GetProximityExpressionsAsync(document, point.Position, waitContext.CancellationToken).WaitAndGetResult(waitContext.CancellationToken); + var proximityExpressions = _proximityExpressionsService.GetProximityExpressionsAsync(document, point.Position, context.UserCancellationToken).WaitAndGetResult(context.UserCancellationToken); if (proximityExpressions != null) { @@ -184,13 +187,14 @@ public int ResolveName(string pszName, uint dwFlags, out IVsEnumDebugName? ppNam } VsEnumDebugName? enumName = null; - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( title: ServicesVSResources.Debugger, - message: ServicesVSResources.Resolving_breakpoint_location, - allowCancel: true, + defaultDescription: ServicesVSResources.Resolving_breakpoint_location, + allowCancellation: true, + showProgress: false, action: waitContext => { - var cancellationToken = waitContext.CancellationToken; + var cancellationToken = waitContext.UserCancellationToken; if (dwFlags == (uint)RESOLVENAMEFLAGS.RNF_BREAKPOINT) { var solution = _languageService.Workspace.CurrentSolution; @@ -233,13 +237,14 @@ public int ValidateBreakpointLocation(IVsTextBuffer pBuffer, int iLine, int iCol using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ValidateBreakpointLocation, CancellationToken.None)) { var result = VSConstants.E_NOTIMPL; - _waitIndicator.Wait( + _uiThreadOperationExecutor.Execute( title: ServicesVSResources.Debugger, - message: ServicesVSResources.Validating_breakpoint_location, - allowCancel: true, + defaultDescription: ServicesVSResources.Validating_breakpoint_location, + allowCancellation: true, + showProgress: false, action: waitContext => { - result = ValidateBreakpointLocationWorker(pBuffer, iLine, iCol, pCodeSpan, waitContext.CancellationToken); + result = ValidateBreakpointLocationWorker(pBuffer, iLine, iCol, pCodeSpan, waitContext.UserCancellationToken); }); return result; diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs index 788540a77f02f..87da60aa58d4a 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs @@ -7,13 +7,10 @@ using System; using System.Diagnostics; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.Structure; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; -using Microsoft.CodeAnalysis.Editor.Structure; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -23,11 +20,10 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Outlining; -using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService @@ -226,9 +222,7 @@ private void ConditionallyCollapseOutliningRegions(IVsTextView textView, IWpfTex var outliningManagerService = this.Package.ComponentModel.GetService(); var outliningManager = outliningManagerService.GetOutliningManager(wpfTextView); if (outliningManager == null) - { return; - } if (!workspace.Options.GetOption(FeatureOnOffOptions.Outlining, this.RoslynLanguageName)) { @@ -240,73 +234,21 @@ private void ConditionallyCollapseOutliningRegions(IVsTextView textView, IWpfTex { if (isOpenMetadataAsSource) { - // If this file is a metadata-from-source file, we want to force-collapse any implementations. - // First make sure we know what all the outlining spans are. Then ask the outlining mananger - // to collapse all the implementation spans. - EnsureOutliningTagsComputed(wpfTextView); + // If this file is a metadata-from-source file, we want to force-collapse any implementations + // to keep the display clean and condensed. outliningManager.CollapseAll(wpfTextView.TextBuffer.CurrentSnapshot.GetFullSpan(), c => c.Tag.IsImplementation); } else { - // We also want to automatically collapse any region tags *on the first - // load of a file* if the file contains them. In order to not do expensive - // parsing, we only do this if the file contains #region in it. - if (ContainsRegionTag(wpfTextView.TextSnapshot)) - { - // Make sure we at least know what the outlining spans are. - // Then when we call PersistOutliningState below the editor will - // get these outlining tags and automatically collapse any - // IsDefaultCollapsed spans the first time around. - // - // If it is not the first time opening a file, VS will simply use - // the data stored in the SUO file. - EnsureOutliningTagsComputed(wpfTextView); - } - + // Otherwise, attempt to persist any outlining state we have computed. This + // ensures that any new opened files that have any IsDefaultCollapsed spans + // will both have them collapsed and remembered in the SUO file. viewEx.PersistOutliningState(); } } } } - private bool ContainsRegionTag(ITextSnapshot textSnapshot) - { - foreach (var line in textSnapshot.Lines) - { - if (StartsWithRegionTag(line)) - { - return true; - } - } - - return false; - } - - private bool StartsWithRegionTag(ITextSnapshotLine line) - { - var start = line.GetFirstNonWhitespacePosition(); - if (start != null) - { - var index = start.Value; - return line.StartsWith(index, "#region", ignoreCase: true); - } - - return false; - } - - private void EnsureOutliningTagsComputed(IWpfTextView wpfTextView) - { - // We need to get our outlining tag source to notify it to start blocking - var outliningTaggerProvider = this.Package.ComponentModel.GetService(); - - var subjectBuffer = wpfTextView.TextBuffer; - var snapshot = subjectBuffer.CurrentSnapshot; - var tagger = outliningTaggerProvider.CreateTagger(subjectBuffer); - - using var disposable = tagger as IDisposable; - tagger.GetAllTags(new NormalizedSnapshotSpanCollection(snapshot.GetFullSpan()), CancellationToken.None); - } - private void InitializeLanguageDebugInfo() => this.LanguageDebugInfo = this.CreateLanguageDebugInfo(); @@ -321,7 +263,7 @@ private VsLanguageDebugInfo CreateLanguageDebugInfo() this.DebuggerLanguageId, (TLanguageService)this, languageServices, - this.Package.ComponentModel.GetService()); + this.Package.ComponentModel.GetService()); } private void UninitializeLanguageDebugInfo() diff --git a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs index 55957c105f082..5bca469a14289 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs @@ -493,7 +493,7 @@ protected override bool TryExec(Guid commandGroup, uint commandId) // asynchronously added to the FindReferences window as they are computed. The user // also knows something is happening as the window, with the progress-banner will pop up // immediately. - _ = FindReferencesAsync(_streamingPresenter, symbolListItem, project, CancellationToken.None); + _ = FindReferencesAsync(_streamingPresenter, symbolListItem, project); return true; } } @@ -506,27 +506,26 @@ protected override bool TryExec(Guid commandGroup, uint commandId) } private async Task FindReferencesAsync( - IStreamingFindUsagesPresenter presenter, SymbolListItem symbolListItem, Project project, CancellationToken cancellationToken) + IStreamingFindUsagesPresenter presenter, SymbolListItem symbolListItem, Project project) { try { // Let the presented know we're starting a search. It will give us back the context object that the FAR - // service will push results into. - var context = presenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true, cancellationToken); + // service will push results into. Because we kicked off this work in a fire and forget fashion, + // the presenter owns canceling this work (i.e. if it's closed or if another FAR request is made). + var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true); try { // Kick off the work to do the actual finding on a BG thread. That way we don' // t block the calling (UI) thread too long if we happen to do our work on this // thread. - await Task.Run(async () => - { - await FindReferencesAsync(symbolListItem, project, context).ConfigureAwait(false); - }, cancellationToken).ConfigureAwait(false); + await Task.Run( + () => FindReferencesAsync(symbolListItem, project, context, cancellationToken), cancellationToken).ConfigureAwait(false); } finally { - await context.OnCompletedAsync().ConfigureAwait(false); + await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -537,12 +536,14 @@ await Task.Run(async () => } } - private static async Task FindReferencesAsync(SymbolListItem symbolListItem, Project project, CodeAnalysis.FindUsages.FindUsagesContext context) + private static async Task FindReferencesAsync( + SymbolListItem symbolListItem, Project project, + CodeAnalysis.FindUsages.FindUsagesContext context, CancellationToken cancellationToken) { - var compilation = await project.GetCompilationAsync(context.CancellationToken).ConfigureAwait(false); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var symbol = symbolListItem.ResolveSymbol(compilation); if (symbol != null) - await AbstractFindUsagesService.FindSymbolReferencesAsync(context, symbol, project).ConfigureAwait(false); + await AbstractFindUsagesService.FindSymbolReferencesAsync(context, symbol, project, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs b/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs index f70c5d55544d7..49ac0e51e673d 100644 --- a/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.Internal.VisualStudio.Shell; @@ -127,7 +128,7 @@ int IVsDropdownBarClient.GetComboAttributes(int iCombo, out uint pcEntries, out var currentTypeItem = GetCurrentTypeItem(); pcEntries = currentTypeItem != null - ? (uint)currentTypeItem.ChildItems.Count + ? (uint)currentTypeItem.ChildItems.Length : 0; break; @@ -321,9 +322,9 @@ void INavigationBarPresenter.Disconnect() } void INavigationBarPresenter.PresentItems( - IList projects, + ImmutableArray projects, NavigationBarProjectItem selectedProject, - IList types, + ImmutableArray types, NavigationBarItem selectedType, NavigationBarItem selectedMember) { diff --git a/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml b/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml index 496a5a5c7b62e..ea6dce3a10f54 100644 --- a/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml +++ b/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml @@ -45,12 +45,17 @@ + + @@ -153,7 +158,7 @@ - diff --git a/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml.cs b/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml.cs index 69d57f1773718..be9ff12717a86 100644 --- a/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml.cs +++ b/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialog.xaml.cs @@ -58,6 +58,12 @@ private void SetCommandBindings() Deselect_All_Click)); } + private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + _viewModel.Filter(SearchTextBox.Text); + Members.Items.Refresh(); + } + private void OK_Click(object sender, RoutedEventArgs e) => DialogResult = true; diff --git a/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialogViewModel.cs b/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialogViewModel.cs index 1dfd9143512d9..11d5bff688a50 100644 --- a/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/PickMembers/PickMembersDialogViewModel.cs @@ -2,50 +2,76 @@ // 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 System.Collections.Immutable; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PickMembers; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; +using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers { internal class PickMembersDialogViewModel : AbstractNotifyPropertyChanged { + private readonly List _allMembers; + public List MemberContainers { get; set; } public List Options { get; set; } + /// + /// if 'Select All' was chosen. if 'Deselect All' was chosen. + /// + public bool SelectedAll { get; set; } + internal PickMembersDialogViewModel( IGlyphService glyphService, ImmutableArray members, - ImmutableArray options) + ImmutableArray options, + bool selectAll) { - MemberContainers = members.Select(m => new MemberSymbolViewModel(m, glyphService)).ToList(); + _allMembers = members.Select(m => new MemberSymbolViewModel(m, glyphService)).ToList(); + MemberContainers = _allMembers; Options = options.Select(o => new OptionViewModel(o)).ToList(); + + if (selectAll) + { + SelectAll(); + } + else + { + DeselectAll(); + } + } + + internal void Filter(string searchText) + { + searchText = searchText.Trim(); + MemberContainers = searchText.Length == 0 + ? _allMembers + : _allMembers.Where(m => m.SymbolAutomationText.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0).ToList(); + NotifyPropertyChanged(nameof(MemberContainers)); } internal void DeselectAll() { + SelectedAll = false; foreach (var memberContainer in MemberContainers) - { memberContainer.IsChecked = false; - } } internal void SelectAll() { + SelectedAll = true; foreach (var memberContainer in MemberContainers) - { memberContainer.IsChecked = true; - } } private int? _selectedIndex; + public int? SelectedIndex { get @@ -96,6 +122,7 @@ public string MoveDownAutomationText } } + [MemberNotNullWhen(true, nameof(SelectedIndex))] public bool CanMoveUp { get @@ -110,6 +137,7 @@ public bool CanMoveUp } } + [MemberNotNullWhen(true, nameof(SelectedIndex))] public bool CanMoveDown { get @@ -126,7 +154,7 @@ public bool CanMoveDown internal void MoveUp() { - Debug.Assert(CanMoveUp); + Contract.ThrowIfFalse(CanMoveUp); var index = SelectedIndex.Value; Move(MemberContainers, index, delta: -1); @@ -134,7 +162,7 @@ internal void MoveUp() internal void MoveDown() { - Debug.Assert(CanMoveDown); + Contract.ThrowIfFalse(CanMoveDown); var index = SelectedIndex.Value; Move(MemberContainers, index, delta: 1); diff --git a/src/VisualStudio/Core/Def/Implementation/PickMembers/VisualStudioPickMembersService.cs b/src/VisualStudio/Core/Def/Implementation/PickMembers/VisualStudioPickMembersService.cs index f363bb2669f29..efe4d4eba8021 100644 --- a/src/VisualStudio/Core/Def/Implementation/PickMembers/VisualStudioPickMembersService.cs +++ b/src/VisualStudio/Core/Def/Implementation/PickMembers/VisualStudioPickMembersService.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.Collections.Immutable; using System.Composition; @@ -26,21 +24,25 @@ public VisualStudioPickMembersService(IGlyphService glyphService) => _glyphService = glyphService; public PickMembersResult PickMembers( - string title, ImmutableArray members, ImmutableArray options) + string title, + ImmutableArray members, + ImmutableArray options, + bool selectAll) { options = options.NullToEmpty(); - var viewModel = new PickMembersDialogViewModel(_glyphService, members, options); + var viewModel = new PickMembersDialogViewModel(_glyphService, members, options, selectAll); var dialog = new PickMembersDialog(viewModel, title); var result = dialog.ShowModal(); - if (result.HasValue && result.Value) + if (result == true) { return new PickMembersResult( viewModel.MemberContainers.Where(c => c.IsChecked) .Select(c => c.Symbol) .ToImmutableArray(), - options); + options, + viewModel.SelectedAll); } else { diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs index 58af6780b309c..4f8626212699e 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs @@ -70,7 +70,7 @@ public void NavigateTo(GraphObject graphObject) { // If we are already on the UI thread, invoke NavigateOnForegroundThread // directly to preserve any existing NewDocumentStateScope. - NavigateOnForegroundThread(sourceLocation, symbolId, project, document); + NavigateOnForegroundThread(sourceLocation, symbolId, project, document, CancellationToken.None); } else { @@ -82,7 +82,7 @@ public void NavigateTo(GraphObject graphObject) async () => { await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - NavigateOnForegroundThread(sourceLocation, symbolId, project, document); + NavigateOnForegroundThread(sourceLocation, symbolId, project, document, CancellationToken.None); }, CancellationToken.None, TaskScheduler.Default); @@ -92,7 +92,7 @@ public void NavigateTo(GraphObject graphObject) } private void NavigateOnForegroundThread( - SourceLocation sourceLocation, SymbolKey? symbolId, Project project, Document document) + SourceLocation sourceLocation, SymbolKey? symbolId, Project project, Document document, CancellationToken cancellationToken) { AssertIsForeground(); @@ -100,13 +100,13 @@ private void NavigateOnForegroundThread( if (symbolId != null) { var symbolNavigationService = _workspace.Services.GetService(); - var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None)).Symbol; + var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken), cancellationToken: cancellationToken).Symbol; // Do not allow third party navigation to types or constructors if (symbol != null && - !(symbol is ITypeSymbol) && + symbol is not ITypeSymbol && !symbol.IsConstructor() && - symbolNavigationService.TrySymbolNavigationNotify(symbol, project, CancellationToken.None)) + symbolNavigationService.TrySymbolNavigationNotifyAsync(symbol, project, cancellationToken).WaitAndGetResult(cancellationToken)) { return; } @@ -129,14 +129,13 @@ private void NavigateOnForegroundThread( document.Id, sourceLocation.StartPosition.Line, sourceLocation.StartPosition.Character, - CancellationToken.None); + cancellationToken); } } } public int GetRank(GraphObject graphObject) { - if (graphObject is GraphNode graphNode) { var sourceLocation = graphNode.GetValue(CodeNodeProperties.SourceLocation); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs index 240a399223658..bae5d127b04f7 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.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.Threading; using System.Threading.Tasks; @@ -17,7 +15,11 @@ internal interface IWorkspaceProjectContextFactory { /// [Obsolete("Use CreateProjectContextAsync instead")] - IWorkspaceProjectContext CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath); + IWorkspaceProjectContext CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath); + + /// + [Obsolete("Use CreateProjectContextAsync instead")] + IWorkspaceProjectContext CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath, string? assemblyName); /// /// Creates and initializes a new Workspace project and returns a Unique name for the project. /// Full path to the project file for the project. /// Project guid. - /// Obsolete. The argument is ignored. + /// The IVsHierarchy for the project; this is used to track linked files across multiple projects when determining contexts. /// Initial project binary output path. - Task CreateProjectContextAsync(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath, CancellationToken cancellationToken); + Task CreateProjectContextAsync( + string languageName, + string projectUniqueName, + string projectFilePath, + Guid projectGuid, + object? hierarchy, + string? binOutputPath, + string? assemblyName, + CancellationToken cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs index c33167f72bcb3..b77308868e46e 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs @@ -8,7 +8,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.TestHooks; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { @@ -177,8 +179,12 @@ private void IncludeUpdated(object sender, string fileChanged) // waiting for the foreground thread to release its lock on the file change service. // To avoid this, just queue up a Task to do the work on the foreground thread later, after // the lock on the file change service has been released. - _ruleSetManager._foregroundNotificationService.RegisterNotification( - () => IncludeUpdateCore(), _ruleSetManager._listener.BeginAsyncOperation("IncludeUpdated"), _disposalToken); + _ruleSetManager._threadingContext.JoinableTaskFactory.RunAsync(async () => + { + using var _ = _ruleSetManager._listener.BeginAsyncOperation("IncludeUpdated"); + await _ruleSetManager._threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, _disposalToken); + IncludeUpdateCore(); + }); } private void IncludeUpdateCore() diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs index a0734e94a6fa2..e556902faaed3 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs @@ -4,7 +4,7 @@ #nullable disable -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.TestHooks; using Roslyn.Utilities; @@ -13,17 +13,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { internal sealed partial class VisualStudioRuleSetManager : IWorkspaceService { + private readonly IThreadingContext _threadingContext; private readonly FileChangeWatcher _fileChangeWatcher; - private readonly IForegroundNotificationService _foregroundNotificationService; private readonly IAsynchronousOperationListener _listener; private readonly ReferenceCountedDisposableCache _ruleSetFileMap = new(); public VisualStudioRuleSetManager( - FileChangeWatcher fileChangeWatcher, IForegroundNotificationService foregroundNotificationService, IAsynchronousOperationListener listener) + IThreadingContext threadingContext, + FileChangeWatcher fileChangeWatcher, + IAsynchronousOperationListener listener) { + _threadingContext = threadingContext; _fileChangeWatcher = fileChangeWatcher; - _foregroundNotificationService = foregroundNotificationService; _listener = listener; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs index f218e47ec7bc3..b12dda642a839 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs @@ -2,41 +2,35 @@ // 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 System.Composition; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { [ExportWorkspaceServiceFactory(typeof(VisualStudioRuleSetManager), ServiceLayer.Host), Shared] internal sealed class VisualStudioRuleSetManagerFactory : IWorkspaceServiceFactory { + private readonly IThreadingContext _threadingContext; private readonly FileChangeWatcherProvider _fileChangeWatcherProvider; - private readonly IForegroundNotificationService _foregroundNotificationService; private readonly IAsynchronousOperationListener _listener; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioRuleSetManagerFactory( + IThreadingContext threadingContext, FileChangeWatcherProvider fileChangeWatcherProvider, - IForegroundNotificationService foregroundNotificationService, IAsynchronousOperationListenerProvider listenerProvider) { + _threadingContext = threadingContext; _fileChangeWatcherProvider = fileChangeWatcherProvider; - _foregroundNotificationService = foregroundNotificationService; _listener = listenerProvider.GetListener(FeatureAttribute.RuleSetEditor); } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new VisualStudioRuleSetManager(_fileChangeWatcherProvider.Watcher, _foregroundNotificationService, _listener); + => new VisualStudioRuleSetManager(_threadingContext, _fileChangeWatcherProvider.Watcher, _listener); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index b736ee94f4fc6..cecf5cae341f3 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -693,20 +693,11 @@ internal override void ApplyMappedFileChanges(SolutionChanges solutionChanges) } var newDocument = projectChanges.NewProject.GetRequiredDocument(changedDocumentId); - var textChanges = (await newDocument.GetTextChangesAsync(oldDocument, CancellationToken.None).ConfigureAwait(false)).ToImmutableArray(); - var mappedSpanResults = await mappingService.MapSpansAsync(oldDocument, textChanges.Select(tc => tc.Span), CancellationToken.None).ConfigureAwait(false); - - Contract.ThrowIfFalse(mappedSpanResults.Length == textChanges.Length); - - for (var i = 0; i < mappedSpanResults.Length; i++) + var mappedTextChanges = await mappingService.GetMappedTextChangesAsync( + oldDocument, newDocument, CancellationToken.None).ConfigureAwait(false); + foreach (var (filePath, textChange) in mappedTextChanges) { - // Only include changes that could be mapped. - var newText = textChanges[i].NewText; - if (!mappedSpanResults[i].IsDefault && newText != null) - { - var newTextChange = new TextChange(mappedSpanResults[i].Span, newText); - filePathToMappedTextChanges.Add(mappedSpanResults[i].FilePath, (newTextChange, projectChanges.ProjectId)); - } + filePathToMappedTextChanges.Add(filePath, (textChange, projectChanges.ProjectId)); } } } @@ -749,18 +740,15 @@ protected override void ApplyProjectReferenceAdded( private OleInterop.IOleUndoManager? TryGetUndoManager() { - var documentTrackingService = this.Services.GetService(); - if (documentTrackingService != null) + var documentTrackingService = this.Services.GetRequiredService(); + var documentId = documentTrackingService.TryGetActiveDocument() ?? documentTrackingService.GetVisibleDocuments().FirstOrDefault(); + if (documentId != null) { - var documentId = documentTrackingService.TryGetActiveDocument() ?? documentTrackingService.GetVisibleDocuments().FirstOrDefault(); - if (documentId != null) - { - var composition = (IComponentModel)ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel)); - var exportProvider = composition.DefaultExportProvider; - var editorAdaptersService = exportProvider.GetExportedValue(); + var composition = (IComponentModel)ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel)); + var exportProvider = composition.DefaultExportProvider; + var editorAdaptersService = exportProvider.GetExportedValue(); - return editorAdaptersService.TryGetUndoManager(this, documentId, CancellationToken.None); - } + return editorAdaptersService.TryGetUndoManager(this, documentId, CancellationToken.None); } return null; diff --git a/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs b/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs index ffae9c56d55b9..92e1880510208 100644 --- a/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.PullMemberUp; using Microsoft.VisualStudio.LanguageServices.Implementation.CommonControls; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog { @@ -25,23 +26,21 @@ internal class PullMemberUpDialogViewModel : AbstractNotifyPropertyChanged private bool _selectAllCheckBoxThreeStateEnable; private bool? _selectAllCheckBoxState; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly ImmutableDictionary>> _symbolToDependentsMap; - private readonly ImmutableDictionary _symbolToMemberViewMap; private bool _okButtonEnabled; public PullMemberUpDialogViewModel( - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, ImmutableArray members, BaseTypeTreeNodeViewModel destinationTreeViewModel, ImmutableDictionary>> dependentsMap) { - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _symbolToDependentsMap = dependentsMap; - _symbolToMemberViewMap = members.ToImmutableDictionary(memberViewModel => memberViewModel.Symbol); MemberSelectionViewModel = new MemberSelectionViewModel( - _waitIndicator, + _uiThreadOperationExecutor, members, _symbolToDependentsMap); @@ -88,7 +87,7 @@ public PullMembersUpOptions CreatePullMemberUpOptions() private void EnableOrDisableOkButton() { var selectedMembers = MemberSelectionViewModel.CheckedMembers; - OkButtonEnabled = SelectedDestination != null && selectedMembers.Any(); + OkButtonEnabled = SelectedDestination != DestinationTreeNodeViewModel && selectedMembers.Any(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/PullMemberUp/VisualStudioPullMemberUpService.cs b/src/VisualStudio/Core/Def/Implementation/PullMemberUp/VisualStudioPullMemberUpService.cs index 303b4c3d13bd7..03457d353ed60 100644 --- a/src/VisualStudio/Core/Def/Implementation/PullMemberUp/VisualStudioPullMemberUpService.cs +++ b/src/VisualStudio/Core/Def/Implementation/PullMemberUp/VisualStudioPullMemberUpService.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp @@ -24,14 +25,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp internal class VisualStudioPullMemberUpService : IPullMemberUpOptionsService { private readonly IGlyphService _glyphService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioPullMemberUpService(IGlyphService glyphService, IWaitIndicator waitIndicator) + public VisualStudioPullMemberUpService(IGlyphService glyphService, IUIThreadOperationExecutor uiThreadOperationExecutor) { _glyphService = glyphService; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; } public PullMembersUpOptions GetPullMemberUpOptions(Document document, ISymbol selectedMember) @@ -56,7 +57,7 @@ public PullMembersUpOptions GetPullMemberUpOptions(Document document, ISymbol se selectedMember.ContainingType, cancellationTokenSource.Token); var memberToDependentsMap = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, document.Project, cancellationTokenSource.Token); - var viewModel = new PullMemberUpDialogViewModel(_waitIndicator, memberViewModels, baseTypeRootViewModel, memberToDependentsMap); + var viewModel = new PullMemberUpDialogViewModel(_uiThreadOperationExecutor, memberViewModels, baseTypeRootViewModel, memberToDependentsMap); var dialog = new PullMemberUpDialog(viewModel); var result = dialog.ShowModal(); diff --git a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs index 06322b7964a41..8b583bbcccb9c 100644 --- a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs +++ b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/VisualStudioSemanticClassificationCacheService.cs @@ -9,10 +9,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Editor.Implementation.Classification; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; @@ -57,12 +57,9 @@ public async Task> GetCachedSemanticClassificatio if (!classifiedSpans.HasValue || classifiedSpans.Value == null) return default; - var list = ClassificationUtilities.GetOrCreateClassifiedSpanList(); - classifiedSpans.Value.Rehydrate(list); - - var result = list.ToImmutableArray(); - ClassificationUtilities.ReturnClassifiedSpanList(list); - return result; + using var _ = ArrayBuilder.GetInstance(out var result); + classifiedSpans.Value.Rehydrate(result); + return result.ToImmutable(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs index d05d04721374c..5f5fda2abed13 100644 --- a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs @@ -535,7 +535,7 @@ private bool TryInsertArgumentCompletionSnippet(SnapshotSpan triggerSpan, Snapsh { // This is the method name as it appears in source text var methodName = dataBufferSpan.GetText(); - var snippet = CreateMethodCallSnippet(methodName, includeMethod: true, ImmutableArray.Empty, ImmutableDictionary.Empty, cancellationToken); + var snippet = CreateMethodCallSnippet(methodName, includeMethod: true, ImmutableArray.Empty, ImmutableDictionary.Empty); var doc = new DOMDocumentClass(); if (doc.loadXML(snippet.ToString(SaveOptions.OmitDuplicateNamespaces))) @@ -613,7 +613,7 @@ static void EnsureRegisteredForModelUpdatedEvents(AbstractSnippetExpansionClient /// The parameters to the method. If the specific target of the invocation is not /// known, an empty array may be passed to create a template with a placeholder where arguments will eventually /// go. - private static XDocument CreateMethodCallSnippet(string methodName, bool includeMethod, ImmutableArray parameters, ImmutableDictionary parameterValues, CancellationToken cancellationToken) + private static XDocument CreateMethodCallSnippet(string methodName, bool includeMethod, ImmutableArray parameters, ImmutableDictionary parameterValues) { XNamespace snippetNamespace = "http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"; @@ -657,9 +657,11 @@ private static XDocument CreateMethodCallSnippet(string methodName, bool include if (includeMethod) { - template.Append(")$end$"); + template.Append(')'); } + template.Append("$end$"); + // A snippet is manually constructed. Replacement fields are added for each argument, and the field name // matches the parameter name. // https://docs.microsoft.com/en-us/visualstudio/ide/code-snippets-schema-reference?view=vs-2019 @@ -896,7 +898,7 @@ public void MoveToSpecificMethod(IMethodSymbol method, CancellationToken cancell newArguments = newArguments.SetItem(parameter.Name, value); } - var snippet = CreateMethodCallSnippet(method.Name, includeMethod: false, method.Parameters, newArguments, cancellationToken); + var snippet = CreateMethodCallSnippet(method.Name, includeMethod: false, method.Parameters, newArguments); var doc = new DOMDocumentClass(); if (doc.loadXML(snippet.ToString(SaveOptions.OmitDuplicateNamespaces))) { diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs index e7a82bea149e2..571a6c22a2773 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs @@ -194,34 +194,6 @@ protected bool TryNavigateToItem(int index, bool previewTab, bool activate, Canc return TryNavigateTo(workspace, documentId, position, previewTab, activate, cancellationToken); } - protected static string GetFileName(string original, string mapped) - => mapped == null ? original : original == null ? mapped : Combine(original, mapped); - - private static string Combine(string path1, string path2) - { - if (TryCombine(path1, path2, out var result)) - { - return result; - } - - return string.Empty; - } - - public static bool TryCombine(string path1, string path2, out string result) - { - try - { - // don't throw exception when either path1 or path2 contains illegal path char - result = System.IO.Path.Combine(path1, path2); - return true; - } - catch - { - result = null; - return false; - } - } - // we don't use these #pragma warning disable IDE0060 // Remove unused parameter - Implements interface method for sub-type public object Identity(int index) diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs index 61e75aa6403de..f2c4b27c28f4f 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.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 Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Suppression @@ -17,22 +15,22 @@ internal interface IVisualStudioSuppressionFixService /// /// Adds source suppressions for all the diagnostics in the error list, i.e. baseline all active issues. /// - /// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed. - bool AddSuppressions(IVsHierarchy projectHierarchyOpt); + /// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed. + bool AddSuppressions(IVsHierarchy? projectHierarchy); /// /// Adds source suppressions for diagnostics. /// /// If true, then only the currently selected entries in the error list will be suppressed. Otherwise, all suppressable entries in the error list will be suppressed. /// If true, then suppressions will be generated inline in the source file. Otherwise, they will be generated in a separate global suppressions file. - /// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed. - bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt); + /// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed. + bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy? projectHierarchy); /// /// Removes source suppressions for suppressed diagnostics. /// /// If true, then only the currently selected entries in the error list will be unsuppressed. Otherwise, all unsuppressable entries in the error list will be unsuppressed. - /// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be unsuppressed. - bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt); + /// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be unsuppressed. + bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy? projectHierarchy); } } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs index 5bfec0247333b..13ba5f7493112 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs @@ -9,6 +9,7 @@ using System.ComponentModel.Composition; using System.ComponentModel.Design; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes.Configuration; @@ -16,12 +17,14 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Implementation; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Suppression; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.TableControl; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using Task = System.Threading.Tasks.Task; @@ -33,7 +36,7 @@ internal partial class VisualStudioDiagnosticListTableCommandHandler private readonly VisualStudioWorkspace _workspace; private readonly VisualStudioSuppressionFixService _suppressionFixService; private readonly VisualStudioDiagnosticListSuppressionStateService _suppressionStateService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ICodeActionEditHandlerService _editHandlerService; private readonly IWpfTableControl _tableControl; @@ -45,14 +48,14 @@ public VisualStudioDiagnosticListTableCommandHandler( VisualStudioWorkspace workspace, IVisualStudioSuppressionFixService suppressionFixService, IVisualStudioDiagnosticListSuppressionStateService suppressionStateService, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, IDiagnosticAnalyzerService diagnosticService, ICodeActionEditHandlerService editHandlerService) { _workspace = workspace; _suppressionFixService = (VisualStudioSuppressionFixService)suppressionFixService; _suppressionStateService = (VisualStudioDiagnosticListSuppressionStateService)suppressionStateService; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _diagnosticService = diagnosticService; _editHandlerService = editHandlerService; @@ -173,24 +176,26 @@ private void SetSeverityHandler(object sender, EventArgs args) var pathToAnalyzerConfigDoc = TryGetPathToAnalyzerConfigDoc(selectedDiagnostic, out var project); if (pathToAnalyzerConfigDoc != null) { - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( title: ServicesVSResources.Updating_severity, - message: ServicesVSResources.Updating_severity, - allowCancel: true, - action: waitContext => + defaultDescription: ServicesVSResources.Updating_severity, + allowCancellation: true, + showProgress: true, + action: context => { - var newSolution = ConfigureSeverityAsync(waitContext).WaitAndGetResult(waitContext.CancellationToken); + var newSolution = ConfigureSeverityAsync(context.UserCancellationToken).WaitAndGetResult(context.UserCancellationToken); var operations = ImmutableArray.Create(new ApplyChangesOperation(newSolution)); + var scope = context.AddScope(allowCancellation: true, ServicesVSResources.Updating_severity); _editHandlerService.Apply( _workspace, fromDocument: null, operations: operations, title: ServicesVSResources.Updating_severity, - progressTracker: waitContext.ProgressTracker, - cancellationToken: waitContext.CancellationToken); + progressTracker: new UIThreadOperationContextProgressTracker(scope), + cancellationToken: context.UserCancellationToken); }); - if (result == WaitIndicatorResult.Completed && selectedDiagnostic.DocumentId != null) + if (result == UIThreadOperationStatus.Completed && selectedDiagnostic.DocumentId != null) { // Kick off diagnostic re-analysis for affected document so that the configured diagnostic gets refreshed. Task.Run(() => @@ -203,10 +208,10 @@ private void SetSeverityHandler(object sender, EventArgs args) return; // Local functions. - async System.Threading.Tasks.Task ConfigureSeverityAsync(IWaitContext waitContext) + async System.Threading.Tasks.Task ConfigureSeverityAsync(CancellationToken cancellationToken) { - var diagnostic = await selectedDiagnostic.ToDiagnosticAsync(project, waitContext.CancellationToken).ConfigureAwait(false); - return await ConfigurationUpdater.ConfigureSeverityAsync(reportDiagnostic.Value, diagnostic, project, waitContext.CancellationToken).ConfigureAwait(false); + var diagnostic = await selectedDiagnostic.ToDiagnosticAsync(project, cancellationToken).ConfigureAwait(false); + return await ConfigurationUpdater.ConfigureSeverityAsync(reportDiagnostic.Value, diagnostic, project, cancellationToken).ConfigureAwait(false); } } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index cb126645e5cc6..df280a8761c44 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Implementation; using Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; @@ -27,6 +28,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.TableControl; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using Task = System.Threading.Tasks.Task; @@ -47,7 +49,7 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi private readonly IFixMultipleOccurrencesService _fixMultipleOccurencesService; private readonly ICodeActionEditHandlerService _editHandlerService; private readonly VisualStudioDiagnosticListSuppressionStateService _suppressionStateService; - private readonly IWaitIndicator _waitIndicator; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; private readonly IVsHierarchyItemManager _vsHierarchyItemManager; private readonly IHierarchyItemToProjectIdMap _projectMap; @@ -60,7 +62,7 @@ public VisualStudioSuppressionFixService( ICodeFixService codeFixService, ICodeActionEditHandlerService editHandlerService, IVisualStudioDiagnosticListSuppressionStateService suppressionStateService, - IWaitIndicator waitIndicator, + IUIThreadOperationExecutor uiThreadOperationExecutor, IVsHierarchyItemManager vsHierarchyItemManager) { _workspace = workspace; @@ -69,7 +71,7 @@ public VisualStudioSuppressionFixService( _codeFixService = codeFixService; _suppressionStateService = (VisualStudioDiagnosticListSuppressionStateService)suppressionStateService; _editHandlerService = editHandlerService; - _waitIndicator = waitIndicator; + _uiThreadOperationExecutor = uiThreadOperationExecutor; _vsHierarchyItemManager = vsHierarchyItemManager; _fixMultipleOccurencesService = workspace.Services.GetService(); _projectMap = workspace.Services.GetService(); @@ -137,7 +139,7 @@ private static Func GetShouldFixInProjectDelegate(IVsHierarchyIte private async Task> GetAllBuildDiagnosticsAsync(Func shouldFixInProject, CancellationToken cancellationToken) { - var builder = ArrayBuilder.GetInstance(); + var builder = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(); var buildDiagnostics = _buildErrorDiagnosticService.GetBuildErrors().Where(d => d.ProjectId != null && d.Severity != DiagnosticSeverity.Hidden); var solution = _workspace.CurrentSolution; @@ -193,9 +195,9 @@ private static string GetWaitDialogMessage(bool isAddSuppression) private IEnumerable GetDiagnosticsToFix(Func shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression) { var diagnosticsToFix = ImmutableHashSet.Empty; - void computeDiagnosticsToFix(IWaitContext context) + void computeDiagnosticsToFix(IUIThreadOperationContext context) { - var cancellationToken = context.CancellationToken; + var cancellationToken = context.UserCancellationToken; // If we are fixing selected diagnostics in error list, then get the diagnostics from error list entry snapshots. // Otherwise, get all diagnostics from the diagnostic service. @@ -211,7 +213,7 @@ void computeDiagnosticsToFix(IWaitContext context) var result = InvokeWithWaitDialog(computeDiagnosticsToFix, title, waitDialogMessage); // Bail out if the user cancelled. - if (result == WaitIndicatorResult.Canceled) + if (result == UIThreadOperationStatus.Canceled) { return null; } @@ -249,9 +251,9 @@ private bool ApplySuppressionFix(IEnumerable diagnosticsToFix, F var newSolution = _workspace.CurrentSolution; HashSet languages = null; - void computeDiagnosticsAndFix(IWaitContext context) + void computeDiagnosticsAndFix(IUIThreadOperationContext context) { - var cancellationToken = context.CancellationToken; + var cancellationToken = context.UserCancellationToken; cancellationToken.ThrowIfCancellationRequested(); documentDiagnosticsToFixMap = GetDocumentDiagnosticsToFixAsync(diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics: filterStaleDiagnostics, cancellationToken: cancellationToken) .WaitAndGetResult(cancellationToken); @@ -345,7 +347,7 @@ void computeDiagnosticsAndFix(IWaitContext context) var result = InvokeWithWaitDialog(computeDiagnosticsAndFix, title, waitDialogMessage); // Bail out if the user cancelled. - if (cancelled || result == WaitIndicatorResult.Canceled) + if (cancelled || result == UIThreadOperationStatus.Canceled) { return false; } @@ -371,21 +373,22 @@ void computeDiagnosticsAndFix(IWaitContext context) } waitDialogMessage = isAddSuppression ? ServicesVSResources.Applying_suppressions_fix : ServicesVSResources.Applying_remove_suppressions_fix; - void applyFix(IWaitContext context) + void applyFix(IUIThreadOperationContext context) { var operations = ImmutableArray.Create(new ApplyChangesOperation(newSolution)); - var cancellationToken = context.CancellationToken; + var cancellationToken = context.UserCancellationToken; + var scope = context.AddScope(allowCancellation: true, description: ""); _editHandlerService.Apply( _workspace, fromDocument: null, operations: operations, title: title, - progressTracker: context.ProgressTracker, + progressTracker: new UIThreadOperationContextProgressTracker(scope), cancellationToken: cancellationToken); } result = InvokeWithWaitDialog(applyFix, title, waitDialogMessage); - if (result == WaitIndicatorResult.Canceled) + if (result == UIThreadOperationStatus.Canceled) { return false; } @@ -426,14 +429,14 @@ private static IEnumerable FilterDiagnostics(IEnumerable action, string waitDialogTitle, string waitDialogMessage) + private UIThreadOperationStatus InvokeWithWaitDialog( + Action action, string waitDialogTitle, string waitDialogMessage) { var cancelled = false; - var result = _waitIndicator.Wait( + var result = _uiThreadOperationExecutor.Execute( waitDialogTitle, waitDialogMessage, - allowCancel: true, + allowCancellation: true, showProgress: true, action: waitContext => { @@ -447,7 +450,7 @@ private WaitIndicatorResult InvokeWithWaitDialog( } }); - return cancelled ? WaitIndicatorResult.Canceled : result; + return cancelled ? UIThreadOperationStatus.Canceled : result; } private static ImmutableDictionary> GetDocumentDiagnosticsMappedToNewSolution(ImmutableDictionary> documentDiagnosticsToFixMap, Solution newSolution, string language) diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs index cc5fb14bac73a..400c299288b9b 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs @@ -362,7 +362,7 @@ public override bool TryGetValue(int index, string columnName, [NotNullWhen(retu content = data.Message; return content != null; case StandardTableKeyNames.DocumentName: - content = GetFileName(data.DataLocation?.OriginalFilePath, data.DataLocation?.MappedFilePath); + content = data.DataLocation?.GetFilePath(); return content != null; case StandardTableKeyNames.Line: content = data.DataLocation?.MappedStartLine ?? 0; diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs index 827fda729f71b..5ba1a4fe9cb60 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Common; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.TodoComments; @@ -225,7 +226,7 @@ public override bool TryGetValue(int index, string columnName, out object conten content = data.Value.Message; return content != null; case StandardTableKeyNames.DocumentName: - content = GetFileName(data.Value.OriginalFilePath, data.Value.MappedFilePath); + content = DiagnosticDataLocation.GetFilePath(data.Value.OriginalFilePath, data.Value.MappedFilePath); return content != null; case StandardTableKeyNames.Line: content = GetLineColumn(item).Line; diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs index 940d4b6a61ee8..bb04f29635839 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs @@ -178,7 +178,7 @@ public override bool TryGetValue(int index, string columnName, out object conten content = data.Message; return content != null; case StandardTableKeyNames.DocumentName: - content = GetFileName(data.DataLocation?.OriginalFilePath, data.DataLocation?.MappedFilePath); + content = data.DataLocation?.GetFilePath(); return content != null; case StandardTableKeyNames.Line: content = data.DataLocation?.MappedStartLine ?? 0; diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs index d50e45650ab3d..37edecb1bc305 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -208,7 +208,7 @@ public void ClearErrors(ProjectId projectId) return; - async Task ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InProgressState? state) + async ValueTask ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InProgressState? state) { Debug.Assert(state == null || !state.WereProjectErrorsCleared(projectId)); @@ -311,9 +311,7 @@ internal void OnSolutionBuildCompleted() // pause live analyzer using var operation = _notificationService.Start("BuildDone"); if (_diagnosticService is DiagnosticAnalyzerService diagnosticService) - { await SyncBuildErrorsAndReportOnBuildCompletedAsync(diagnosticService, inProgressState).ConfigureAwait(false); - } // Mark build as complete. OnBuildProgressChanged(inProgressState, BuildProgress.Done); @@ -330,7 +328,7 @@ internal void OnSolutionBuildCompleted() /// It raises diagnostic update events for both the Build-only diagnostics and Build + Intellisense diagnostics /// in the error list. /// - private async Task SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnalyzerService diagnosticService, InProgressState inProgressState) + private ValueTask SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnalyzerService diagnosticService, InProgressState inProgressState) { var solution = inProgressState.Solution; var cancellationToken = inProgressState.CancellationToken; @@ -357,7 +355,7 @@ private async Task SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnaly } // Report pending live errors - await diagnosticService.SynchronizeWithBuildAsync(_workspace, pendingLiveErrorsToSync, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: true, cancellationToken).ConfigureAwait(false); + return diagnosticService.SynchronizeWithBuildAsync(_workspace, pendingLiveErrorsToSync, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: true, cancellationToken); } private void ReportBuildErrors(T item, Solution solution, ImmutableArray buildErrors) @@ -429,9 +427,7 @@ public void AddNewErrors( await ReportPreviousProjectErrorsIfRequiredAsync(projectId, state).ConfigureAwait(false); foreach (var kv in documentErrorMap) - { state.AddErrors(kv.Key, kv.Value); - } state.AddErrors(projectId, projectErrors); }, state.CancellationToken); @@ -445,30 +441,34 @@ public void AddNewErrors( /// This ensures that error list keeps getting refreshed while a build is in progress, as opposed to doing all the work /// and a single refresh when the build completes. /// - private async Task ReportPreviousProjectErrorsIfRequiredAsync(ProjectId projectId, InProgressState state) + private ValueTask ReportPreviousProjectErrorsIfRequiredAsync(ProjectId projectId, InProgressState state) { if (state.TryGetLastProjectWithReportedErrors() is ProjectId lastProjectId && lastProjectId != projectId) { - await SetLiveErrorsForProjectAsync(lastProjectId, state).ConfigureAwait(false); + return SetLiveErrorsForProjectAsync(lastProjectId, state); } + + return default; } - private async Task SetLiveErrorsForProjectAsync(ProjectId projectId, InProgressState state) + private async ValueTask SetLiveErrorsForProjectAsync(ProjectId projectId, InProgressState state) { var diagnostics = state.GetLiveErrorsForProject(projectId); await SetLiveErrorsForProjectAsync(projectId, diagnostics, state.CancellationToken).ConfigureAwait(false); state.MarkLiveErrorsReported(projectId); } - private async Task SetLiveErrorsForProjectAsync(ProjectId projectId, ImmutableArray diagnostics, CancellationToken cancellationToken) + private ValueTask SetLiveErrorsForProjectAsync(ProjectId projectId, ImmutableArray diagnostics, CancellationToken cancellationToken) { if (_diagnosticService is DiagnosticAnalyzerService diagnosticAnalyzerService) { // make those errors live errors var map = ProjectErrorMap.Empty.Add(projectId, diagnostics); - await diagnosticAnalyzerService.SynchronizeWithBuildAsync(_workspace, map, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: false, cancellationToken).ConfigureAwait(false); + return diagnosticAnalyzerService.SynchronizeWithBuildAsync(_workspace, map, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: false, cancellationToken); } + + return default; } private CancellationToken GetApplicableCancellationToken(InProgressState? state) @@ -813,16 +813,17 @@ private bool IsSupportedLiveDiagnosticId(Project project, string id) private ImmutableHashSet GetOrCreateSupportedLiveDiagnostics(Project project) { + var fullSolutionAnalysis = SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) == BackgroundAnalysisScope.FullSolution; + if (!project.SupportsCompilation || fullSolutionAnalysis) + { + // Defer to _allDiagnosticIdMap so we avoid placing FSA diagnostics in _liveDiagnosticIdMap + return GetOrCreateSupportedDiagnosticIds(project.Id); + } + return GetOrCreateDiagnosticIds(project.Id, _liveDiagnosticIdMap, ComputeSupportedLiveDiagnosticIds); ImmutableHashSet ComputeSupportedLiveDiagnosticIds() { - var fullSolutionAnalysis = SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) == BackgroundAnalysisScope.FullSolution; - if (!project.SupportsCompilation || fullSolutionAnalysis) - { - return GetOrCreateSupportedDiagnosticIds(project.Id); - } - // set ids set var builder = ImmutableHashSet.CreateBuilder(); var infoCache = _owner._diagnosticService.AnalyzerInfoCache; 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 c33796e4ea9d1..ca4ee097e3db9 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs @@ -123,7 +123,8 @@ public bool TryGetValue(string keyName, out object? content) content = ReferenceUpdate.ReferenceInfo.ReferenceType; break; case UnusedReferencesTableKeyNames.ReferenceName: - content = ReferenceUpdate.ReferenceInfo.ItemSpecification; + // It is unnecessary to display the full path to project and assembly files. + content = Path.GetFileNameWithoutExtension(ReferenceUpdate.ReferenceInfo.ItemSpecification); break; case UnusedReferencesTableKeyNames.UpdateAction: content = ReferenceUpdate.Action; diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs index f88ec084c9b02..4db7302726166 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs @@ -40,6 +40,13 @@ public static ImmutableArray ReadReferences( return ImmutableArray.Empty; } + return ReadReferences(projectReferences, projectAssets); + } + + internal static ImmutableArray ReadReferences( + ImmutableArray projectReferences, + ProjectAssetsFile projectAssets) + { if (projectAssets is null || projectAssets.Version != 3) { @@ -97,6 +104,7 @@ public static ImmutableArray ReadReferences( var dependencyNames = new HashSet(); var compilationAssemblies = ImmutableArray.CreateBuilder(); var referenceType = ReferenceType.Unknown; + var itemSpecification = referenceName; var packagesPath = projectAssets.Project?.Restore?.PackagesPath ?? string.Empty; @@ -121,6 +129,15 @@ public static ImmutableArray ReadReferences( _ => ReferenceType.Assembly }; + if (referenceType == ReferenceType.Project && + library.Path is not null) + { + // Project references are keyed by their filename but the + // item specification should be the path to the project file + // with Windows-style directory separators. + itemSpecification = library.Path.Replace('/', '\\'); + } + if (targetLibrary.Dependencies != null) { dependencyNames.AddRange(targetLibrary.Dependencies.Keys); @@ -144,7 +161,7 @@ public static ImmutableArray ReadReferences( .WhereNotNull() .ToImmutableArray(); - return new ReferenceInfo(referenceType, referenceName, treatAsUsed, compilationAssemblies.ToImmutable(), dependencies); + return new ReferenceInfo(referenceType, itemSpecification, 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 5791ef40449ad..4e90a832da777 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs @@ -34,7 +34,6 @@ internal sealed class RemoveUnusedReferencesCommandHandler private readonly Lazy _lazyReferenceCleanupService; private readonly RemoveUnusedReferencesDialogProvider _unusedReferenceDialogProvider; private readonly VisualStudioWorkspace _workspace; - private readonly IVsHierarchyItemManager _vsHierarchyItemManager; private readonly IUIThreadOperationExecutor _threadOperationExecutor; private IServiceProvider? _serviceProvider; @@ -42,12 +41,10 @@ internal sealed class RemoveUnusedReferencesCommandHandler [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RemoveUnusedReferencesCommandHandler( RemoveUnusedReferencesDialogProvider unusedReferenceDialogProvider, - IVsHierarchyItemManager vsHierarchyItemManager, IUIThreadOperationExecutor threadOperationExecutor, VisualStudioWorkspace workspace) { _unusedReferenceDialogProvider = unusedReferenceDialogProvider; - _vsHierarchyItemManager = vsHierarchyItemManager; _threadOperationExecutor = threadOperationExecutor; _workspace = workspace; @@ -185,7 +182,7 @@ private void OnRemoveUnusedReferencesForSelectedProject(object sender, EventArgs private ImmutableArray GetUnusedReferencesForProject(Solution solution, string projectFilePath, string projectAssetsFile, CancellationToken cancellationToken) { - ImmutableArray unusedReferences = ThreadHelper.JoinableTaskFactory.Run(async () => + var unusedReferences = ThreadHelper.JoinableTaskFactory.Run(async () => { var projectReferences = await _lazyReferenceCleanupService.Value.GetProjectReferencesAsync(projectFilePath, cancellationToken).ConfigureAwait(true); var references = ProjectAssetsReader.ReadReferences(projectReferences, projectAssetsFile); diff --git a/src/VisualStudio/Core/Def/Implementation/Utilities/TimeSlice.cs b/src/VisualStudio/Core/Def/Implementation/Utilities/TimeSlice.cs index e006354ce6e68..9e2b3533c6d62 100644 --- a/src/VisualStudio/Core/Def/Implementation/Utilities/TimeSlice.cs +++ b/src/VisualStudio/Core/Def/Implementation/Utilities/TimeSlice.cs @@ -2,13 +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. -#nullable disable - using System; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Utilities { - internal class TimeSlice + internal readonly struct TimeSlice { private readonly DateTime _end; diff --git a/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitContext.cs b/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitContext.cs index 8c512977d27b3..5f2b152eb7d51 100644 --- a/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitContext.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis.Editor.Host; @@ -14,6 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Utilities { + [Obsolete] internal sealed partial class VisualStudioWaitContext : IWaitContext { private const int DelayToShowDialogSecs = 2; diff --git a/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitIndicator.cs b/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitIndicator.cs index f58be31a0ddb3..7949d285d13b4 100644 --- a/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitIndicator.cs +++ b/src/VisualStudio/Core/Def/Implementation/Utilities/VisualStudioWaitIndicator.cs @@ -19,6 +19,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Utilities { + [Obsolete("You should now use IUIThreadOperationExecutor, which is a platform supported version of this.")] [Export(typeof(IWaitIndicator))] internal sealed class VisualStudioWaitIndicator : IWaitIndicator { diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.DocumentServiceProvider.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.DocumentServiceProvider.cs index f30d753b578d4..c1d94a3319048 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.DocumentServiceProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.DocumentServiceProvider.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -19,6 +20,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Projection; +using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus { @@ -56,7 +58,7 @@ public TService GetService() where TService : class, IDocumentService private static ITextSnapshot GetRoslynSnapshot(SourceText sourceText) => sourceText.FindCorrespondingEditorTextSnapshot(); - private class SpanMapper : ISpanMappingService + private class SpanMapper : AbstractSpanMappingService { private readonly ITextBuffer _primaryBuffer; @@ -66,9 +68,23 @@ public SpanMapper(ITextBuffer primaryBuffer) /// /// Legacy venus does not support us adding import directives and them mapping them to their own concepts. /// - public bool SupportsMappingImportDirectives => false; + public override bool SupportsMappingImportDirectives => false; - public async Task> MapSpansAsync(Document document, IEnumerable spans, CancellationToken cancellationToken) + public override async Task> GetMappedTextChangesAsync( + Document oldDocument, + Document newDocument, + CancellationToken cancellationToken) + { + var textChanges = (await newDocument.GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false)).ToImmutableArray(); + var mappedSpanResults = await MapSpansAsync(oldDocument, textChanges.Select(tc => tc.Span), CancellationToken.None).ConfigureAwait(false); + var mappedTextChanges = MatchMappedSpansToTextChanges(textChanges, mappedSpanResults); + return mappedTextChanges; + } + + public override async Task> MapSpansAsync( + Document document, + IEnumerable spans, + CancellationToken cancellationToken) { // REVIEW: for now, we keep document here due to open file case, otherwise, we need to create new SpanMappingService for every char user types. var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -168,8 +184,7 @@ private static async Task> GetClassifiedSpansOnCo // anything based on content is starting from 0 var startPositionOnContentSpan = GetNonWhitespaceStartPositionOnContent(contentSpanOnPrimarySnapshot); - using var pooledObject = SharedPools.Default>().GetPooledObject(); - var list = pooledObject.Object; + using var _1 = ArrayBuilder.GetInstance(out var list); foreach (var roslynSpan in primarySnapshot.MapToSourceSnapshots(contentSpanOnPrimarySnapshot.Span)) { @@ -212,7 +227,7 @@ private static async Task> GetClassifiedSpansOnCo // // the EditorClassifier call above fills all the gaps for the span it is called with, but we are combining // multiple spans with html code, so we need to fill those gaps - var builder = ArrayBuilder.GetInstance(); + using var _2 = ArrayBuilder.GetInstance(out var builder); ClassifierHelper.FillInClassifiedSpanGaps(startPositionOnContentSpan, list, builder); // add html after roslyn content if there is any @@ -223,14 +238,14 @@ private static async Task> GetClassifiedSpansOnCo } else { - var lastSpan = builder[builder.Count - 1].TextSpan; + var lastSpan = builder[^1].TextSpan; if (lastSpan.End < contentSpan.Length) { builder.Add(new ClassifiedSpan(new TextSpan(lastSpan.End, contentSpan.Length - lastSpan.End), ClassificationTypeNames.Text)); } } - return builder.ToImmutableAndFree(); + return builder.ToImmutable(); } private static int GetNonWhitespaceStartPositionOnContent(SnapshotSpan spanOnPrimarySnapshot) diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedCode.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedCode.cs index e44a27752e7fa..e86c27ba0f25a 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedCode.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedCode.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; @@ -32,13 +33,15 @@ public int HostSpansUpdated() /// public int EnumOriginalCodeBlocks(out IVsEnumCodeBlocks ppEnum) { - var waitIndicator = ComponentModel.GetService(); - IList result = null; - waitIndicator.Wait( + + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, - action: c => result = EnumOriginalCodeBlocksWorker(c.CancellationToken)); + defaultDescription: "", + allowCancellation: false, + showProgress: false, + action: c => result = EnumOriginalCodeBlocksWorker(c.UserCancellationToken)); ppEnum = new CodeBlockEnumerator(result); return VSConstants.S_OK; diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs index 9ed3201dc7475..c0efece2d6155 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus @@ -24,12 +25,14 @@ public int CreateUniqueEventName(string pszClassName, string pszObjectName, stri { string result = null; - var waitIndicator = ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, + defaultDescription: "", + allowCancellation: false, + showProgress: false, action: c => - result = ContainedLanguageCodeSupport.CreateUniqueEventName(GetThisDocument(), pszClassName, pszObjectName, pszNameOfEvent, c.CancellationToken)); + result = ContainedLanguageCodeSupport.CreateUniqueEventName(GetThisDocument(), pszClassName, pszObjectName, pszNameOfEvent, c.UserCancellationToken)); pbstrEventHandlerName = result; return VSConstants.S_OK; @@ -57,10 +60,12 @@ public int EnsureEventHandler( } Tuple idBodyAndInsertionPoint = null; - var waitIndicator = ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, + defaultDescription: "", + allowCancellation: false, + showProgress: false, action: c => idBodyAndInsertionPoint = ContainedLanguageCodeSupport.EnsureEventHandler( thisDocument, targetDocument, @@ -72,7 +77,7 @@ public int EnsureEventHandler( itemidInsertionPoint, useHandlesClause: false, additionalFormattingRule: targetDocument.Project.LanguageServices.GetService().GetAdditionalCodeGenerationRule(), - cancellationToken: c.CancellationToken)); + cancellationToken: c.UserCancellationToken)); pbstrUniqueMemberID = idBodyAndInsertionPoint.Item1; pbstrEventBody = idBodyAndInsertionPoint.Item2; @@ -83,12 +88,14 @@ public int EnsureEventHandler( public int GetBaseClassName(string pszClassName, out string pbstrBaseClassName) { var result = false; - var waitIndicator = this.ComponentModel.GetService(); string baseClassName = null; - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, - action: c => result = ContainedLanguageCodeSupport.TryGetBaseClassName(GetThisDocument(), pszClassName, c.CancellationToken, out baseClassName)); + defaultDescription: "", + allowCancellation: false, + showProgress: false, + action: c => result = ContainedLanguageCodeSupport.TryGetBaseClassName(GetThisDocument(), pszClassName, c.UserCancellationToken, out baseClassName)); pbstrBaseClassName = baseClassName; return result ? VSConstants.S_OK : VSConstants.E_FAIL; @@ -104,11 +111,13 @@ public int GetCompatibleEventHandlers( { IEnumerable> membersAndIds = null; - var waitIndicator = this.ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, - action: c => membersAndIds = ContainedLanguageCodeSupport.GetCompatibleEventHandlers(GetThisDocument(), pszClassName, pszObjectTypeName, pszNameOfEvent, c.CancellationToken)); + defaultDescription: "", + allowCancellation: false, + showProgress: false, + action: c => membersAndIds = ContainedLanguageCodeSupport.GetCompatibleEventHandlers(GetThisDocument(), pszClassName, pszObjectTypeName, pszNameOfEvent, c.UserCancellationToken)); pcMembers = membersAndIds.Count(); CreateBSTRArray(ppbstrEventHandlerNames, membersAndIds.Select(t => t.Item1)); @@ -121,11 +130,13 @@ public int GetEventHandlerMemberID(string pszClassName, string pszObjectTypeName { string memberId = null; - var waitIndicator = this.ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, - action: c => memberId = ContainedLanguageCodeSupport.GetEventHandlerMemberId(GetThisDocument(), pszClassName, pszObjectTypeName, pszNameOfEvent, pszEventHandlerName, c.CancellationToken)); + defaultDescription: "", + allowCancellation: false, + showProgress: false, + action: c => memberId = ContainedLanguageCodeSupport.GetEventHandlerMemberId(GetThisDocument(), pszClassName, pszObjectTypeName, pszNameOfEvent, pszEventHandlerName, c.UserCancellationToken)); pbstrUniqueMemberID = memberId; return pbstrUniqueMemberID == null ? VSConstants.S_FALSE : VSConstants.S_OK; @@ -137,13 +148,15 @@ public int GetMemberNavigationPoint(string pszClassName, string pszUniqueMemberI TextSpan textSpan = default; var succeeded = false; - var waitIndicator = this.ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, + defaultDescription: "", + allowCancellation: false, + showProgress: false, action: c => { - if (ContainedLanguageCodeSupport.TryGetMemberNavigationPoint(GetThisDocument(), pszClassName, pszUniqueMemberID, out textSpan, out var targetDocument, c.CancellationToken)) + if (ContainedLanguageCodeSupport.TryGetMemberNavigationPoint(GetThisDocument(), pszClassName, pszUniqueMemberID, out textSpan, out var targetDocument, c.UserCancellationToken)) { succeeded = true; itemId = this.ContainedDocument.FindItemIdOfDocument(targetDocument); @@ -159,11 +172,13 @@ public int GetMembers(string pszClassName, uint dwFlags, out int pcMembers, IntP { IEnumerable> membersAndIds = null; - var waitIndicator = this.ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, - action: c => membersAndIds = ContainedLanguageCodeSupport.GetMembers(GetThisDocument(), pszClassName, (CODEMEMBERTYPE)dwFlags, c.CancellationToken)); + defaultDescription: "", + allowCancellation: false, + showProgress: false, + action: c => membersAndIds = ContainedLanguageCodeSupport.GetMembers(GetThisDocument(), pszClassName, (CODEMEMBERTYPE)dwFlags, c.UserCancellationToken)); pcMembers = membersAndIds.Count(); CreateBSTRArray(ppbstrDisplayNames, membersAndIds.Select(t => t.Item1)); @@ -182,15 +197,17 @@ public int OnRenamed(ContainedLanguageRenameType clrt, string bstrOldID, string { var result = 0; - var waitIndicator = this.ComponentModel.GetService(); - waitIndicator.Wait( + var uiThreadOperationExecutor = ComponentModel.GetService(); + uiThreadOperationExecutor.Execute( "Intellisense", - allowCancel: false, + defaultDescription: "", + allowCancellation: false, + showProgress: false, action: c => { var refactorNotifyServices = this.ComponentModel.DefaultExportProvider.GetExportedValues(); - if (!ContainedLanguageCodeSupport.TryRenameElement(GetThisDocument(), clrt, bstrOldID, bstrNewID, refactorNotifyServices, c.CancellationToken)) + if (!ContainedLanguageCodeSupport.TryRenameElement(GetThisDocument(), clrt, bstrOldID, bstrNewID, refactorNotifyServices, c.UserCancellationToken)) { result = s_CONTAINEDLANGUAGE_CANNOTFINDITEM; } diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioSupportsFeatureService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioSupportsFeatureService.cs index d1a7392bd6370..8076a6635795f 100644 --- a/src/VisualStudio/Core/Def/Implementation/VisualStudioSupportsFeatureService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioSupportsFeatureService.cs @@ -23,6 +23,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SuggestionServi { internal sealed class VisualStudioSupportsFeatureService { + private const string ContainedLanguageMarker = nameof(ContainedLanguageMarker); + [ExportWorkspaceService(typeof(ITextBufferSupportsFeatureService), ServiceLayer.Host), Shared] private class VisualStudioTextBufferSupportsFeatureService : ITextBufferSupportsFeatureService { @@ -44,6 +46,14 @@ public bool SupportsRefactorings(ITextBuffer textBuffer) public bool SupportsRename(ITextBuffer textBuffer) { + // TS creates generated documents to back script blocks in razor generated files. + // These files are opened in the roslyn workspace but are not valid to rename + // as they are not proper buffers. So we exclude any buffer that is marked as a contained language. + if (textBuffer.Properties.TryGetProperty(ContainedLanguageMarker, out var markerValue) && markerValue) + { + return false; + } + var sourceTextContainer = textBuffer.AsTextContainer(); if (Workspace.TryGetWorkspace(sourceTextContainer, out var workspace)) { diff --git a/src/VisualStudio/Core/Def/Implementation/Watson/WatsonTraceListener.cs b/src/VisualStudio/Core/Def/Implementation/Watson/WatsonTraceListener.cs index b8471fbad1d12..e52572acf958d 100644 --- a/src/VisualStudio/Core/Def/Implementation/Watson/WatsonTraceListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/Watson/WatsonTraceListener.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.Diagnostics; @@ -11,7 +9,7 @@ namespace Microsoft.CodeAnalysis.ErrorReporting { internal class WatsonTraceListener : TraceListener { - public override void Fail(string message, string detailMessage) + public override void Fail(string? message, string? detailMessage) { if (string.IsNullOrEmpty(message)) { @@ -27,7 +25,7 @@ public override void Fail(string message, string detailMessage) } } - public override void Write(object o) + public override void Write(object? o) { if (Debugger.IsLogging()) { @@ -35,7 +33,7 @@ public override void Write(object o) } } - public override void Write(object o, string category) + public override void Write(object? o, string? category) { if (Debugger.IsLogging()) { @@ -43,7 +41,7 @@ public override void Write(object o, string category) } } - public override void Write(string message) + public override void Write(string? message) { if (Debugger.IsLogging()) { @@ -51,7 +49,7 @@ public override void Write(string message) } } - public override void Write(string message, string category) + public override void Write(string? message, string? category) { if (Debugger.IsLogging()) { @@ -59,7 +57,7 @@ public override void Write(string message, string category) } } - public override void WriteLine(object o) + public override void WriteLine(object? o) { if (Debugger.IsLogging()) { @@ -67,7 +65,7 @@ public override void WriteLine(object o) } } - public override void WriteLine(object o, string category) + public override void WriteLine(object? o, string? category) { if (Debugger.IsLogging()) { @@ -75,7 +73,7 @@ public override void WriteLine(object o, string category) } } - public override void WriteLine(string message) + public override void WriteLine(string? message) { if (Debugger.IsLogging()) { @@ -83,7 +81,7 @@ public override void WriteLine(string message) } } - public override void WriteLine(string message, string category) + public override void WriteLine(string? message, string? category) { if (Debugger.IsLogging()) { @@ -91,7 +89,7 @@ public override void WriteLine(string message, string category) } } - private static void Exit(string message) + private static void Exit(string? message) { FatalError.ReportAndPropagate(new Exception(message)); } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index 56cf64f41c760..2dad6684c85fe 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -4,15 +4,16 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -44,7 +45,9 @@ internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventLis private readonly ITextDocumentFactoryService _textDocumentFactoryService; private readonly VisualStudioDocumentNavigationService _visualStudioDocumentNavigationService; +#pragma warning disable IDE0052 // Remove unread private members private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; +#pragma warning restore IDE0052 // Remove unread private members /// /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. @@ -57,8 +60,6 @@ internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventLis private readonly Dictionary _openFiles = new(); private readonly VisualStudioWorkspace _visualStudioWorkspace; - private readonly Dictionary _directoryInfoOnDiskByContainingDirectoryId = new(); - /// /// When we have to put a placeholder file on disk, we put it in a directory named by the GUID portion of the DocumentId. /// We store the actual DocumentId (which includes the ProjectId) and some other textual information in @@ -66,17 +67,16 @@ internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventLis /// If we put the GUIDs and string names directly as components of the path, we quickly run into MAX_PATH. If we had a way to do virtual /// monikers that don't run into MAX_PATH issues then we absolutely would want to get rid of this. /// - private class GeneratedFileDirectoryInfo - { - public GeneratedFileDirectoryInfo(DocumentId documentId, Type generatorType) - { - DocumentId = documentId; - GeneratorType = generatorType; - } + /// All accesses should be on the UI thread. + private readonly Dictionary _directoryInfoOnDiskByContainingDirectoryId = new(); - public DocumentId DocumentId { get; } - public Type GeneratorType { get; } - } + /// + /// This option allows the user to enable this. We are putting this behind a feature flag for now since we could have extensions + /// surprised by this and we want some time to work through those issues. + /// + internal static readonly Option2 EnableOpeningInWorkspace = + new(nameof(SourceGeneratedFileManager), nameof(EnableOpeningInWorkspace), defaultValue: null, + storageLocations: new RoamingProfileStorageLocation("TextEditor.Roslyn.Specific.EnableOpeningSourceGeneratedFilesInWorkspaceExperiment")); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -122,8 +122,7 @@ public void NavigateToSourceGeneratedFile(SourceGeneratedDocument document, Text if (!_directoryInfoOnDiskByContainingDirectoryId.ContainsKey(document.Id.Id)) { - _directoryInfoOnDiskByContainingDirectoryId.Add(document.Id.Id, - new GeneratedFileDirectoryInfo(document.Id, document.SourceGenerator.GetType())); + _directoryInfoOnDiskByContainingDirectoryId.Add(document.Id.Id, document.Identity); } // We must always ensure the file name portion of the path is just the hint name, which matches the compiler's choice so @@ -164,15 +163,11 @@ public void NavigateToSourceGeneratedFile(SourceGeneratedDocument document, Text public bool TryGetGeneratedFileInformation( string filePath, - [NotNullWhen(true)] out DocumentId? documentId, - [NotNullWhen(true)] out Type? generatorType, - [NotNullWhen(true)] out string? generatedSourceHintName) + out SourceGeneratedDocumentIdentity identity) { _foregroundThreadAffintizedObject.AssertIsForeground(); - documentId = null; - generatorType = null; - generatedSourceHintName = null; + identity = default; if (!filePath.StartsWith(_temporaryDirectory)) { @@ -180,29 +175,23 @@ public bool TryGetGeneratedFileInformation( } var fileInfo = new FileInfo(filePath); - if (!Guid.TryParse(fileInfo.Directory.Name, out var guid) || - !_directoryInfoOnDiskByContainingDirectoryId.TryGetValue(guid, out var directoryInfo)) - { - return false; - } - - documentId = directoryInfo.DocumentId; - generatorType = directoryInfo.GeneratorType; - generatedSourceHintName = fileInfo.Name; - - return true; + return Guid.TryParse(fileInfo.Directory.Name, out var guid) && + _directoryInfoOnDiskByContainingDirectoryId.TryGetValue(guid, out identity); } void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy, IVsWindowFrame? windowFrame) { - if (TryGetGeneratedFileInformation(moniker, out var documentId, out var generatorType, out var generatedSourceHintName)) + _foregroundThreadAffintizedObject.AssertIsForeground(); + + if (TryGetGeneratedFileInformation(moniker, out var documentIdentity)) { // Attach to the text buffer if we haven't already - if (!_openFiles.TryGetValue(moniker, out OpenSourceGeneratedFile openFile)) + if (!_openFiles.TryGetValue(moniker, out var openFile)) { - openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, documentId, generatorType, _threadingContext); + openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, documentIdentity, _threadingContext); _openFiles.Add(moniker, openFile); - _threadingContext.JoinableTaskFactory.Run(() => openFile.UpdateBufferContentsAsync(CancellationToken.None)); + + _threadingContext.JoinableTaskFactory.Run(() => openFile.RefreshFileAsync(CancellationToken.None)); // Update the RDT flags to ensure the file can't be saved or appears in any MRUs as it's a temporary generated file name. var cookie = ((IVsRunningDocumentTable4)_runningDocumentTable).GetDocumentCookie(moniker); @@ -211,13 +200,15 @@ void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuff if (windowFrame != null) { - openFile.SetWindowFrame(windowFrame, generatedSourceHintName); + openFile.SetWindowFrame(windowFrame, documentIdentity.HintName); } } } void IRunningDocumentTableEventListener.OnCloseDocument(string moniker) { + _foregroundThreadAffintizedObject.AssertIsForeground(); + if (_openFiles.TryGetValue(moniker, out var openFile)) { openFile.Dispose(); @@ -238,8 +229,7 @@ private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisp private readonly SourceGeneratedFileManager _fileManager; private readonly ITextBuffer _textBuffer; private readonly Workspace _workspace; - private readonly DocumentId _documentId; - private readonly Type _generatorType; + private readonly SourceGeneratedDocumentIdentity _documentIdentity; /// /// A read-only region that we create across the entire file to prevent edits unless we are the one making them. @@ -271,14 +261,13 @@ private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisp private ImageMoniker _currentWindowFrameImageMoniker = default; private IVsInfoBarUIElement? _currentWindowFrameInfoBarElement = null; - public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, DocumentId documentId, Type generatorType, IThreadingContext threadingContext) + public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, SourceGeneratedDocumentIdentity documentIdentity, IThreadingContext threadingContext) : base(threadingContext, assertIsForeground: true) { _fileManager = fileManager; _textBuffer = textBuffer; _workspace = workspace; - _documentId = documentId; - _generatorType = generatorType; + _documentIdentity = documentIdentity; // We'll create a read-only region for the file, but it'll be a dynamic region we can temporarily suspend // while we're doing edits. @@ -297,13 +286,25 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff _batchingWorkQueue = new AsyncBatchingDelay( TimeSpan.FromSeconds(1), - UpdateBufferContentsAsync, + RefreshFileAsync, asyncListener: _fileManager._listener, _cancellationTokenSource.Token); } + private void DisconnectFromWorkspaceIfOpen() + { + AssertIsForeground(); + + if (_workspace.IsDocumentOpen(_documentIdentity.DocumentId)) + { + _workspace.OnSourceGeneratedDocumentClosed(_documentIdentity.DocumentId); + } + } + public void Dispose() { + AssertIsForeground(); + using (var readOnlyRegionEdit = _textBuffer.CreateReadOnlyRegionEdit()) { readOnlyRegionEdit.RemoveReadOnlyRegion(_readOnlyRegion); @@ -312,16 +313,18 @@ public void Dispose() _workspace.WorkspaceChanged -= OnWorkspaceChanged; + DisconnectFromWorkspaceIfOpen(); + // Cancel any remaining asynchronous work we may have had to update this file _cancellationTokenSource.Cancel(); } - private string GeneratorDisplayName => _generatorType.FullName; + private string GeneratorDisplayName => _documentIdentity.GeneratorTypeName; - public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) + public async Task RefreshFileAsync(CancellationToken cancellationToken) { SourceText? generatedSource = null; - var project = _workspace.CurrentSolution.GetProject(_documentId.ProjectId); + var project = _workspace.CurrentSolution.GetProject(_documentIdentity.DocumentId.ProjectId); // Locals correspond to the equivalently-named fields; we'll assign these and then assign to the fields while on the // UI thread to avoid any potential race where we update the InfoBar while this is running. @@ -335,7 +338,7 @@ public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) } else { - var generatedDocument = await project.GetSourceGeneratedDocumentAsync(_documentId, cancellationToken).ConfigureAwait(false); + var generatedDocument = await project.GetSourceGeneratedDocumentAsync(_documentIdentity.DocumentId, cancellationToken).ConfigureAwait(false); if (generatedDocument != null) { @@ -346,7 +349,7 @@ public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) else { // The file isn't there anymore; do we still have the generator at all? - if (project.AnalyzerReferences.Any(a => a.GetGenerators(project.Language).Any(g => g.GetType().Assembly.Equals(_generatorType.Assembly)))) + if (project.AnalyzerReferences.Any(a => a.GetGenerators(project.Language).Any(g => SourceGeneratedDocumentIdentity.GetGeneratorAssemblyName(g) == _documentIdentity.GeneratorAssemblyName))) { windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_stopped_generating_this_file, GeneratorDisplayName); windowFrameImageMonikerToShow = KnownMonikers.StatusError; @@ -390,12 +393,28 @@ public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) edit.Replace(startPosition: 0, _textBuffer.CurrentSnapshot.Length, generatedSource.ToString()); edit.Apply(); } + + // If the file isn't already open, open it now. We may transition between opening and closing + // if the file is repeatedly appearing and disappearing. + var connectToWorkspace = _workspace.Options.GetOption(EnableOpeningInWorkspace) ?? false; + + if (connectToWorkspace && !_workspace.IsDocumentOpen(_documentIdentity.DocumentId)) + { + _workspace.OnSourceGeneratedDocumentOpened(_documentIdentity, _textBuffer.AsTextContainer()); + } } finally { _updatingBuffer = false; } } + else + { + // The user made an edit that meant the source generator that generated this file is no longer generating this file. + // We can't update buffer contents anymore. We'll remove the connection between this buffer and the workspace, + // so this file now appears in Miscellaneous Files. + DisconnectFromWorkspaceIfOpen(); + } // Update the InfoBar either way EnsureWindowFrameInfoBarUpdated(); @@ -403,8 +422,8 @@ public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) { - var oldProject = e.OldSolution.GetProject(_documentId.ProjectId); - var newProject = e.NewSolution.GetProject(_documentId.ProjectId); + var oldProject = e.OldSolution.GetProject(_documentIdentity.DocumentId.ProjectId); + var newProject = e.NewSolution.GetProject(_documentIdentity.DocumentId.ProjectId); if (oldProject != null && newProject != null) { @@ -487,5 +506,17 @@ public void NavigateToSpan(TextSpan sourceSpan, CancellationToken cancellationTo _fileManager._visualStudioDocumentNavigationService.NavigateTo(_textBuffer, sourceText.GetVsTextSpanForSpan(sourceSpan), cancellationToken); } } + + [Export(typeof(IOptionProvider))] + private class OptionProvider : IOptionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OptionProvider() + { + } + + public ImmutableArray Options => ImmutableArray.Create(EnableOpeningInWorkspace); + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs index 5321b549378f8..9949e2e33fc02 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.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.Collections.Immutable; using System.ComponentModel.Composition; @@ -43,7 +41,7 @@ internal class VisualStudioActiveDocumentTracker : ForegroundThreadAffinitizedOb /// /// The active IVsWindowFrame. This can only be written by the UI thread, although can be read (with care) from any thread. /// - private IVsWindowFrame _activeFrame; + private IVsWindowFrame? _activeFrame; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -58,7 +56,7 @@ public VisualStudioActiveDocumentTracker( { await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var monitorSelectionService = (IVsMonitorSelection)await asyncServiceProvider.GetServiceAsync(typeof(SVsShellMonitorSelection)).ConfigureAwait(true); + var monitorSelectionService = (IVsMonitorSelection?)await asyncServiceProvider.GetServiceAsync(typeof(SVsShellMonitorSelection)).ConfigureAwait(true); Assumes.Present(monitorSelectionService); // No need to track windows if we are shutting down @@ -80,17 +78,17 @@ public VisualStudioActiveDocumentTracker( /// Raised when the set of window frames being tracked changes, which means the results from or may change. /// May be raised on any thread. /// - public event EventHandler DocumentsChanged; + public event EventHandler? DocumentsChanged; /// /// Raised when a non-Roslyn text buffer is edited, which can be used to back off of expensive background processing. May be raised on any thread. /// - public event EventHandler NonRoslynBufferTextChanged; + public event EventHandler? NonRoslynBufferTextChanged; /// /// Returns the of the active document in a given . /// - public DocumentId TryGetActiveDocument(Workspace workspace) + public DocumentId? TryGetActiveDocument(Workspace workspace) { ThisCanBeCalledOnAnyThread(); @@ -205,7 +203,7 @@ private class FrameListener : IVsWindowFrameNotify, IVsWindowFrameNotify2 private readonly VisualStudioActiveDocumentTracker _documentTracker; private readonly uint _frameEventsCookie; - private readonly ITextBuffer _textBuffer; + private readonly ITextBuffer? _textBuffer; public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame frame) { @@ -237,7 +235,7 @@ private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventA /// Returns the current DocumentId for this window frame. Care must be made with this value, since "current" could change asynchronously as the document /// could be unregistered from a workspace. /// - public DocumentId GetDocumentId(Workspace workspace) + public DocumentId? GetDocumentId(Workspace workspace) { if (_textBuffer == null) { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentTrackingServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentTrackingServiceFactory.cs index b025844d8e26d..dc228066886d1 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentTrackingServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentTrackingServiceFactory.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.Collections.Immutable; using System.Composition; @@ -39,9 +37,11 @@ public VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker act private readonly object _gate = new(); private int _subscriptions = 0; - private event EventHandler _activeDocumentChangedEventHandler; + private EventHandler? _activeDocumentChangedEventHandler; + + public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged + public event EventHandler ActiveDocumentChanged { add { @@ -74,7 +74,7 @@ public event EventHandler ActiveDocumentChanged } } - private void ActiveDocumentTracker_DocumentsChanged(object sender, EventArgs e) + private void ActiveDocumentTracker_DocumentsChanged(object? sender, EventArgs e) => _activeDocumentChangedEventHandler?.Invoke(this, TryGetActiveDocument()); public event EventHandler NonRoslynBufferTextChanged @@ -90,7 +90,7 @@ public event EventHandler NonRoslynBufferTextChanged } } - public DocumentId TryGetActiveDocument() + public DocumentId? TryGetActiveDocument() => _activeDocumentTracker.TryGetActiveDocument(_workspace); public ImmutableArray GetVisibleDocuments() diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs index 68a8f71047bd4..997683be55a73 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Extensions; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation @@ -22,7 +23,12 @@ public VisualStudioErrorReportingService(IInfoBarService infoBarService) public string HostDisplayName => "Visual Studio"; public void ShowGlobalErrorInfo(string message, params InfoBarUI[] items) - => _infoBarService.ShowInfoBar(message, items); + { + _infoBarService.ShowInfoBar(message, items); + + // Have to use KeyValueLogMessage so it gets reported in telemetry + Logger.Log(FunctionId.VS_ErrorReportingService_ShowGlobalErrorInfo, message, LogLevel.Information); + } public void ShowDetailedErrorInfo(Exception exception) { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs index eda4dc2640de1..23b3b6b1a8f8e 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs @@ -64,7 +64,7 @@ private static IWorkspaceService GetVisualStudioProjectCache(HostWorkspaceServic internal static void ConnectProjectCacheServiceToDocumentTracking(HostWorkspaceServices workspaceServices, ProjectCacheService projectCacheService) { - var documentTrackingService = workspaceServices.GetService(); + var documentTrackingService = workspaceServices.GetRequiredService(); // Subscribe to events so that we can cache items from the active document's project var manager = new ActiveProjectCacheManager(documentTrackingService, projectCacheService); @@ -98,11 +98,8 @@ public ActiveProjectCacheManager(IDocumentTrackingService documentTrackingServic { _projectCacheService = projectCacheService; - if (documentTrackingService != null) - { - documentTrackingService.ActiveDocumentChanged += UpdateCache; - UpdateCache(null, documentTrackingService.TryGetActiveDocument()); - } + documentTrackingService.ActiveDocumentChanged += UpdateCache; + UpdateCache(null, documentTrackingService.TryGetActiveDocument()); } private void UpdateCache(object sender, DocumentId activeDocument) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 6bc550c09167c..b930ac585e834 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.DecompiledSource; using Microsoft.CodeAnalysis.Editor; @@ -53,7 +54,7 @@ public VisualStudioSymbolNavigationService( _metadataAsSourceFileService = metadataAsSourceFileService; } - public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options, CancellationToken cancellationToken) + public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet? options, CancellationToken cancellationToken) { if (project == null || symbol == null) { @@ -172,14 +173,14 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet optio return true; } - public bool TrySymbolNavigationNotify(ISymbol symbol, Project project, CancellationToken cancellationToken) - => TryNotifyForSpecificSymbol(symbol, project.Solution, cancellationToken); - - private bool TryNotifyForSpecificSymbol( - ISymbol symbol, Solution solution, CancellationToken cancellationToken) + public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) { + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + AssertIsForeground(); + var solution = project.Solution; + var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); diff --git a/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs b/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs index 3410a8d44027a..e55001a37dc47 100644 --- a/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs +++ b/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs @@ -24,6 +24,7 @@ using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Interactive { @@ -323,8 +324,8 @@ protected override Task BuildProjectAsync() protected override void CancelBuildProject() => _dte.ExecuteCommand("Build.Cancel"); - protected override IWaitIndicator GetWaitIndicator() - => _componentModel.GetService(); + protected override IUIThreadOperationExecutor GetUIThreadOperationExecutor() + => _componentModel.GetService(); /// /// Return namespaces that can be resolved in the latest interactive compilation. diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 1eb93e319b038..324c659f597a5 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -147,7 +147,6 @@ - @@ -170,6 +169,7 @@ + diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 99050d8eb4648..bfa8dd1049081 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -56,7 +56,6 @@ internal partial class PackageInstallerService : AbstractDelayStartedService, IP private readonly Shell.IAsyncServiceProvider _asyncServiceProvider; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; - private readonly Lazy? _packageInstallerServices; private readonly Lazy? _packageInstaller; private readonly Lazy? _packageUninstaller; private readonly Lazy? _packageSourceProvider; @@ -97,7 +96,6 @@ public PackageInstallerService( SVsServiceProvider serviceProvider, [Import("Microsoft.VisualStudio.Shell.Interop.SAsyncServiceProvider")] object asyncServiceProvider, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, - [Import(AllowDefault = true)] Lazy? packageInstallerServices, [Import(AllowDefault = true)] Lazy? packageInstaller, [Import(AllowDefault = true)] Lazy? packageUninstaller, [Import(AllowDefault = true)] Lazy? packageSourceProvider) @@ -116,7 +114,6 @@ public PackageInstallerService( _asyncServiceProvider = (Shell.IAsyncServiceProvider)asyncServiceProvider; _editorAdaptersFactoryService = editorAdaptersFactoryService; - _packageInstallerServices = packageInstallerServices; _packageInstaller = packageInstaller; _packageUninstaller = packageUninstaller; _packageSourceProvider = packageSourceProvider; @@ -180,7 +177,6 @@ private async Task> GetPackageSourcesAsync() return ImmutableArray.Empty; } - [MemberNotNullWhen(true, nameof(_packageInstallerServices))] [MemberNotNullWhen(true, nameof(_packageInstaller))] [MemberNotNullWhen(true, nameof(_packageUninstaller))] [MemberNotNullWhen(true, nameof(_packageSourceProvider))] @@ -188,8 +184,7 @@ private bool IsEnabled { get { - return _packageInstallerServices != null - && _packageInstaller != null + return _packageInstaller != null && _packageUninstaller != null && _packageSourceProvider != null; } @@ -211,7 +206,7 @@ bool IPackageInstallerService.IsEnabled(ProjectId projectId) return true; } - private async ValueTask GetPackageSourceProviderAsync(CancellationToken cancellationToken) + private async ValueTask GetPackageSourceProviderAsync() { Contract.ThrowIfFalse(IsEnabled); @@ -232,7 +227,7 @@ protected override async Task EnableServiceAsync(CancellationToken cancellationT } // Continue on captured context since EnableServiceAsync is part of a UI thread initialization sequence - var packageSourceProvider = await GetPackageSourceProviderAsync(cancellationToken).ConfigureAwait(true); + var packageSourceProvider = await GetPackageSourceProviderAsync().ConfigureAwait(true); // Start listening to additional events workspace changes. _workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -290,13 +285,14 @@ public bool TryInstallPackage( var projectId = documentId.ProjectId; var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(SDTE)); var dteProject = _workspace.TryGetDTEProject(projectId); - if (dteProject != null) + var projectGuid = _workspace.GetProjectGuid(projectId); + if (dteProject != null && projectGuid != Guid.Empty) { var undoManager = _editorAdaptersFactoryService.TryGetUndoManager( workspace, documentId, cancellationToken); return TryInstallAndAddUndoAction( - source, packageName, versionOpt, includePrerelease, dte, dteProject, undoManager); + source, packageName, versionOpt, includePrerelease, projectGuid, dte, dteProject, undoManager); } } @@ -308,37 +304,49 @@ private bool TryInstallPackage( string packageName, string versionOpt, bool includePrerelease, + Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject) { this.AssertIsForeground(); Contract.ThrowIfFalse(IsEnabled); + // TODO: consider putting this under a TWD so it would actually be cancellable. + var cancellationToken = CancellationToken.None; + try { - if (!_packageInstallerServices.Value.IsPackageInstalled(dteProject, packageName)) + return this.ThreadingContext.JoinableTaskFactory.Run(() => { - dte.StatusBar.Text = string.Format(ServicesVSResources.Installing_0, packageName); - - if (versionOpt == null) - { - _packageInstaller.Value.InstallLatestPackage( - source, dteProject, packageName, includePrerelease, ignoreDependencies: false); - } - else + return this.PerformNuGetProjectServiceWorkAsync(async (nugetService, cancellationToken) => { - _packageInstaller.Value.InstallPackage( - source, dteProject, packageName, versionOpt, ignoreDependencies: false); - } - - var installedVersion = GetInstalledVersion(packageName, dteProject); - dte.StatusBar.Text = string.Format(ServicesVSResources.Installing_0_completed, - GetStatusBarText(packageName, installedVersion)); - - return true; - } - - // fall through. + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var installedPackagesMap = await GetInstalledPackagesMapAsync(nugetService, projectGuid, cancellationToken).ConfigureAwait(true); + if (installedPackagesMap.ContainsKey(packageName)) + return false; + + dte.StatusBar.Text = string.Format(ServicesVSResources.Installing_0, packageName); + + if (versionOpt == null) + { + _packageInstaller.Value.InstallLatestPackage( + source, dteProject, packageName, includePrerelease, ignoreDependencies: false); + } + else + { + _packageInstaller.Value.InstallPackage( + source, dteProject, packageName, versionOpt, ignoreDependencies: false); + } + + installedPackagesMap = await GetInstalledPackagesMapAsync(nugetService, projectGuid, cancellationToken).ConfigureAwait(true); + var installedVersion = installedPackagesMap.TryGetValue(packageName, out var version) ? version : null; + dte.StatusBar.Text = string.Format(ServicesVSResources.Installing_0_completed, + GetStatusBarText(packageName, installedVersion)); + + return true; + }, cancellationToken); + }); } catch (Exception e) when (FatalError.ReportAndCatch(e)) { @@ -349,36 +357,43 @@ private bool TryInstallPackage( string.Format(ServicesVSResources.Installing_0_failed_Additional_information_colon_1, packageName, e.Message), severity: NotificationSeverity.Error); - // fall through. + return false; } - - return false; } private static string GetStatusBarText(string packageName, string? installedVersion) => installedVersion == null ? packageName : $"{packageName} - {installedVersion}"; private bool TryUninstallPackage( - string packageName, EnvDTE.DTE dte, EnvDTE.Project dteProject) + string packageName, Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject) { this.AssertIsForeground(); Contract.ThrowIfFalse(IsEnabled); + // TODO: consider putting this under a TWD so it would actually be cancellable. + var cancellationToken = CancellationToken.None; + try { - if (_packageInstallerServices.Value.IsPackageInstalled(dteProject, packageName)) + return this.ThreadingContext.JoinableTaskFactory.Run(() => { - dte.StatusBar.Text = string.Format(ServicesVSResources.Uninstalling_0, packageName); - var installedVersion = GetInstalledVersion(packageName, dteProject); - _packageUninstaller.Value.UninstallPackage(dteProject, packageName, removeDependencies: true); + return this.PerformNuGetProjectServiceWorkAsync(async (nugetService, cancellationToken) => + { + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - dte.StatusBar.Text = string.Format(ServicesVSResources.Uninstalling_0_completed, - GetStatusBarText(packageName, installedVersion)); + var installedPackagesMap = await GetInstalledPackagesMapAsync(nugetService, projectGuid, cancellationToken).ConfigureAwait(true); + if (!installedPackagesMap.TryGetValue(packageName, out var installedVersion)) + return false; - return true; - } + dte.StatusBar.Text = string.Format(ServicesVSResources.Uninstalling_0, packageName); + _packageUninstaller.Value.UninstallPackage(dteProject, packageName, removeDependencies: true); - // fall through. + dte.StatusBar.Text = string.Format(ServicesVSResources.Uninstalling_0_completed, + GetStatusBarText(packageName, installedVersion)); + + return true; + }, cancellationToken); + }); } catch (Exception e) when (FatalError.ReportAndCatch(e)) { @@ -389,28 +404,8 @@ private bool TryUninstallPackage( string.Format(ServicesVSResources.Uninstalling_0_failed_Additional_information_colon_1, packageName, e.Message), severity: NotificationSeverity.Error); - // fall through. - } - - return false; - } - - private string? GetInstalledVersion(string packageName, EnvDTE.Project dteProject) - { - this.AssertIsForeground(); - Contract.ThrowIfFalse(IsEnabled); - - try - { - var installedPackages = _packageInstallerServices.Value.GetInstalledPackages(dteProject); - var metadata = installedPackages.FirstOrDefault(m => m.Id == packageName); - return metadata?.VersionString; - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { + return false; } - - return null; } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) @@ -465,10 +460,32 @@ private async Task ProcessWorkQueueWorkerAsync( { ThisCanBeCalledOnAnyThread(); + await PerformNuGetProjectServiceWorkAsync(async (nugetService, cancellationToken) => + { + // Figure out the entire set of projects to process. + using var _ = PooledHashSet.GetInstance(out var projectsToProcess); + + var solution = _workspace.CurrentSolution; + AddProjectsToProcess(workQueue, solution, projectsToProcess); + + // And Process them one at a time. + foreach (var projectId in projectsToProcess) + { + cancellationToken.ThrowIfCancellationRequested(); + await ProcessProjectChangeAsync(nugetService, solution, projectId, cancellationToken).ConfigureAwait(false); + } + + return null; + }, cancellationToken).ConfigureAwait(false); + } + + private async Task PerformNuGetProjectServiceWorkAsync( + Func> doWorkAsync, CancellationToken cancellationToken) + { var serviceContainer = (IBrokeredServiceContainer?)await _asyncServiceProvider.GetServiceAsync(typeof(SVsBrokeredServiceContainer)).ConfigureAwait(false); var serviceBroker = serviceContainer?.GetFullAccessServiceBroker(); if (serviceBroker == null) - return; + return default; // Make sure we are on the thread pool to avoid UI thread dependencies if external code uses ConfigureAwait(true) await TaskScheduler.Default; @@ -480,20 +497,9 @@ private async Task ProcessWorkQueueWorkerAsync( // If we didn't get a nuget service, there's nothing we can do in terms of querying the solution for // nuget info. if (nugetService == null) - return; + return default; - // Figure out the entire set of projects to process. - using var _ = PooledHashSet.GetInstance(out var projectsToProcess); - - var solution = _workspace.CurrentSolution; - AddProjectsToProcess(workQueue, solution, projectsToProcess); - - // And Process them one at a time. - foreach (var projectId in projectsToProcess) - { - cancellationToken.ThrowIfCancellationRequested(); - await ProcessProjectChangeAsync(nugetService, solution, projectId, cancellationToken).ConfigureAwait(false); - } + return await doWorkAsync(nugetService, cancellationToken).ConfigureAwait(false); } } @@ -557,16 +563,9 @@ private async Task ProcessProjectChangeAsync( cancellationToken.ThrowIfCancellationRequested(); try { - var installedPackagesResult = await nugetService.GetInstalledPackagesAsync(projectGuid, cancellationToken).ConfigureAwait(false); - - using var _ = PooledDictionary.GetInstance(out var installedPackages); - if (installedPackagesResult?.Status == InstalledPackageResultStatus.Successful) - { - foreach (var installedPackage in installedPackagesResult.Packages) - installedPackages[installedPackage.Id] = installedPackage.Version; - } + var installedPackagesMap = await GetInstalledPackagesMapAsync(nugetService, projectGuid, cancellationToken).ConfigureAwait(false); - return new ProjectState(installedPackages.ToImmutableDictionary()); + return new ProjectState(installedPackagesMap); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -574,6 +573,21 @@ private async Task ProcessProjectChangeAsync( } } + private static async Task> GetInstalledPackagesMapAsync(INuGetProjectService nugetService, Guid projectGuid, CancellationToken cancellationToken) + { + var installedPackagesResult = await nugetService.GetInstalledPackagesAsync(projectGuid, cancellationToken).ConfigureAwait(false); + + using var _ = PooledDictionary.GetInstance(out var installedPackages); + if (installedPackagesResult?.Status == InstalledPackageResultStatus.Successful) + { + foreach (var installedPackage in installedPackagesResult.Packages) + installedPackages[installedPackage.Id] = installedPackage.Version; + } + + var installedPackagesMap = installedPackages.ToImmutableDictionary(); + return installedPackagesMap; + } + public bool IsInstalled(Workspace workspace, ProjectId projectId, string packageName) { ThisCanBeCalledOnAnyThread(); diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory_UndoRedo.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory_UndoRedo.cs index 5c0d80c2fabfc..172ff2dbff907 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory_UndoRedo.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory_UndoRedo.cs @@ -6,7 +6,6 @@ using System; using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Text; namespace Microsoft.VisualStudio.LanguageServices.Packaging { @@ -14,17 +13,17 @@ internal partial class PackageInstallerService { private bool TryInstallAndAddUndoAction( string source, string packageName, string versionOpt, bool includePrerelease, - EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) + Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) { var installed = TryInstallPackage( - source, packageName, versionOpt, includePrerelease, dte, dteProject); + source, packageName, versionOpt, includePrerelease, projectGuid, dte, dteProject); if (installed) { // if the install succeeded, then add an uninstall item to the undo manager. undoManager?.Add(new UninstallPackageUndoUnit( this, source, packageName, versionOpt, includePrerelease, - dte, dteProject, undoManager)); + projectGuid, dte, dteProject, undoManager)); } return installed; @@ -32,16 +31,16 @@ private bool TryInstallAndAddUndoAction( private bool TryUninstallAndAddRedoAction( string source, string packageName, string versionOpt, bool includePrerelease, - EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) + Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) { - var uninstalled = TryUninstallPackage(packageName, dte, dteProject); + var uninstalled = TryUninstallPackage(packageName, projectGuid, dte, dteProject); if (uninstalled) { // if the install succeeded, then add an uninstall item to the undo manager. undoManager?.Add(new InstallPackageUndoUnit( this, source, packageName, versionOpt, includePrerelease, - dte, dteProject, undoManager)); + projectGuid, dte, dteProject, undoManager)); } return uninstalled; @@ -49,6 +48,7 @@ private bool TryUninstallAndAddRedoAction( private abstract class BaseUndoUnit : IOleUndoUnit { + protected readonly Guid projectGuid; protected readonly EnvDTE.DTE dte; protected readonly EnvDTE.Project dteProject; protected readonly PackageInstallerService packageInstallerService; @@ -64,6 +64,7 @@ protected BaseUndoUnit( string packageName, string versionOpt, bool includePrerelease, + Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) @@ -73,6 +74,7 @@ protected BaseUndoUnit( this.packageName = packageName; this.versionOpt = versionOpt; this.includePrerelease = includePrerelease; + this.projectGuid = projectGuid; this.dte = dte; this.dteProject = dteProject; this.undoManager = undoManager; @@ -98,12 +100,13 @@ public UninstallPackageUndoUnit( string packageName, string versionOpt, bool includePrerelease, + Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) : base(packageInstallerService, source, packageName, versionOpt, includePrerelease, - dte, dteProject, undoManager) + projectGuid, dte, dteProject, undoManager) { } @@ -111,7 +114,7 @@ public override void Do(IOleUndoManager pUndoManager) { packageInstallerService.TryUninstallAndAddRedoAction( source, packageName, versionOpt, includePrerelease, - dte, dteProject, undoManager); + projectGuid, dte, dteProject, undoManager); } public override void GetDescription(out string pBstr) @@ -126,12 +129,13 @@ public InstallPackageUndoUnit( string packageName, string versionOpt, bool includePrerelease, + Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager) : base(packageInstallerService, source, packageName, versionOpt, includePrerelease, - dte, dteProject, undoManager) + projectGuid, dte, dteProject, undoManager) { } @@ -141,7 +145,7 @@ public override void GetDescription(out string pBstr) public override void Do(IOleUndoManager pUndoManager) { packageInstallerService.TryInstallAndAddUndoAction( - source, packageName, versionOpt, includePrerelease, dte, dteProject, undoManager); + source, packageName, versionOpt, includePrerelease, projectGuid, dte, dteProject, undoManager); } } } diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 6839f66f2cf12..3e01cafb3530a 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -22,7 +22,6 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Telemetry; -using Microsoft.CodeAnalysis.Versions; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.ColorSchemes; using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings; @@ -267,13 +266,10 @@ protected override void Dispose(bool disposing) private void ReportSessionWideTelemetry() { - PersistedVersionStampLogger.ReportTelemetry(); - LinkedFileDiffMergingLogger.ReportTelemetry(); SolutionLogger.ReportTelemetry(); AsyncCompletionLogger.ReportTelemetry(); CompletionProvidersLogger.ReportTelemetry(); ChangeSignatureLogger.ReportTelemetry(); - SyntacticLspLogger.ReportTelemetry(); } private void DisposeVisualStudioServices() diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 4b383c59a84d9..cf374e9343835 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1635,6 +1635,9 @@ I agree to all of the foregoing: Reference + + Enable all features in opened files from source generators (experimental) + Remove Unused References @@ -1650,17 +1653,26 @@ I agree to all of the foregoing: Show "Remove Unused References" command in Solution Explorer (experimental) + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + This action cannot be undone. Do you wish to continue? + + Show inheritance margin + + + Inheritance Margin (experimental) + Analyzers - - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) + + Carriage Return (\r) Category @@ -1704,4 +1716,35 @@ I agree to all of the foregoing: Search Settings + + Error updating suppressions: {0} + + + Underline reassigned variables + + + Multiple members are inherited + + + '{0}' is inherited + + + Navigate to '{0}' + + + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + + + Implemented members + + + Implementing members + + + Overriding members + + + Overridden members + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Shared/VisualStudioImageMonikerService.cs b/src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs similarity index 77% rename from src/VisualStudio/Core/Def/Shared/VisualStudioImageMonikerService.cs rename to src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs index 20b0e2d824c20..787206ffc6377 100644 --- a/src/VisualStudio/Core/Def/Shared/VisualStudioImageMonikerService.cs +++ b/src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs @@ -10,10 +10,12 @@ using System.ComponentModel.Composition; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tags; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Core.Imaging; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Shell; @@ -34,11 +36,11 @@ public CompositeImage(ImmutableArray layers, IImageHandle } } - [ExportImageMonikerService(Name = Name)] - [Order(Before = DefaultImageMonikerService.Name)] - internal class VisualStudioImageMonikerService : ForegroundThreadAffinitizedObject, IImageMonikerService + [ExportImageIdService(Name = Name)] + [Order(Before = DefaultImageIdService.Name)] + internal class VisualStudioImageIdService : ForegroundThreadAffinitizedObject, IImageIdService { - public const string Name = nameof(VisualStudioImageMonikerService); + public const string Name = nameof(VisualStudioImageIdService); private readonly IVsImageService2 _imageService; @@ -47,32 +49,32 @@ internal class VisualStudioImageMonikerService : ForegroundThreadAffinitizedObje [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioImageMonikerService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) + public VisualStudioImageIdService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) : base(threadingContext) { _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService)); } - public bool TryGetImageMoniker(ImmutableArray tags, out ImageMoniker imageMoniker) + public bool TryGetImageId(ImmutableArray tags, out ImageId imageId) { this.AssertIsForeground(); - imageMoniker = GetImageMoniker(tags); - return !imageMoniker.IsNullImage(); + imageId = GetImageId(tags); + return imageId != default; } - private ImageMoniker GetImageMoniker(ImmutableArray tags) + private ImageId GetImageId(ImmutableArray tags) { var glyph = tags.GetFirstGlyph(); switch (glyph) { case Glyph.AddReference: - return GetCompositedImageMoniker( + return GetCompositedImageId( CreateLayer(Glyph.Reference.GetImageMoniker(), virtualXOffset: 1, virtualYOffset: 2), CreateLayer(KnownMonikers.PendingAddNode, virtualWidth: 7, virtualXOffset: -1, virtualYOffset: -2)); } - return glyph.GetImageMoniker(); + return glyph.GetImageId(); } private ImageCompositionLayer CreateLayer( @@ -93,7 +95,7 @@ private ImageCompositionLayer CreateLayer( }; } - private ImageMoniker GetCompositedImageMoniker(params ImageCompositionLayer[] layers) + private ImageId GetCompositedImageId(params ImageCompositionLayer[] layers) { this.AssertIsForeground(); @@ -101,7 +103,7 @@ private ImageMoniker GetCompositedImageMoniker(params ImageCompositionLayer[] la { if (compositeImage.Layers.SequenceEqual(layers)) { - return compositeImage.ImageHandle.Moniker; + return compositeImage.ImageHandle.Moniker.ToImageId(); } } @@ -112,7 +114,7 @@ private ImageMoniker GetCompositedImageMoniker(params ImageCompositionLayer[] la _compositeImages.Add(new CompositeImage(layers.AsImmutableOrEmpty(), imageHandle)); var moniker = imageHandle.Moniker; - return moniker; + return moniker.ToImageId(); } } } diff --git a/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs b/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs new file mode 100644 index 0000000000000..db39f0747d521 --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs @@ -0,0 +1,54 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Storage; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + internal abstract class AbstractCloudCachePersistentStorageService : AbstractPersistentStorageService + { + private const string StorageExtension = "CloudCache"; + + protected AbstractCloudCachePersistentStorageService( + IPersistentStorageLocationService locationService) + : base(locationService) + { + } + + protected abstract void DisposeCacheService(ICacheService cacheService); + protected abstract ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken); + + protected sealed override string GetDatabaseFilePath(string workingFolderPath) + { + Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath)); + return Path.Combine(workingFolderPath, StorageExtension); + } + + protected sealed override bool ShouldDeleteDatabase(Exception exception) + { + // CloudCache owns the db, so we don't have to delete anything ourselves. + return false; + } + + protected sealed override async ValueTask TryOpenDatabaseAsync( + SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken) + { + var cacheService = await this.CreateCacheServiceAsync(cancellationToken).ConfigureAwait(false); + var relativePathBase = await cacheService.GetRelativePathBaseAsync(cancellationToken).ConfigureAwait(false); + if (string.IsNullOrEmpty(relativePathBase)) + return null; + + return new CloudCachePersistentStorage( + cacheService, solutionKey, workingFolderPath, relativePathBase, databaseFilePath, this.DisposeCacheService); + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs new file mode 100644 index 0000000000000..e91587331cae7 --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs @@ -0,0 +1,225 @@ +// 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.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Nerdbank.Streams; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + /// + /// Implementation of Roslyn's sitting on top of the platform's cloud storage + /// system. + /// + internal class CloudCachePersistentStorage : AbstractPersistentStorage + { + private static readonly ObjectPool s_byteArrayPool = new(() => new byte[Checksum.HashSize]); + + /// + /// We do not need to store anything specific about the solution in this key as the platform cloud cache is + /// already keyed to the current solution. So this just allows us to store values considering that as the root. + /// + private static readonly CacheContainerKey s_solutionKey = new("Roslyn.Solution"); + + /// + /// Cache from project green nodes to the container keys we've computed for it (and the documents inside of it). + /// We can avoid computing these container keys when called repeatedly for the same projects/documents. + /// + private static readonly ConditionalWeakTable s_projectToContainerKeyCache = new(); + private readonly ConditionalWeakTable.CreateValueCallback _projectToContainerKeyCacheCallback; + + /// + /// Underlying cache service (owned by platform team) responsible for actual storage and retrieval of data. + /// + private readonly ICacheService _cacheService; + private readonly Action _disposeCacheService; + + public CloudCachePersistentStorage( + ICacheService cacheService, + SolutionKey solutionKey, + string workingFolderPath, + string relativePathBase, + string databaseFilePath, + Action disposeCacheService) + : base(workingFolderPath, relativePathBase, databaseFilePath) + { + _cacheService = cacheService; + _disposeCacheService = disposeCacheService; + _projectToContainerKeyCacheCallback = ps => new ProjectContainerKeyCache(relativePathBase, ProjectKey.ToProjectKey(solutionKey, ps)); + } + + public sealed override void Dispose() + => _disposeCacheService(_cacheService); + + public sealed override ValueTask DisposeAsync() + { + if (this._cacheService is IAsyncDisposable asyncDisposable) + { + return asyncDisposable.DisposeAsync(); + } + else if (this._cacheService is IDisposable disposable) + { + disposable.Dispose(); + return ValueTaskFactory.CompletedTask; + } + + return ValueTaskFactory.CompletedTask; + } + + /// + /// Maps our own roslyn key to the appropriate key to use for the cloud cache system. To avoid lots of + /// allocations we cache these (weakly) so if the same keys are used we can use the same platform keys. + /// + private CacheContainerKey? GetContainerKey(ProjectKey projectKey, Project? project) + { + return project != null + ? s_projectToContainerKeyCache.GetValue(project.State, _projectToContainerKeyCacheCallback).ProjectContainerKey + : ProjectContainerKeyCache.CreateProjectContainerKey(this.SolutionFilePath, projectKey); + } + + /// + /// Maps our own roslyn key to the appropriate key to use for the cloud cache system. To avoid lots of + /// allocations we cache these (weakly) so if the same keys are used we can use the same platform keys. + /// + private CacheContainerKey? GetContainerKey( + DocumentKey documentKey, Document? document) + { + return document != null + ? s_projectToContainerKeyCache.GetValue(document.Project.State, _projectToContainerKeyCacheCallback).GetDocumentContainerKey(document.State) + : ProjectContainerKeyCache.CreateDocumentContainerKey(this.SolutionFilePath, documentKey); + } + + public sealed override Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) + => ChecksumMatchesAsync(name, checksum, s_solutionKey, cancellationToken); + + protected sealed override Task ChecksumMatchesAsync(ProjectKey projectKey, Project? project, string name, Checksum checksum, CancellationToken cancellationToken) + => ChecksumMatchesAsync(name, checksum, GetContainerKey(projectKey, project), cancellationToken); + + protected sealed override Task ChecksumMatchesAsync(DocumentKey documentKey, Document? document, string name, Checksum checksum, CancellationToken cancellationToken) + => ChecksumMatchesAsync(name, checksum, GetContainerKey(documentKey, document), cancellationToken); + + private async Task ChecksumMatchesAsync(string name, Checksum checksum, CacheContainerKey? containerKey, CancellationToken cancellationToken) + { + // If we failed to get a container key (for example, because the client is referencing a file not under the + // solution folder) then we can't proceed. + if (containerKey == null) + return false; + + using var bytes = s_byteArrayPool.GetPooledObject(); + checksum.WriteTo(bytes.Object); + + return await _cacheService.CheckExistsAsync(new CacheItemKey(containerKey.Value, name) { Version = bytes.Object }, cancellationToken).ConfigureAwait(false); + } + + public sealed override Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken) + => ReadStreamAsync(name, checksum, s_solutionKey, cancellationToken); + + protected sealed override Task ReadStreamAsync(ProjectKey projectKey, Project? project, string name, Checksum? checksum, CancellationToken cancellationToken) + => ReadStreamAsync(name, checksum, GetContainerKey(projectKey, project), cancellationToken); + + protected sealed override Task ReadStreamAsync(DocumentKey documentKey, Document? document, string name, Checksum? checksum, CancellationToken cancellationToken) + => ReadStreamAsync(name, checksum, GetContainerKey(documentKey, document), cancellationToken); + + private async Task ReadStreamAsync(string name, Checksum? checksum, CacheContainerKey? containerKey, CancellationToken cancellationToken) + { + // If we failed to get a container key (for example, because the client is referencing a file not under the + // solution folder) then we can't proceed. + if (containerKey == null) + return null; + + if (checksum == null) + { + return await ReadStreamAsync(new CacheItemKey(containerKey.Value, name), cancellationToken).ConfigureAwait(false); + } + else + { + using var bytes = s_byteArrayPool.GetPooledObject(); + checksum.WriteTo(bytes.Object); + + return await ReadStreamAsync(new CacheItemKey(containerKey.Value, name) { Version = bytes.Object }, cancellationToken).ConfigureAwait(false); + } + } + + private async Task ReadStreamAsync(CacheItemKey key, CancellationToken cancellationToken) + { + var pipe = new Pipe(); + var result = await _cacheService.TryGetItemAsync(key, pipe.Writer, cancellationToken).ConfigureAwait(false); + if (!result) + return null; + + // Clients will end up doing blocking reads on the synchronous stream we return from this. This can + // negatively impact our calls as that will cause sync blocking on the async work to fill the pipe. To + // alleviate that issue, we actually asynchronously read in the entire stream into memory inside the reader + // and then pass that out. This should not be a problem in practice as PipeReader internally intelligently + // uses and pools reasonable sized buffers, preventing us from exacerbating the GC or causing LOH + // allocations. + return await AsPrebufferedStreamAsync(pipe.Reader, cancellationToken).ConfigureAwait(false); + } + + private static async Task AsPrebufferedStreamAsync(PipeReader pipeReader, CancellationToken cancellationToken = default) + { + while (true) + { + // Read and immediately report all bytes as "examined" so that the next ReadAsync call will block till more bytes come in. + // The goal here is to force the PipeReader to buffer everything internally (even if it were to exceed its natural writer threshold limit). + var readResult = await pipeReader.ReadAsync(cancellationToken).ConfigureAwait(false); + pipeReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); + + if (readResult.IsCompleted) + { + // After having buffered and "examined" all the bytes, the stream returned from PipeReader.AsStream() would fail + // because it may not "examine" all bytes at once. + // Instead, we'll create our own Stream over just the buffer itself, and recycle the buffers when the stream is disposed + // the way the stream returned from PipeReader.AsStream() would have. + return new ReadOnlySequenceStream(readResult.Buffer, reader => ((PipeReader)reader!).Complete(), pipeReader); + } + } + } + + public sealed override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + => WriteStreamAsync(name, stream, checksum, s_solutionKey, cancellationToken); + + protected sealed override Task WriteStreamAsync(ProjectKey projectKey, Project? project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + => WriteStreamAsync(name, stream, checksum, GetContainerKey(projectKey, project), cancellationToken); + + protected sealed override Task WriteStreamAsync(DocumentKey documentKey, Document? document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + => WriteStreamAsync(name, stream, checksum, GetContainerKey(documentKey, document), cancellationToken); + + private async Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CacheContainerKey? containerKey, CancellationToken cancellationToken) + { + // If we failed to get a container key (for example, because the client is referencing a file not under the + // solution folder) then we can't proceed. + if (containerKey == null) + return false; + + if (checksum == null) + { + return await WriteStreamAsync(new CacheItemKey(containerKey.Value, name), stream, cancellationToken).ConfigureAwait(false); + } + else + { + using var bytes = s_byteArrayPool.GetPooledObject(); + checksum.WriteTo(bytes.Object); + + return await WriteStreamAsync(new CacheItemKey(containerKey.Value, name) { Version = bytes.Object }, stream, cancellationToken).ConfigureAwait(false); + } + } + + private async Task WriteStreamAsync(CacheItemKey key, Stream stream, CancellationToken cancellationToken) + { + await _cacheService.SetItemAsync(key, PipeReader.Create(stream), shareable: false, cancellationToken).ConfigureAwait(false); + return true; + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs new file mode 100644 index 0000000000000..ede5b8938ee2a --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs @@ -0,0 +1,262 @@ +// 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. + +// Copied from https://raw.githubusercontent.com/AArnott/Nerdbank.Streams/2b142fa6a38b15e4b06ecc53bf073aa49fd1de34/src/Nerdbank.Streams/ReadOnlySequenceStream.cs +// Remove once we move to Nerdbank.Streams 2.7.62-alpha + +namespace Nerdbank.Streams +{ + using System; + using System.Buffers; + using System.IO; + using System.Runtime.InteropServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft; + + internal class ReadOnlySequenceStream : Stream, IDisposableObservable + { + private static readonly Task TaskOfZero = Task.FromResult(0); + + private readonly Action? disposeAction; + private readonly object? disposeActionArg; + + /// + /// A reusable task if two consecutive reads return the same number of bytes. + /// + private Task? lastReadTask; + + private readonly ReadOnlySequence readOnlySequence; + + private SequencePosition position; + + internal ReadOnlySequenceStream(ReadOnlySequence readOnlySequence, Action? disposeAction, object? disposeActionArg) + { + this.readOnlySequence = readOnlySequence; + this.disposeAction = disposeAction; + this.disposeActionArg = disposeActionArg; + this.position = readOnlySequence.Start; + } + + /// + public override bool CanRead => !this.IsDisposed; + + /// + public override bool CanSeek => !this.IsDisposed; + + /// + public override bool CanWrite => false; + + /// + public override long Length => this.ReturnOrThrowDisposed(this.readOnlySequence.Length); + + /// + public override long Position + { + get => this.readOnlySequence.Slice(0, this.position).Length; + set + { + Requires.Range(value >= 0, nameof(value)); + this.position = this.readOnlySequence.GetPosition(value, this.readOnlySequence.Start); + } + } + + /// + public bool IsDisposed { get; private set; } + + /// + public override void Flush() => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override Task FlushAsync(CancellationToken cancellationToken) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + var remaining = this.readOnlySequence.Slice(this.position); + var toCopy = remaining.Slice(0, Math.Min(count, remaining.Length)); + this.position = toCopy.End; + toCopy.CopyTo(buffer.AsSpan(offset, count)); + return (int)toCopy.Length; + } + + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var bytesRead = this.Read(buffer, offset, count); + if (bytesRead == 0) + { + return TaskOfZero; + } + + if (this.lastReadTask?.Result == bytesRead) + { + return this.lastReadTask; + } + else + { + return this.lastReadTask = Task.FromResult(bytesRead); + } + } + + /// + public override int ReadByte() + { + var remaining = this.readOnlySequence.Slice(this.position); + if (remaining.Length > 0) + { + var result = remaining.First.Span[0]; + this.position = this.readOnlySequence.GetPosition(1, this.position); + return result; + } + else + { + return -1; + } + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + Verify.NotDisposed(this); + + SequencePosition relativeTo; + switch (origin) + { + case SeekOrigin.Begin: + relativeTo = this.readOnlySequence.Start; + break; + case SeekOrigin.Current: + if (offset >= 0) + { + relativeTo = this.position; + } + else + { + relativeTo = this.readOnlySequence.Start; + offset += this.Position; + } + + break; + case SeekOrigin.End: + if (offset >= 0) + { + relativeTo = this.readOnlySequence.End; + } + else + { + relativeTo = this.readOnlySequence.Start; + offset += this.Position; + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(origin)); + } + + this.position = this.readOnlySequence.GetPosition(offset, relativeTo); + return this.Position; + } + + /// + public override void SetLength(long value) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override void Write(byte[] buffer, int offset, int count) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override void WriteByte(byte value) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + foreach (var segment in this.readOnlySequence) + { + await WriteAsync(destination, segment, cancellationToken).ConfigureAwait(false); + } + } + + private static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + Requires.NotNull(stream, nameof(stream)); + + if (MemoryMarshal.TryGetArray(buffer, out var array)) + { + return new ValueTask(stream.WriteAsync(array.Array!, array.Offset, array.Count, cancellationToken)); + } + else + { + var sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(sharedBuffer); + return new ValueTask(FinishWriteAsync(stream.WriteAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer)); + } + + async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) + { + try + { + await writeTask.ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(localBuffer); + } + } + } + +#if SPAN_BUILTIN + + /// + public override int Read(Span buffer) + { + ReadOnlySequence remaining = this.readOnlySequence.Slice(this.position); + ReadOnlySequence toCopy = remaining.Slice(0, Math.Min(buffer.Length, remaining.Length)); + this.position = toCopy.End; + toCopy.CopyTo(buffer); + return (int)toCopy.Length; + } + + /// + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(this.Read(buffer.Span)); + } + + /// + public override void Write(ReadOnlySpan buffer) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw this.ThrowDisposedOr(new NotSupportedException()); + +#endif + + /// + protected override void Dispose(bool disposing) + { + if (!this.IsDisposed) + { + this.IsDisposed = true; + this.disposeAction?.Invoke(this.disposeActionArg); + base.Dispose(disposing); + } + } + + private T ReturnOrThrowDisposed(T value) + { + Verify.NotDisposed(this); + return value; + } + + private Exception ThrowDisposedOr(Exception ex) + { + Verify.NotDisposed(this); + throw ex; + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/ProjectContainerKeyCache.cs b/src/VisualStudio/Core/Def/Storage/ProjectContainerKeyCache.cs new file mode 100644 index 0000000000000..2d8bbc75d725d --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/ProjectContainerKeyCache.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + /// + /// Cache of our own internal roslyn storage keys to the equivalent platform cloud cache keys. Cloud cache keys can + /// store a lot of date in them (like their 'dimensions' dictionary. We don't want to continually recreate these as + /// we read/write date to the db. + /// + internal class ProjectContainerKeyCache + { + private static readonly ImmutableSortedDictionary EmptyDimensions = ImmutableSortedDictionary.Create(StringComparer.Ordinal); + + /// + /// Container key explicitly for the project itself. + /// + public readonly CacheContainerKey? ProjectContainerKey; + + /// + /// Cache from document green nodes to the container keys we've computed for it. We can avoid computing these + /// container keys when called repeatedly for the same documents. + /// + /// + /// We can use a normal Dictionary here instead of a as + /// instances of are always owned in a context where the is alive. As that instance is alive, all s the project + /// points at will be held alive strongly too. + /// + private readonly Dictionary _documentToContainerKey = new(); + private readonly Func _documentToContainerKeyCallback; + + public ProjectContainerKeyCache(string relativePathBase, ProjectKey projectKey) + { + ProjectContainerKey = CreateProjectContainerKey(relativePathBase, projectKey); + + _documentToContainerKeyCallback = ds => CreateDocumentContainerKey(relativePathBase, DocumentKey.ToDocumentKey(projectKey, ds)); + } + + public CacheContainerKey? GetDocumentContainerKey(TextDocumentState state) + { + lock (_documentToContainerKey) + return _documentToContainerKey.GetOrAdd(state, _documentToContainerKeyCallback); + } + + public static CacheContainerKey? CreateProjectContainerKey( + string relativePathBase, ProjectKey projectKey) + { + // Creates a container key for this project. The container key is a mix of the project's name, relative + // file path (to the solution), and optional parse options. + + // If we don't have a valid solution path, we can't store anything. + if (string.IsNullOrEmpty(relativePathBase)) + return null; + + // We have to have a file path for this project + if (RoslynString.IsNullOrEmpty(projectKey.FilePath)) + return null; + + // The file path has to be relative to the base path the DB is associated with (either the solution-path or + // repo-path). + var relativePath = PathUtilities.GetRelativePath(relativePathBase, projectKey.FilePath!); + if (relativePath == projectKey.FilePath) + return null; + + var dimensions = EmptyDimensions + .Add($"{nameof(ProjectKey)}.{nameof(ProjectKey.Name)}", projectKey.Name) + .Add($"{nameof(ProjectKey)}.{nameof(ProjectKey.FilePath)}", relativePath) + .Add($"{nameof(ProjectKey)}.{nameof(ProjectKey.ParseOptionsChecksum)}", projectKey.ParseOptionsChecksum.ToString()); + + return new CacheContainerKey("Roslyn.Project", dimensions); + } + + public static CacheContainerKey? CreateDocumentContainerKey( + string relativePathBase, + DocumentKey documentKey) + { + // See if we can get a project key for this info. If not, we def can't get a doc key. + var projectContainerKey = CreateProjectContainerKey(relativePathBase, documentKey.Project); + if (projectContainerKey == null) + return null; + + // We have to have a file path for this document + if (string.IsNullOrEmpty(documentKey.FilePath)) + return null; + + // The file path has to be relative to the base path the DB is associated with (either the solution-path or + // repo-path). + var relativePath = PathUtilities.GetRelativePath(relativePathBase, documentKey.FilePath!); + if (relativePath == documentKey.FilePath) + return null; + + var dimensions = projectContainerKey.Value.Dimensions + .Add($"{nameof(DocumentKey)}.{nameof(DocumentKey.Name)}", documentKey.Name) + .Add($"{nameof(DocumentKey)}.{nameof(DocumentKey.FilePath)}", relativePath); + + return new CacheContainerKey("Roslyn.Document", dimensions); + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs new file mode 100644 index 0000000000000..6ef9fa3714c54 --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.ServiceBroker; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + internal class VisualStudioCloudCacheStorageService : AbstractCloudCachePersistentStorageService + { + private readonly IAsyncServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + + public VisualStudioCloudCacheStorageService(IAsyncServiceProvider serviceProvider, IThreadingContext threadingContext, IPersistentStorageLocationService locationService) + : base(locationService) + { + _serviceProvider = serviceProvider; + _threadingContext = threadingContext; + } + + protected sealed override void DisposeCacheService(ICacheService cacheService) + { + if (cacheService is IAsyncDisposable asyncDisposable) + { + _threadingContext.JoinableTaskFactory.Run( + () => asyncDisposable.DisposeAsync().AsTask()); + } + else if (cacheService is IDisposable disposable) + { + disposable.Dispose(); + } + } + + protected sealed override async ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken) + { + var serviceContainer = await _serviceProvider.GetServiceAsync().ConfigureAwait(false); + var serviceBroker = serviceContainer.GetFullAccessServiceBroker(); + +#pragma warning disable ISB001 // Dispose of proxies + // cache service will be disposed inside VisualStudioCloudCachePersistentStorage.Dispose + var cacheService = await serviceBroker.GetProxyAsync(VisualStudioServices.VS2019_10.CacheService, cancellationToken: cancellationToken).ConfigureAwait(false); +#pragma warning restore ISB001 // Dispose of proxies + + Contract.ThrowIfNull(cacheService); + return cacheService; + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs new file mode 100644 index 0000000000000..da2e9afbae3de --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Storage.CloudCache; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), ServiceLayer.Host), Shared] + internal class VisualStudioCloudCacheStorageServiceFactory : ICloudCacheStorageServiceFactory + { + private readonly IAsyncServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public VisualStudioCloudCacheStorageServiceFactory( + IThreadingContext threadingContext, + SVsServiceProvider serviceProvider) + { + _threadingContext = threadingContext; + _serviceProvider = (IAsyncServiceProvider)serviceProvider; + } + + public AbstractPersistentStorageService Create(IPersistentStorageLocationService locationService) + => new VisualStudioCloudCacheStorageService(_serviceProvider, _threadingContext, locationService); + } +} diff --git a/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs b/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs index d92d25ce2d4ec..b8017f09fba9a 100644 --- a/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs +++ b/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs @@ -31,21 +31,21 @@ public bool IsEnabled(FunctionId functionId) public void Log(FunctionId functionId, LogMessage logMessage) { - if (!(logMessage is KeyValueLogMessage kvLogMessage)) + if (logMessage.LogLevel < LogLevel.Information) { return; } try { - // guard us from exception thrown by telemetry - if (!kvLogMessage.ContainsProperty) + if (logMessage is KeyValueLogMessage { ContainsProperty: false }) { + // guard us from exception thrown by telemetry _session.PostEvent(functionId.GetEventName()); return; } - var telemetryEvent = CreateTelemetryEvent(functionId, kvLogMessage); + var telemetryEvent = CreateTelemetryEvent(functionId, logMessage); _session.PostEvent(telemetryEvent); } catch @@ -127,10 +127,17 @@ private object CreateAndStartScope(LogType kind, FunctionId functionId) }; } - private static TelemetryEvent CreateTelemetryEvent(FunctionId functionId, KeyValueLogMessage logMessage) + private static TelemetryEvent CreateTelemetryEvent(FunctionId functionId, LogMessage logMessage) { var eventName = functionId.GetEventName(); - return AppendProperties(new TelemetryEvent(eventName), functionId, logMessage); + var telemetryEvent = new TelemetryEvent(eventName); + + if (logMessage is KeyValueLogMessage kvLogMessage) + { + telemetryEvent = AppendProperties(telemetryEvent, functionId, kvLogMessage); + } + + return telemetryEvent; } private static T AppendProperties(T @event, FunctionId functionId, KeyValueLogMessage logMessage) diff --git a/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs index 77d44118d2999..cc98f15d3e775 100644 --- a/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs @@ -38,6 +38,7 @@ protected override ILogger CreateLogger(TelemetrySession telemetrySession) CodeMarkerLogger.Instance, new EtwLogger(_optionsService), new VSTelemetryLogger(telemetrySession), + new FileLogger(_optionsService), Logger.GetLogger()); protected override void TelemetrySessionInitialized() diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf index 6ab46a153fc53..0e52ac94505e0 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + Odebrat nepo&užívané odkazy... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf index 39691247a87d9..0519b3914d895 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + &Nicht verwendete Verweise entfernen... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf index 63941e8dbc88d..ea66b98523deb 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + &Quitar referencias sin usar... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf index 5eaa9a71bec58..9d160e734b01e 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + &Supprimer les références inutilisées... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf index bccc689169acb..eca0815f79dfd 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + Rim&uovi riferimenti inutilizzati... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf index 2ce24c0fc615d..c9fd9de0dacc2 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + 未使用の参照を削除する(&U)... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf index fed9ec757ba77..f46c17cad23dc 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + 사용하지 않는 참조 제거(&U)... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf index bb907800c0146..84daa1c5d3583 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + &Usuń nieużywane odwołania... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf index 5b6cd2c43c66c..649547a61b334 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + Remover as Referências Não &Usadas... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf index d68b6204e131e..04c129f151eaa 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + &Удалить неиспользуемые ссылки… RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf index 1d0204a6df606..5b4ddbcbab5ea 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + &Kullanılmayan Başvuruları Kaldır... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf index 316d4be560b9c..5d051cf4b5a87 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + 删除未使用的引用(&U)… RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf index c01b09934ee99..96f40643fd380 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -214,17 +214,17 @@ Remove &Unused References... - Remove &Unused References... + 移除未使用的參考(&U)... RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences RemoveUnusedReferences - RemoveUnusedReferences + RemoveUnusedReferences diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 78d2b64d74833..2002c08bc6807 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Akce Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Povolit více než jeden prázdný řádek Allow statement immediately after block - Allow statement immediately after block + Povolit příkaz hned za blokem @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analyzátory Analyzing project references... - Analyzing project references... + Analyzují se odkazy projektů... Apply - Apply + Použít @@ -99,7 +99,7 @@ Assemblies - Assemblies + Sestavení @@ -162,29 +162,29 @@ Lokalita volání - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Kategorie Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Zvolte, kterou akci chcete provést pro nepoužívané odkazy. Code Style - Code Style + Styl kódu @@ -244,7 +244,7 @@ Disabled - Disabled + Zakázáno @@ -292,6 +292,16 @@ Povolit diagnostiku pull Razor (experimentální, vyžaduje restart) + + Enable all features in opened files from source generators (experimental) + Povolit všechny funkce v otevřených souborech ze zdrojových generátorů (experimentální) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Povolit protokolování souboru pro diagnostiku (protokolování ve složce '%Temp%\Roslyn') + + Enable 'pull' diagnostics (experimental, requires restart) Povolit diagnostiku pull (experimentální, vyžaduje restart) @@ -299,7 +309,7 @@ Enabled - Enabled + Povoleno @@ -319,7 +329,12 @@ Error - Error + Chyba + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Formátovat dokument Formatting - Formatting + Formátování @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + Implementovaní členové + + + + Implementing members + Implementace členů @@ -387,6 +412,11 @@ Indexováno v úložišti + + Inheritance Margin (experimental) + Míra dědičnosti (experimentální) + + Inline Hints (experimental) Vložené nápovědy (experimentální) @@ -419,7 +449,7 @@ Keep - Keep + Zachovat @@ -482,6 +512,16 @@ Přesunout do oboru názvů + + Multiple members are inherited + Více členů se dědí. + + + + Multiple members are inherited on line {0} + Více členů se dědí na řádku {0}. + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Název koliduje s existujícím názvem typu. @@ -647,6 +687,11 @@ Pravidla pojmenování + + Navigate to '{0}' + Přejít na {0} + + Never if unnecessary Nikdy, pokud jsou nadbytečné @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Předvolby nových řádků (experimentální): Newline (\\n) - Newline (\\n) + Nový řádek (\\n) No unused references were found. - No unused references were found. + Nenašly se žádné nepoužívané odkazy. @@ -707,9 +752,19 @@ Jiné + + Overridden members + Přepsaní členové + + + + Overriding members + Přepsání členů + + Packages - Packages + Balíčky @@ -804,7 +859,7 @@ Projects - Projects + Projekty @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Pouze refaktoring Reference - Reference + Odkaz @@ -829,12 +884,12 @@ Remove All - Remove All + Odebrat vše Remove Unused References - Remove Unused References + Odebrat nepoužívané odkazy @@ -854,7 +909,7 @@ Require: - Require: + Vyžadovat: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Nastavení hledání @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Zobrazit příkaz Odebrat nepoužívané odkazy v Průzkumníkovi řešení (experimentální) @@ -977,6 +1032,11 @@ Zobrazit nápovědy pro proměnné s odvozenými typy + + Show inheritance margin + Zobrazit míru dědičnosti + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Některé barvy barevného schématu se přepsaly změnami na stránce možností Prostředí > Písma a barvy. Pokud chcete zrušit všechna přizpůsobení, vyberte na stránce Písma a barvy možnost Použít výchozí. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Návrh @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Symboly bez odkazů Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Stisknout dvakrát tabulátor, aby se vložily argumenty (experimentální) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Tato akce se nedá vrátit. Chcete pokračovat? @@ -1039,7 +1099,7 @@ Title - Title + Název @@ -1062,6 +1122,11 @@ Název typu se rozpoznal. "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local Nepoužitá hodnota se explicitně přiřadí nepoužité lokální hodnotě. @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Aktualizují se odkazy projektů... @@ -1104,7 +1169,7 @@ Value - Value + Hodnota @@ -1139,7 +1204,7 @@ Warning - Warning + Upozornění @@ -1277,6 +1342,11 @@ Není platnou hodnotou. + + '{0}' is inherited + {0} se dědí. + + '{0}' will be changed to abstract. {0} se změní na abstraktní. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index e60716fd3d02b..301bfcadbfd8d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Aktion Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Mehrere Leerzeilen zulassen Allow statement immediately after block - Allow statement immediately after block + Anweisung direkt nach Block zulassen @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analyse Analyzing project references... - Analyzing project references... + Projektverweise werden analysiert... Apply - Apply + Anwenden @@ -99,7 +99,7 @@ Assemblies - Assemblies + Assemblys @@ -162,29 +162,29 @@ Aufrufsite - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Kategorie Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Wählen Sie die Aktion aus, die Sie für nicht verwendete Verweise ausführen möchten. Code Style - Code Style + Codeformat @@ -244,7 +244,7 @@ Disabled - Disabled + Deaktiviert @@ -292,6 +292,16 @@ Razor-Pull-Diagnose aktivieren (experimentell, Neustart erforderlich) + + Enable all features in opened files from source generators (experimental) + Aktivieren aller Funktionen in geöffneten Dateien von Quellgeneratoren (experimentell) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Dateiprotokollierung für Diagnose aktivieren (protokolliert im Ordner "%Temp%\Roslyn") + + Enable 'pull' diagnostics (experimental, requires restart) Pull-Diagnose aktivieren (experimentell, Neustart erforderlich) @@ -299,7 +309,7 @@ Enabled - Enabled + Aktiviert @@ -319,7 +329,12 @@ Error - Error + Fehler + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Dokument formatieren Formatting - Formatting + Formatierung @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + Implementierte Member + + + + Implementing members + Member werden implementiert. @@ -387,6 +412,11 @@ In Repository indiziert + + Inheritance Margin (experimental) + Vererbungsrand (experimentell) + + Inline Hints (experimental) Inlinehinweise (experimentell) @@ -419,7 +449,7 @@ Keep - Keep + Beibehalten @@ -482,6 +512,16 @@ In Namespace verschieben + + Multiple members are inherited + Mehrere Member werden geerbt. + + + + Multiple members are inherited on line {0} + In Zeile {0} werden mehrere Member geerbt. + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Der Name verursacht einen Konflikt mit einem vorhandenen Typnamen. @@ -647,6 +687,11 @@ Benennungsregeln + + Navigate to '{0}' + Zu "{0}" navigieren + + Never if unnecessary Nie, wenn nicht erforderlich @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Einstellungen für neue Zeilen (experimentell): Newline (\\n) - Newline (\\n) + Zeilenumbruch (\\n) No unused references were found. - No unused references were found. + Es wurden keine nicht verwendeten Verweise gefunden. @@ -707,9 +752,19 @@ Andere + + Overridden members + Außer Kraft gesetzte Member + + + + Overriding members + Member werden außer Kraft gesetzt. + + Packages - Packages + Pakete @@ -804,7 +859,7 @@ Projects - Projects + Projekte @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Nur Refactoring Reference - Reference + Verweis @@ -829,12 +884,12 @@ Remove All - Remove All + Alle entfernen Remove Unused References - Remove Unused References + Nicht verwendete Verweise entfernen @@ -854,7 +909,7 @@ Require: - Require: + Erforderlich: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Sucheinstellungen @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Befehl "Nicht verwendete Verweise entfernen" in Projektmappen-Explorer anzeigen (experimentell) @@ -977,6 +1032,11 @@ Hinweise für Variablen mit abgeleiteten Typen anzeigen + + Show inheritance margin + Vererbungsrand anzeigen + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Einige Farbschemafarben werden durch Änderungen überschrieben, die auf der Optionsseite "Umgebung" > "Schriftarten und Farben" vorgenommen wurden. Wählen Sie auf der Seite "Schriftarten und Farben" die Option "Standardwerte verwenden" aus, um alle Anpassungen rückgängig zu machen. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Vorschlag @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Symbole ohne Verweise Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Zweimaliges Drücken der TAB-Taste zum Einfügen von Argumenten (experimentell) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie den Vorgang fortsetzen? @@ -1039,7 +1099,7 @@ Title - Title + Titel @@ -1062,6 +1122,11 @@ Der Typname wurde erkannt. "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local Der nicht verwendete Wert wird explizit einer nicht verwendeten lokalen Variablen zugewiesen. @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Projektverweise werden aktualisiert... @@ -1104,7 +1169,7 @@ Value - Value + Wert @@ -1139,7 +1204,7 @@ Warning - Warning + Warnung @@ -1277,6 +1342,11 @@ Kein gültiger Wert. + + '{0}' is inherited + "{0}" wird geerbt. + + '{0}' will be changed to abstract. "{0}" wird in abstrakten Wert geändert. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 42add4119c7dd..0c45d7110260e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Acción Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Permitir varias líneas en blanco Allow statement immediately after block - Allow statement immediately after block + Permitir una instrucción inmediatamente después del bloque @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analizadores Analyzing project references... - Analyzing project references... + Analizando las referencias del proyecto... Apply - Apply + Aplicar @@ -99,7 +99,7 @@ Assemblies - Assemblies + Ensamblados @@ -162,29 +162,29 @@ Sitio de llamada - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Categoría Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Elija la acción que quiera realizar en las referencias sin usar. Code Style - Code Style + Estilo de código @@ -244,7 +244,7 @@ Disabled - Disabled + Deshabilitado @@ -292,6 +292,16 @@ Habilitar diagnósticos "pull" de Razor (experimental, requiere reiniciar) + + Enable all features in opened files from source generators (experimental) + Habilitar todas las características de los archivos abiertos de los generadores de origen (experimental) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Habilitar registro de archivos para el diagnóstico (registrados en la carpeta "%Temp%\Roslyn") + + Enable 'pull' diagnostics (experimental, requires restart) Habilitar diagnósticos "pull" (experimental, requiere reiniciar) @@ -299,7 +309,7 @@ Enabled - Enabled + Habilitado @@ -319,7 +329,12 @@ Error - Error + Error + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Dar formato al documento Formatting - Formatting + Formato @@ -359,7 +374,17 @@ Id - Id + Id. + + + + Implemented members + Miembros implementados + + + + Implementing members + Implementando miembros @@ -387,6 +412,11 @@ Indexado en el repositorio + + Inheritance Margin (experimental) + Margen de herencia (experimental) + + Inline Hints (experimental) Sugerencias en línea (experimental) @@ -419,7 +449,7 @@ Keep - Keep + Mantener @@ -482,6 +512,16 @@ Mover a espacio de nombres + + Multiple members are inherited + Varios miembros son heredados + + + + Multiple members are inherited on line {0} + Varios miembros se heredan en la línea {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. El nombre está en conflicto con un nombre de tipo que ya existía. @@ -647,6 +687,11 @@ Reglas de nomenclatura + + Navigate to '{0}' + Navegar a "{0}" + + Never if unnecessary Nunca si es innecesario @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Nuevas preferencias de línea (experimental): Newline (\\n) - Newline (\\n) + Nueva línea (\\n) No unused references were found. - No unused references were found. + No se encontraron referencias sin usar. @@ -707,9 +752,19 @@ Otros + + Overridden members + Miembros reemplazados + + + + Overriding members + Reemplando miembros + + Packages - Packages + Paquetes @@ -804,7 +859,7 @@ Projects - Projects + Proyectos @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Solo refactorización Reference - Reference + Referencia @@ -829,12 +884,12 @@ Remove All - Remove All + Quitar todo Remove Unused References - Remove Unused References + Quitar referencias sin usar @@ -854,7 +909,7 @@ Require: - Require: + Requerir: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Buscar configuración @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Mostrar el comando "Quitar referencias sin usar" en el Explorador de soluciones (experimental) @@ -977,6 +1032,11 @@ Mostrar sugerencias para las variables con tipos inferidos + + Show inheritance margin + Mostrar margen de herencia + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Algunos de los colores de la combinación se reemplazan por los cambios realizados en la página de opciones de Entorno > Fuentes y colores. Elija "Usar valores predeterminados" en la página Fuentes y colores para revertir todas las personalizaciones. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Sugerencia @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Símbolos sin referencias Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Presionar dos veces la tecla Tab para insertar argumentos (experimental) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Esta acción no se puede deshacer. ¿Quiere continuar? @@ -1039,7 +1099,7 @@ Title - Title + Título @@ -1062,6 +1122,11 @@ Se reconoce el nombre de tipo "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local El valor sin usar se asigna explícitamente a una variable local sin usar @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Actualizando las referencias del proyecto... @@ -1104,7 +1169,7 @@ Value - Value + Valor @@ -1139,7 +1204,7 @@ Warning - Warning + Advertencia @@ -1277,6 +1342,11 @@ No es un valor válido + + '{0}' is inherited + "{0}" is heredado + + '{0}' will be changed to abstract. "{0}" se cambiará a abstracto. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index ce66c20ea3ed9..1f65b3053e75c 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Action Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Autoriser plusieurs lignes vides Allow statement immediately after block - Allow statement immediately after block + Autoriser l'instruction juste après le bloc @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analyseurs Analyzing project references... - Analyzing project references... + Analyse des références de projet... Apply - Apply + Appliquer @@ -99,7 +99,7 @@ Assemblies - Assemblies + Assemblys @@ -162,29 +162,29 @@ Site d'appel - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Catégorie Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Choisissez l'action à effectuer sur les références inutilisées. Code Style - Code Style + Style de code @@ -244,7 +244,7 @@ Disabled - Disabled + Désactivé @@ -292,6 +292,16 @@ Activer les diagnostics de 'tirage (pull)' Razor (expérimental, nécessite un redémarrage) + + Enable all features in opened files from source generators (experimental) + Activer toutes les fonctionnalités dans les fichiers ouverts à partir des générateurs de code source (expérimental) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Activer la journalisation des fichiers pour les Diagnostics (connexion au dossier « %Temp% \Roslyn ») + + Enable 'pull' diagnostics (experimental, requires restart) Activer les diagnostics de 'tirage (pull)' (expérimental, nécessite un redémarrage) @@ -299,7 +309,7 @@ Enabled - Enabled + Activé @@ -319,7 +329,12 @@ Error - Error + Erreur + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Mettre en forme le document Formatting - Formatting + Mise en forme @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + Membres implémentés + + + + Implementing members + Implémentation des membres @@ -387,6 +412,11 @@ Indexé dans le dépôt + + Inheritance Margin (experimental) + Marge d’héritage (expérimental) + + Inline Hints (experimental) Indicateurs inline (expérimental) @@ -419,7 +449,7 @@ Keep - Keep + Conserver @@ -482,6 +512,16 @@ Déplacer vers un espace de noms + + Multiple members are inherited + Plusieurs membres sont hérités + + + + Multiple members are inherited on line {0} + Plusieurs membres sont hérités à la ligne {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Le nom est en conflit avec un nom de type existant. @@ -647,6 +687,11 @@ Règles de nommage + + Navigate to '{0}' + Accéder à « {0} » + + Never if unnecessary Jamais si ce n'est pas nécessaire @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Préférences de nouvelle ligne (expérimental) : Newline (\\n) - Newline (\\n) + Nouvelle ligne (\\n) No unused references were found. - No unused references were found. + Références inutilisées introuvables. @@ -707,9 +752,19 @@ Autres + + Overridden members + Membres remplacés + + + + Overriding members + Remplacement des membres + + Packages - Packages + Packages @@ -804,7 +859,7 @@ Projects - Projects + Projets @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Refactoring Only Reference - Reference + Référence @@ -829,12 +884,12 @@ Remove All - Remove All + Tout supprimer Remove Unused References - Remove Unused References + Supprimer les références inutilisées @@ -854,7 +909,7 @@ Require: - Require: + Nécessite : @@ -904,7 +959,7 @@ Search Settings - Search Settings + Paramètres de recherche @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Afficher la commande Supprimer les références inutilisées dans l'Explorateur de solutions (expérimental) @@ -977,6 +1032,11 @@ Afficher les indicateurs pour les variables ayant des types déduits + + Show inheritance margin + Afficher la marge d’héritage + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Certaines couleurs du modèle de couleurs sont substituées à la suite des changements apportés dans la page d'options Environnement > Polices et couleurs. Choisissez Utiliser les valeurs par défaut dans la page Polices et couleurs pour restaurer toutes les personnalisations. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Suggestion @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Symboles sans références Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Appuyer deux fois sur Tab pour insérer des arguments (expérimental) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Il est impossible d'annuler cette action. Voulez-vous continuer ? @@ -1039,7 +1099,7 @@ Title - Title + Titre @@ -1062,6 +1122,11 @@ Le nom de type est reconnu "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local La valeur inutilisée est explicitement affectée à une variable locale inutilisée @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Mise à jour des références de projet... @@ -1104,7 +1169,7 @@ Value - Value + Valeur @@ -1139,7 +1204,7 @@ Warning - Warning + Avertissement @@ -1277,6 +1342,11 @@ Valeur non valide + + '{0}' is inherited + « {0} » est hérité + + '{0}' will be changed to abstract. '{0}' va être changé en valeur abstraite. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 72a65ec3d00c1..0349965882bbd 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Azione Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Consenti più righe vuote Allow statement immediately after block - Allow statement immediately after block + Consenti l'istruzione subito dopo il blocco @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analizzatori Analyzing project references... - Analyzing project references... + Analisi dei riferimenti al progetto... Apply - Apply + Applica @@ -99,7 +99,7 @@ Assemblies - Assemblies + Assembly @@ -162,29 +162,29 @@ Sito di chiamata - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Categoria Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Scegliere l'operazione da eseguire sui riferimenti inutilizzati. Code Style - Code Style + Stile codice @@ -244,7 +244,7 @@ Disabled - Disabled + Disabilitato @@ -292,6 +292,16 @@ Abilita diagnostica 'pull' di Razor (sperimentale, richiede il riavvio) + + Enable all features in opened files from source generators (experimental) + Abilita tutte le funzionalità nei file aperti dai generatori di origine (sperimentale) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Abilita la registrazione dei file a scopo di diagnostica (nella cartella '%Temp%\Roslyn') + + Enable 'pull' diagnostics (experimental, requires restart) Abilita diagnostica 'pull' (sperimentale, richiede il riavvio) @@ -299,7 +309,7 @@ Enabled - Enabled + Abilitato @@ -319,7 +329,12 @@ Error - Error + Errore + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Formatta documento Formatting - Formatting + Formattazione @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + Membri implementati + + + + Implementing members + Membri di implementazione @@ -387,6 +412,11 @@ Indicizzata nel repository + + Inheritance Margin (experimental) + Margine ereditarietà (sperimentale) + + Inline Hints (experimental) Suggerimenti inline (sperimentale) @@ -419,7 +449,7 @@ Keep - Keep + Mantieni @@ -482,6 +512,16 @@ Sposta nello spazio dei nomi + + Multiple members are inherited + Più membri vengono ereditati + + + + Multiple members are inherited on line {0} + Nella riga {0} vengono ereditati più membri + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Il nome è in conflitto con un nome di tipo esistente. @@ -647,6 +687,11 @@ Regole di denominazione + + Navigate to '{0}' + Passa a '{0}' + + Never if unnecessary Mai se non necessario @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Preferenze per nuova riga (sperimentale): Newline (\\n) - Newline (\\n) + Nuova riga (\\n) No unused references were found. - No unused references were found. + Non sono stati trovati riferimenti inutilizzati. @@ -707,9 +752,19 @@ Altri + + Overridden members + Membri sovrascritti + + + + Overriding members + Override di membri + + Packages - Packages + Pacchetti @@ -804,7 +859,7 @@ Projects - Projects + Progetti @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Refactoring Only Reference - Reference + Riferimento @@ -829,12 +884,12 @@ Remove All - Remove All + Rimuovi tutto Remove Unused References - Remove Unused References + Rimuovi riferimenti inutilizzati @@ -854,7 +909,7 @@ Require: - Require: + Richiedi: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Impostazioni di ricerca @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Mostra il comando "Rimuovi riferimenti inutilizzati" in Esplora soluzioni (sperimentale) @@ -977,6 +1032,11 @@ Mostra suggerimenti per variabili con tipi dedotti + + Show inheritance margin + Mostra margine di ereditarietà + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Alcuni colori della combinazione colori sono sostituiti dalle modifiche apportate nella pagina di opzioni Ambiente > Tipi di carattere e colori. Scegliere `Usa impostazioni predefinite` nella pagina Tipi di carattere e colori per ripristinare tutte le personalizzazioni. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Suggerimento @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Simboli senza riferimenti Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Premi due volte TAB per inserire gli argomenti (sperimentale) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Questa azione non può essere annullata. Continuare? @@ -1039,7 +1099,7 @@ Title - Title + Titolo @@ -1062,6 +1122,11 @@ Il nome di tipo è riconosciuto "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local Il valore inutilizzato viene assegnato in modo esplicito a una variabile locale inutilizzata @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Aggiornamento dei riferimenti al progetto... @@ -1104,7 +1169,7 @@ Value - Value + Valore @@ -1139,7 +1204,7 @@ Warning - Warning + Avviso @@ -1277,6 +1342,11 @@ Valore non valido + + '{0}' is inherited + '{0}' viene ereditato + + '{0}' will be changed to abstract. '{0}' verrà modificato in astratto. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 7f21ef693eaec..23f9994b62188 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + 操作 Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + 複数の空白行を許可する Allow statement immediately after block - Allow statement immediately after block + ブロックの直後にステートメントを許可する @@ -79,17 +79,17 @@ Analyzers - Analyzers + アナライザー Analyzing project references... - Analyzing project references... + プロジェクト参照を分析しています... Apply - Apply + 適用 @@ -99,7 +99,7 @@ Assemblies - Assemblies + アセンブリ @@ -162,29 +162,29 @@ 呼び出しサイト - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + カテゴリ Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + 未使用の参照に対して実行する操作を選択します。 Code Style - Code Style + コード スタイル @@ -244,7 +244,7 @@ Disabled - Disabled + 無効 @@ -292,6 +292,16 @@ Razor 'pull' 診断を有効にする (試験段階、再起動が必要) + + Enable all features in opened files from source generators (experimental) + ソース ジェネレーターから開いたファイル内のすべての機能を有効にする (試験段階) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + 診断のファイル ログを有効にする (' %Temp%/Roslyn ' フォルダーにログインしています) + + Enable 'pull' diagnostics (experimental, requires restart) 'pull' 診断を有効にする (試験段階、再起動が必要) @@ -299,7 +309,7 @@ Enabled - Enabled + 有効 @@ -319,7 +329,12 @@ Error - Error + エラー + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + ドキュメントのフォーマット Formatting - Formatting + 書式設定 @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + 実装されたメンバー + + + + Implementing members + メンバーを実装中 @@ -387,6 +412,11 @@ リポジトリ内でインデックス付け + + Inheritance Margin (experimental) + 継承の余白 (試験段階) + + Inline Hints (experimental) インラインのヒント (試験段階) @@ -419,7 +449,7 @@ Keep - Keep + 保持 @@ -482,6 +512,16 @@ 名前空間に移動します + + Multiple members are inherited + 複数のメンバーが継承済み + + + + Multiple members are inherited on line {0} + 複数のメンバーが行 {0} に継承されています + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 名前が既存の型名と競合します。 @@ -647,6 +687,11 @@ 名前付けルール + + Navigate to '{0}' + '{0}' に移動する + + Never if unnecessary 不必要なら保持しない @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + 改行設定 (試験段階): Newline (\\n) - Newline (\\n) + 改行 (\\n) No unused references were found. - No unused references were found. + 未使用の参照が見つかりませんでした。 @@ -707,9 +752,19 @@ その他 + + Overridden members + 上書きされたメンバー + + + + Overriding members + メンバーを上書き中 + + Packages - Packages + パッケージ @@ -804,7 +859,7 @@ Projects - Projects + プロジェクト @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + リファクタリングのみ Reference - Reference + 参照 @@ -829,12 +884,12 @@ Remove All - Remove All + すべて削除 Remove Unused References - Remove Unused References + 未使用の参照を削除する @@ -854,7 +909,7 @@ Require: - Require: + 必要: @@ -904,7 +959,7 @@ Search Settings - Search Settings + 検索設定 @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + ソリューション エクスプローラーで [未使用の参照を削除する] コマンドを表示する (試験段階) @@ -977,6 +1032,11 @@ 推論された型の変数のヒントを表示する + + Show inheritance margin + 継承の余白を表示する + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 一部の配色パターンの色は、[環境] > [フォントおよび色] オプション ページで行われた変更によって上書きされます。[フォントおよび色] オプション ページで [既定値を使用] を選択すると、すべてのカスタマイズが元に戻ります。 @@ -984,7 +1044,7 @@ Suggestion - Suggestion + 提案事項 @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + 参照のないシンボル Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + タブを 2 回押して引数を挿入する (試験段階) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + この操作を元に戻すことはできません。続行しますか? @@ -1039,7 +1099,7 @@ Title - Title + タイトル @@ -1062,6 +1122,11 @@ 型名が認識されます "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local 未使用のローカルに未使用の値が明示的に割り当てられます @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + プロジェクト参照を更新しています... @@ -1104,7 +1169,7 @@ Value - Value + @@ -1139,7 +1204,7 @@ Warning - Warning + 警告 @@ -1277,6 +1342,11 @@ 有効な値ではありません + + '{0}' is inherited + '{0}' が継承済み + + '{0}' will be changed to abstract. '{0}' は抽象に変更されます。 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 2e500eba7fddc..ca4cc9445efba 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + 작업 Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + 여러 빈 줄 허용 Allow statement immediately after block - Allow statement immediately after block + 블록 바로 뒤에 문 허용 @@ -79,17 +79,17 @@ Analyzers - Analyzers + 분석기 Analyzing project references... - Analyzing project references... + 프로젝트 참조를 분석하는 중... Apply - Apply + 적용 @@ -99,7 +99,7 @@ Assemblies - Assemblies + 어셈블리 @@ -162,29 +162,29 @@ 호출 사이트 - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + 범주 Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + 사용하지 않는 참조에 대해 수행할 작업을 선택합니다. Code Style - Code Style + 코드 스타일 @@ -244,7 +244,7 @@ Disabled - Disabled + 사용 안 함 @@ -292,6 +292,16 @@ Razor '풀' 진단 사용(실험적, 다시 시작 필요) + + Enable all features in opened files from source generators (experimental) + 소스 생성기에서 열린 파일의 모든 기능 사용 (실험적) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + 진단용 파일 로깅 사용('%Temp%\Roslyn' 폴더에 로그인) + + Enable 'pull' diagnostics (experimental, requires restart) '풀' 진단 사용(실험적, 다시 시작 필요) @@ -299,7 +309,7 @@ Enabled - Enabled + 사용 @@ -319,7 +329,12 @@ Error - Error + 오류 + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + 문서 서식 Formatting - Formatting + 서식 @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + 구현된 구성원 + + + + Implementing members + 구성원을 구현하는 중 @@ -387,6 +412,11 @@ 리포지토리에서 인덱싱됨 + + Inheritance Margin (experimental) + 상속 여백(실험용) + + Inline Hints (experimental) 인라인 힌트(실험적) @@ -419,7 +449,7 @@ Keep - Keep + 유지 @@ -482,6 +512,16 @@ 네임스페이스로 이동 + + Multiple members are inherited + 여러 구성원이 상속됨 + + + + Multiple members are inherited on line {0} + 여러 구성원이 {0} 행에 상속됨 + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 이름이 기존 형식 이름과 충돌합니다. @@ -647,6 +687,11 @@ 명명 규칙 + + Navigate to '{0}' + '{0}'(으)로 이동 + + Never if unnecessary 필요한 경우 사용 안 함 @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + 새 줄 기본 설정(실험적): Newline (\\n) - Newline (\\n) + 줄 바꿈(\\n) No unused references were found. - No unused references were found. + 사용되지 않는 참조가 없습니다. @@ -707,9 +752,19 @@ 기타 + + Overridden members + 재정의된 구성원 + + + + Overriding members + 구성원 재정의 + + Packages - Packages + 패키지 @@ -804,7 +859,7 @@ Projects - Projects + 프로젝트 @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + 리팩터링만 Reference - Reference + 참조 @@ -829,12 +884,12 @@ Remove All - Remove All + 모두 제거 Remove Unused References - Remove Unused References + 사용하지 않는 참조 제거 @@ -854,7 +909,7 @@ Require: - Require: + 필요: @@ -904,7 +959,7 @@ Search Settings - Search Settings + 검색 설정 @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + 솔루션 탐색기에서 "사용하지 않는 참조 제거" 명령 표시(실험적) @@ -977,6 +1032,11 @@ 유추된 형식의 변수에 대한 힌트 표시 + + Show inheritance margin + 상속 여백 표시 + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 색 구성표의 일부 색이 [환경] > [글꼴 및 색] 옵션 페이지에서 변경한 내용에 따라 재정의됩니다. 모든 사용자 지정을 되돌리려면 [글꼴 및 색] 페이지에서 '기본값 사용'을 선택하세요. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + 제안 @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + 참조 없는 기호 Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + 두 번 탭하여 인수 삽입(실험적) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + 이 작업은 실행 취소할 수 없습니다. 계속하시겠습니까? @@ -1039,7 +1099,7 @@ Title - Title + 제목 @@ -1062,6 +1122,11 @@ 형식 이름이 인식됩니다. "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local 사용되지 않는 값이 사용되지 않는 로컬에 명시적으로 할당됩니다. @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + 프로젝트 참조를 업데이트하는 중... @@ -1104,7 +1169,7 @@ Value - Value + @@ -1139,7 +1204,7 @@ Warning - Warning + 경고 @@ -1277,6 +1342,11 @@ 값이 잘못되었습니다. + + '{0}' is inherited + '{0}'이(가) 상속되었습니다. + + '{0}' will be changed to abstract. '{0}'이(가) 추상으로 변경됩니다. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index b5b68596d83b9..aeb7bee13c4ad 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Akcja Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Zezwalaj na wiele pustych wierszy Allow statement immediately after block - Allow statement immediately after block + Zezwalaj na instrukcję bezpośrednio po bloku @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analizatory Analyzing project references... - Analyzing project references... + Analizowanie odwołań do projektu... Apply - Apply + Zastosuj @@ -99,7 +99,7 @@ Assemblies - Assemblies + Zestawy @@ -162,29 +162,29 @@ Miejsce wywołania - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Kategoria Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Wybierz akcję, którą chcesz wykonać na nieużywanych odwołaniach. Code Style - Code Style + Styl kodu @@ -244,7 +244,7 @@ Disabled - Disabled + Wyłączone @@ -292,6 +292,16 @@ Włącz diagnostykę operacji „pull” oprogramowania Razor (eksperymentalne, wymaga ponownego uruchomienia) + + Enable all features in opened files from source generators (experimental) + Włącz wszystkie funkcje w otwartych plikach z generatorów źródeł (eksperymentalne) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Włącz rejestrowanie plików w celach diagnostycznych (rejestrowane w folderze „%Temp%\Roslyn”) + + Enable 'pull' diagnostics (experimental, requires restart) Włącz diagnostykę operacji „pull” (eksperymentalne, wymaga ponownego uruchomienia) @@ -299,7 +309,7 @@ Enabled - Enabled + Włączone @@ -319,7 +329,12 @@ Error - Error + Błąd + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Formatuj dokument Formatting - Formatting + Formatowanie @@ -359,7 +374,17 @@ Id - Id + Identyfikator + + + + Implemented members + Zaimplementowane składowe + + + + Implementing members + Implementowanie składowych @@ -387,6 +412,11 @@ Indeksowane w repozytorium + + Inheritance Margin (experimental) + Margines dziedziczenia (eksperymentalny) + + Inline Hints (experimental) Wskazówki w tekście (eksperymentalne) @@ -419,7 +449,7 @@ Keep - Keep + Zachowaj @@ -482,6 +512,16 @@ Przenieś do przestrzeni nazw + + Multiple members are inherited + Dziedziczonych jest wiele składowych + + + + Multiple members are inherited on line {0} + Na linii {0} dziedziczonych jest wiele składowych + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Wystąpił konflikt z nazwą istniejącego typu. @@ -647,6 +687,11 @@ Reguły nazewnictwa + + Navigate to '{0}' + Przejdź do pozycji „{0}” + + Never if unnecessary Nigdy, jeśli niepotrzebne @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Preferencje nowego wiersza (eksperymentalne): Newline (\\n) - Newline (\\n) + Nowy wiersz (\\n) No unused references were found. - No unused references were found. + Nie znaleziono żadnych nieużywanych odwołań. @@ -707,9 +752,19 @@ Inne + + Overridden members + Zastąpione składowe + + + + Overriding members + Zastępowanie składowych + + Packages - Packages + Pakiety @@ -804,7 +859,7 @@ Projects - Projects + Projekty @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Tylko refaktoryzacja Reference - Reference + Odwołanie @@ -829,12 +884,12 @@ Remove All - Remove All + Usuń wszystko Remove Unused References - Remove Unused References + Usuń nieużywane odwołania @@ -854,7 +909,7 @@ Require: - Require: + Wymagaj: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Ustawienia wyszukiwania @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Pokaż polecenie „Usuń nieużywane odwołania” w Eksploratorze rozwiązań (eksperymentalne) @@ -977,6 +1032,11 @@ Pokaż wskazówki dla zmiennych z wnioskowanymi typami + + Show inheritance margin + Pokaż margines dziedziczenia + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Niektóre kolory w schemacie kolorów są przesłaniane przez zmiany wprowadzone na stronie opcji Środowisko > Czcionki i kolory. Wybierz pozycję „Użyj ustawień domyślnych” na stronie Czcionki i kolory, aby wycofać wszystkie dostosowania. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Sugestia @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Symbole bez odwołań Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Dwukrotnie naciśnij klawisz Tab, aby wstawić argumenty (eksperymentalna) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Tej akcji nie można cofnąć. Czy chcesz kontynuować? @@ -1039,7 +1099,7 @@ Title - Title + Tytuł @@ -1062,6 +1122,11 @@ Nazwa typu jest rozpoznawana "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local Nieużywana wartość jest jawnie przypisywana do nieużywanej zmiennej lokalnej @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Aktualizowanie odwołań projektu... @@ -1104,7 +1169,7 @@ Value - Value + Wartość @@ -1139,7 +1204,7 @@ Warning - Warning + Ostrzeżenie @@ -1277,6 +1342,11 @@ Nieprawidłowa wartość + + '{0}' is inherited + Dziedziczone: „{0}” + + '{0}' will be changed to abstract. Element „{0}” zostanie zmieniony na abstrakcyjny. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index ca39ce2664ea6..f62f1065ba862 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Ação Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Permitir várias linhas em branco Allow statement immediately after block - Allow statement immediately after block + Permitir uma instrução imediatamente após o bloco @@ -79,17 +79,17 @@ Analyzers - Analyzers + Analisadores Analyzing project references... - Analyzing project references... + Analisando as referências do projeto... Apply - Apply + Aplicar @@ -99,7 +99,7 @@ Assemblies - Assemblies + Assemblies @@ -162,29 +162,29 @@ Chamar site - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Categoria Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Escolha qual ação você deseja executar nas referências não usadas. Code Style - Code Style + Estilo do Código @@ -244,7 +244,7 @@ Disabled - Disabled + Desabilitado @@ -292,6 +292,16 @@ Habilitar o diagnóstico de 'pull' do Razor (experimental, exige uma reinicialização) + + Enable all features in opened files from source generators (experimental) + Habilitar todos os recursos nos arquivos abertos dos geradores de origem (experimental) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Habilite o registro de arquivos para diagnósticos (logado na pasta '%Temp%\Roslyn') + + Enable 'pull' diagnostics (experimental, requires restart) Habilitar o diagnóstico de 'pull' (experimental, exige uma reinicialização) @@ -299,7 +309,7 @@ Enabled - Enabled + Habilitado @@ -319,7 +329,12 @@ Error - Error + Erro + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Formatar o documento Formatting - Formatting + Formatação @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + Membros implementados + + + + Implementing members + Implementando membros @@ -387,6 +412,11 @@ Indexado no repositório + + Inheritance Margin (experimental) + Margem de Herança (experimental) + + Inline Hints (experimental) Dicas Embutidas (experimental) @@ -419,7 +449,7 @@ Keep - Keep + Manter @@ -482,6 +512,16 @@ Mover para o Namespace + + Multiple members are inherited + Múltiplos membros são herdados + + + + Multiple members are inherited on line {0} + Múltiplos membros são herdados online {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. O nome está em conflito com um nome de tipo existente. @@ -647,6 +687,11 @@ Regras de nomenclatura + + Navigate to '{0}' + Navegar até '{0}' + + Never if unnecessary Nunca se desnecessário @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Preferências de nova linha (experimental): Newline (\\n) - Newline (\\n) + Nova linha (\\n) No unused references were found. - No unused references were found. + Não foi encontrada nenhuma referência não usada. @@ -707,9 +752,19 @@ Outros + + Overridden members + Membros substituídos + + + + Overriding members + Substituindo membros + + Packages - Packages + Pacotes @@ -804,7 +859,7 @@ Projects - Projects + Projetos @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Somente Refatoração Reference - Reference + Referência @@ -829,12 +884,12 @@ Remove All - Remove All + Remover Tudo Remove Unused References - Remove Unused References + Remover as Referências Não Usadas @@ -854,7 +909,7 @@ Require: - Require: + Exigir: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Pesquisar as Configurações @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Mostrar o comando "Remover as Referências Não Usadas" no Gerenciador de Soluções (experimental) @@ -977,6 +1032,11 @@ Mostrar as dicas para as variáveis com tipos inferidos + + Show inheritance margin + Mostrar margem de herança + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Algumas cores do esquema de cores estão sendo substituídas pelas alterações feitas na página de Ambiente > Opções de Fontes e Cores. Escolha 'Usar Padrões' na página Fontes e Cores para reverter todas as personalizações. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Sugestão @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Símbolos sem referências Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Pressione Tab duas vezes para inserir argumentos (experimental) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Esta ação não pode ser desfeita. Deseja continuar? @@ -1039,7 +1099,7 @@ Title - Title + Título @@ -1062,6 +1122,11 @@ O nome do Tipo é reconhecido "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local O valor não utilizado é explicitamente atribuído a um local não utilizado @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Atualizando as referências do projeto... @@ -1104,7 +1169,7 @@ Value - Value + Valor @@ -1139,7 +1204,7 @@ Warning - Warning + Aviso @@ -1277,6 +1342,11 @@ O valor não é válido + + '{0}' is inherited + '{0}' é herdado + + '{0}' will be changed to abstract. '{0}' será alterado para abstrato. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 185ab2ad080a5..c7e53a7d54069 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Действие Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Разрешать несколько пустых строк Allow statement immediately after block - Allow statement immediately after block + Разрешать помещать оператор сразу же после блока @@ -79,17 +79,17 @@ Analyzers - Analyzers + Анализаторы Analyzing project references... - Analyzing project references... + Анализ ссылок проекта… Apply - Apply + Применить @@ -99,7 +99,7 @@ Assemblies - Assemblies + Сборки @@ -162,29 +162,29 @@ Место вызова - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Категория Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Выберите действие, которое необходимо выполнить для неиспользуемых ссылок. Code Style - Code Style + Стиль кода @@ -244,7 +244,7 @@ Disabled - Disabled + Отключено @@ -292,6 +292,16 @@ Включить диагностику "pull" Razor (экспериментальная функция, требуется перезапуск) + + Enable all features in opened files from source generators (experimental) + Включить все функции в открытых файлах из генераторов источника (экспериментальная версия) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Включить ведение журнала файлов для диагностики (в папке "%Temp%\Roslyn") + + Enable 'pull' diagnostics (experimental, requires restart) Включить диагностику "pull" (экспериментальная функция, требуется перезапуск) @@ -299,7 +309,7 @@ Enabled - Enabled + Включено @@ -319,7 +329,12 @@ Error - Error + Ошибка + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Форматировать документ Formatting - Formatting + Форматирование @@ -359,7 +374,17 @@ Id - Id + ИД + + + + Implemented members + Реализованные элементы + + + + Implementing members + Реализация элементов @@ -387,6 +412,11 @@ Индексированный в репозитории + + Inheritance Margin (experimental) + Граница наследования (экспериментальная) + + Inline Hints (experimental) Встроенные подсказки (экспериментальная функция) @@ -419,7 +449,7 @@ Keep - Keep + Сохранить @@ -482,6 +512,16 @@ Переместить в пространство имен + + Multiple members are inherited + Несколько элементов наследуются + + + + Multiple members are inherited on line {0} + Несколько элементов наследуются в строке {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Имя конфликтует с существующим именем типа. @@ -647,6 +687,11 @@ Правила именования + + Navigate to '{0}' + Перейти к {0} + + Never if unnecessary Никогда, если не требуется @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Предпочтения для новых строк (экспериментальная функция): Newline (\\n) - Newline (\\n) + Новая строка (\\n) No unused references were found. - No unused references were found. + Неиспользуемые ссылки не найдены. @@ -707,9 +752,19 @@ Другие + + Overridden members + Переопределенные элементы + + + + Overriding members + Переопределение элементов + + Packages - Packages + Пакеты @@ -804,7 +859,7 @@ Projects - Projects + Проекты @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Только рефакторинг Reference - Reference + Ссылка @@ -829,12 +884,12 @@ Remove All - Remove All + Удалить все Remove Unused References - Remove Unused References + Удалить неиспользуемые ссылки @@ -854,7 +909,7 @@ Require: - Require: + Обязательно: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Параметры поиска @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Показать команду "Удалить неиспользуемые ссылки" в Обозревателе решений (экспериментальная версия) @@ -977,6 +1032,11 @@ Отображать подсказки для переменных с выводимыми типами + + Show inheritance margin + Показать границу наследования + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Некоторые цвета цветовой схемы переопределяются изменениями, сделанными на странице "Среда" > "Шрифты и цвета". Выберите "Использовать значения по умолчанию" на странице "Шрифты и цвета", чтобы отменить все настройки. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Рекомендация @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Символы без ссылок Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Дважды нажмите клавишу TAB, чтобы вставить аргументы (экспериментальная функция) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Это действие не может быть отменено. Вы хотите продолжить? @@ -1039,7 +1099,7 @@ Title - Title + Название @@ -1062,6 +1122,11 @@ Имя типа распознано. "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local Неиспользуемое значение явным образом задано неиспользуемой локальной переменной. @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Обновление ссылок проекта… @@ -1104,7 +1169,7 @@ Value - Value + Значение @@ -1139,7 +1204,7 @@ Warning - Warning + Предупреждение @@ -1277,6 +1342,11 @@ Недопустимое значение + + '{0}' is inherited + "{0}" наследуется + + '{0}' will be changed to abstract. Элемент "{0}" будет изменен на абстрактный. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 72f968d5f2ff6..f6e7d482a1cdd 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + Eylem Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + Birden çok boş satıra izin ver Allow statement immediately after block - Allow statement immediately after block + Bloktan hemen sonra deyime izin ver @@ -79,17 +79,17 @@ Analyzers - Analyzers + Çözümleyiciler Analyzing project references... - Analyzing project references... + Proje başvuruları analiz ediliyor... Apply - Apply + Uygula @@ -99,7 +99,7 @@ Assemblies - Assemblies + Derlemeler @@ -162,29 +162,29 @@ Çağrı konumu - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + Kategori Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + Kullanılmayan başvurularda hangi eylemi gerçekleştirmek istediğinizi seçin. Code Style - Code Style + Kod Stili @@ -244,7 +244,7 @@ Disabled - Disabled + Devre dışı @@ -292,6 +292,16 @@ Razor 'pull' tanılamasını etkinleştir (deneysel, yeniden başlatma gerekir) + + Enable all features in opened files from source generators (experimental) + Kaynak oluşturuculardan alınan açık sayfalarda tüm özellikleri etkinleştir (deneysel) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + Tanılama için dosya günlüğünü etkinleştir (oturum açan '%Temp%\Roslyn' klasörü) + + Enable 'pull' diagnostics (experimental, requires restart) 'Pull' tanılamasını etkinleştir (deneysel, yeniden başlatma gerekir) @@ -299,7 +309,7 @@ Enabled - Enabled + Etkin @@ -319,7 +329,12 @@ Error - Error + Hata + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + Belgeyi biçimlendir Formatting - Formatting + Biçimlendirme @@ -359,7 +374,17 @@ Id - Id + Kimlik + + + + Implemented members + Uygulanan üyeler + + + + Implementing members + Üyeleri uygulama @@ -387,6 +412,11 @@ Depo içinde dizini oluşturulmuş + + Inheritance Margin (experimental) + Devralma Marjı (deneysel) + + Inline Hints (experimental) Satır İçi İpuçları (deneysel) @@ -419,7 +449,7 @@ Keep - Keep + Koru @@ -482,6 +512,16 @@ Ad Alanına Taşı + + Multiple members are inherited + Birden fazla üye devralındı + + + + Multiple members are inherited on line {0} + {0}. satırda birden fazla üye devralındı + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Ad, mevcut bir tür adıyla çakışıyor. @@ -647,6 +687,11 @@ Adlandırma kuralları + + Navigate to '{0}' + '{0}' öğesine git + + Never if unnecessary Gereksizse hiçbir zaman @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + Yeni satır tercihleri (deneysel): Newline (\\n) - Newline (\\n) + Yeni Satır (\\n) No unused references were found. - No unused references were found. + Kullanılmayan başvuru bulunamadı. @@ -707,9 +752,19 @@ Diğer + + Overridden members + Geçersiz kılınan üyeler + + + + Overriding members + Üyeleri geçersiz kılma + + Packages - Packages + Paketler @@ -804,7 +859,7 @@ Projects - Projects + Projeler @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + Sadece Yeniden Düzenlenme Reference - Reference + Başvuru @@ -829,12 +884,12 @@ Remove All - Remove All + Tümünü Kaldır Remove Unused References - Remove Unused References + Kullanılmayan Başvuruları Kaldır @@ -854,7 +909,7 @@ Require: - Require: + Gerektir: @@ -904,7 +959,7 @@ Search Settings - Search Settings + Arama Ayarları @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + Çözüm Gezgini'nde "Kullanılmayan Başvuruları Kaldır" komutunu göster (deneysel) @@ -977,6 +1032,11 @@ Çıkarsanan türlere sahip değişkenler için ipuçlarını göster + + Show inheritance margin + Devralma boşluğunu göster + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Bazı renk düzeni renkleri, Ortam > Yazı Tipleri ve Renkler seçenek sayfasında yapılan değişiklikler tarafından geçersiz kılınıyor. Tüm özelleştirmeleri geri döndürmek için Yazı Tipleri ve Renkler sayfasında `Varsayılanları Kullan` seçeneğini belirleyin. @@ -984,7 +1044,7 @@ Suggestion - Suggestion + Öneri @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + Başvuruları olmayan semboller Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + Bağımsız değişkenleri eklemek için iki kez dokunun (deneysel) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + Bu işlem geri alınamaz. Devam etmek istiyor musunuz? @@ -1039,7 +1099,7 @@ Title - Title + Başlık @@ -1062,6 +1122,11 @@ Tür adı tanınıyor "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local Kullanılmayan değer açıkça kullanılmayan bir yerele atandı @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + Proje başvuruları güncelleştiriliyor... @@ -1104,7 +1169,7 @@ Value - Value + Değer @@ -1139,7 +1204,7 @@ Warning - Warning + Uyarı @@ -1277,6 +1342,11 @@ Geçerli bir değer değil + + '{0}' is inherited + '{0}' devralındı + + '{0}' will be changed to abstract. '{0}' soyut olacak şekilde değiştirildi. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 81c3fdd8502ae..793a652ff626f 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + 操作 Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + 允许使用多个空白行 Allow statement immediately after block - Allow statement immediately after block + 允许块后紧跟语句 @@ -79,17 +79,17 @@ Analyzers - Analyzers + 分析器 Analyzing project references... - Analyzing project references... + 正在分析项目引用… Apply - Apply + 应用 @@ -99,7 +99,7 @@ Assemblies - Assemblies + 程序集 @@ -162,29 +162,29 @@ 调用站点 - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + 类别 Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + 选择要对未使用的引用执行的操作。 Code Style - Code Style + 代码样式 @@ -244,7 +244,7 @@ Disabled - Disabled + 已禁用 @@ -292,6 +292,16 @@ 启用 Razor“拉取”诊断(实验性,需要重启) + + Enable all features in opened files from source generators (experimental) + 从源生成器在打开的文件中启用所有功能(实验性) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + 为诊断启用文件日志记录(记录在“%Temp%\Roslyn”文件夹中) + + Enable 'pull' diagnostics (experimental, requires restart) 启用“拉取”诊断(实验性,需要重启) @@ -299,7 +309,7 @@ Enabled - Enabled + 已启用 @@ -319,7 +329,12 @@ Error - Error + 错误 + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + 设置文档的格式 Formatting - Formatting + 格式设置 @@ -359,7 +374,17 @@ Id - Id + ID + + + + Implemented members + 实现的成员 + + + + Implementing members + 正在实现成员 @@ -387,6 +412,11 @@ 已在存储库中编入索引 + + Inheritance Margin (experimental) + 继承边距(实验性) + + Inline Hints (experimental) 内联提示(实验性) @@ -419,7 +449,7 @@ Keep - Keep + 保留 @@ -482,6 +512,16 @@ 移动到命名空间 + + Multiple members are inherited + 继承了多个成员 + + + + Multiple members are inherited on line {0} + 第 {0} 行继承了多个成员 + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 名称与现有类型名称相冲突。 @@ -647,6 +687,11 @@ 命名规则 + + Navigate to '{0}' + 导航到“{0}” + + Never if unnecessary 从不(若无必要) @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + 新行首选项(实验性): Newline (\\n) - Newline (\\n) + 换行(\\n) No unused references were found. - No unused references were found. + 找不到未使用的引用。 @@ -707,9 +752,19 @@ 其他 + + Overridden members + 替代的成员 + + + + Overriding members + 替代成员 + + Packages - Packages + @@ -804,7 +859,7 @@ Projects - Projects + 项目 @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + 仅重构 Reference - Reference + 引用 @@ -829,12 +884,12 @@ Remove All - Remove All + 全部删除 Remove Unused References - Remove Unused References + 删除未使用的引用 @@ -854,7 +909,7 @@ Require: - Require: + 需要: @@ -904,7 +959,7 @@ Search Settings - Search Settings + 搜索设置 @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + 在解决方案资源管理器中显示“删除未使用的引用”命令(实验性) @@ -977,6 +1032,11 @@ 显示具有推断类型的变量的提示 + + Show inheritance margin + 显示继承边距 + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 在“环境”>“字体和颜色”选项页中所做的更改将替代某些配色方案颜色。在“字体和颜色”页中选择“使用默认值”,还原所有自定义项。 @@ -984,7 +1044,7 @@ Suggestion - Suggestion + 建议 @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + 不带引用的符号 Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + 按两次 Tab 以插入参数(实验性) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + 此操作无法撤消。是否要继续? @@ -1039,7 +1099,7 @@ Title - Title + 标题 @@ -1062,6 +1122,11 @@ 已识别类型名称 "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local 未使用的值会显式分配给未使用的本地函数 @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + 正在更新项目引用… @@ -1104,7 +1169,7 @@ Value - Value + @@ -1139,7 +1204,7 @@ Warning - Warning + 警告 @@ -1277,6 +1342,11 @@ 值无效 + + '{0}' is inherited + 已继承“{0}” + + '{0}' will be changed to abstract. “{0}”将更改为“抽象”。 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index eafb48be3a897..3da8a86c54954 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ Action - Action + 動作 Action to perform on an unused reference, such as remove or keep @@ -64,12 +64,12 @@ Allow multiple blank lines - Allow multiple blank lines + 允許多個空白行 Allow statement immediately after block - Allow statement immediately after block + 允許在區塊後面立即加上陳述式 @@ -79,17 +79,17 @@ Analyzers - Analyzers + 分析器 Analyzing project references... - Analyzing project references... + 正在分析專案參考... Apply - Apply + 套用 @@ -99,7 +99,7 @@ Assemblies - Assemblies + 組件 @@ -162,29 +162,29 @@ 呼叫網站 - - Carrage Return + Newline (\\r\\n) - Carrage Return + Newline (\\r\\n) + + Carriage Return + Newline (\r\n) + Carriage Return + Newline (\r\n) - - Carrage Return (\\r) - Carrage Return (\\r) + + Carriage Return (\r) + Carriage Return (\r) Category - Category + 分類 Choose which action you would like to perform on the unused references. - Choose which action you would like to perform on the unused references. + 選擇您要對未使用之參考執行的動作。 Code Style - Code Style + 程式碼樣式 @@ -244,7 +244,7 @@ Disabled - Disabled + 已停用 @@ -292,6 +292,16 @@ 啟用 Razor 'pull' 診斷 (實驗性,需要重新啟動) + + Enable all features in opened files from source generators (experimental) + 從來源產生器中,啟用已開啟檔案中的所有功能 (實驗性) + + + + Enable file logging for diagnostics (logged in '%Temp%\Roslyn' folder) + 啟用診斷的檔案記錄 (在 '%Temp%\Roslyn' 資料夾中記錄) + + Enable 'pull' diagnostics (experimental, requires restart) 啟用 'pull' 診斷 (實驗性,需要重新啟動) @@ -299,7 +309,7 @@ Enabled - Enabled + 已啟用 @@ -319,7 +329,12 @@ Error - Error + 錯誤 + + + + Error updating suppressions: {0} + Error updating suppressions: {0} @@ -339,12 +354,12 @@ Format document - Format document + 格式化文件 Formatting - Formatting + 格式化 @@ -359,7 +374,17 @@ Id - Id + 識別碼 + + + + Implemented members + 已實作的成員 + + + + Implementing members + 實作成員 @@ -387,6 +412,11 @@ 已在存放庫中編制索引 + + Inheritance Margin (experimental) + 繼承邊界 (實驗性) + + Inline Hints (experimental) 內嵌提示 (實驗性) @@ -419,7 +449,7 @@ Keep - Keep + 保留 @@ -482,6 +512,16 @@ 移到命名空間 + + Multiple members are inherited + 已繼承多名成員 + + + + Multiple members are inherited on line {0} + 已在行 {0} 上繼承多名成員 + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 名稱與現有類型名稱衝突。 @@ -647,6 +687,11 @@ 命名規則 + + Navigate to '{0}' + 瀏覽至 '{0}' + + Never if unnecessary 不需要時一律不要 @@ -659,17 +704,17 @@ New line preferences (experimental): - New line preferences (experimental): + 新行喜好設定 (實驗性): Newline (\\n) - Newline (\\n) + 新行 (\\n) No unused references were found. - No unused references were found. + 找不到任何未使用的參考。 @@ -707,9 +752,19 @@ 其他 + + Overridden members + 已覆寫的成員 + + + + Overriding members + 覆寫成員 + + Packages - Packages + 套件 @@ -804,7 +859,7 @@ Projects - Projects + 專案 @@ -814,12 +869,12 @@ Refactoring Only - Refactoring Only + 僅重構 Reference - Reference + 參考 @@ -829,12 +884,12 @@ Remove All - Remove All + 全部移除 Remove Unused References - Remove Unused References + 移除未使用的參考 @@ -854,7 +909,7 @@ Require: - Require: + 需要: @@ -904,7 +959,7 @@ Search Settings - Search Settings + 搜尋設定 @@ -944,7 +999,7 @@ Show "Remove Unused References" command in Solution Explorer (experimental) - Show "Remove Unused References" command in Solution Explorer (experimental) + 在方案總管 (實驗性) 中顯示「移除未使用的參考」命令 @@ -977,6 +1032,11 @@ 顯示有推斷類型之變數的提示 + + Show inheritance margin + 顯示繼承邊界 + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. [環境] > [字型和色彩選項] 頁面中所做的變更覆寫了某些色彩配置的色彩。請選擇 [字型和色彩] 頁面中的 [使用預設] 來還原所有自訂。 @@ -984,7 +1044,7 @@ Suggestion - Suggestion + 建議 @@ -999,12 +1059,12 @@ Symbols without references - Symbols without references + 沒有參考的符號 Tab twice to insert arguments (experimental) - Tab twice to insert arguments (experimental) + 按 Tab 鍵兩次可插入引數 (實驗性) @@ -1024,7 +1084,7 @@ This action cannot be undone. Do you wish to continue? - This action cannot be undone. Do you wish to continue? + 此動作無法復原。要繼續嗎? @@ -1039,7 +1099,7 @@ Title - Title + 標題 @@ -1062,6 +1122,11 @@ 已辨識類型名稱 "Type" is the programming language concept + + Underline reassigned variables + Underline reassigned variables + + Unused value is explicitly assigned to an unused local 未使用的值已明確指派至未使用的區域 @@ -1074,7 +1139,7 @@ Updating project references... - Updating project references... + 正在更新專案參考... @@ -1104,7 +1169,7 @@ Value - Value + @@ -1139,7 +1204,7 @@ Warning - Warning + 警告 @@ -1277,6 +1342,11 @@ 值無效 + + '{0}' is inherited + 已繼承 '{0}' + + '{0}' will be changed to abstract. '{0}' 會變更為抽象。 diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs index 0586c516d23ac..93d72aaa1b78c 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs @@ -20,23 +20,22 @@ public sealed partial class FileCodeModel private const int ElementDeletedDispId = 3; private const int ElementDeletedDispId2 = 4; - public bool FireEvents() + public void FireEvents() { - _codeElementTable.CleanUpDeadObjects(); + _ = _codeElementTable.CleanUpDeadObjectsAsync(State.ProjectCodeModelFactory.Listener).ReportNonFatalErrorAsync(); - var needMoreTime = _codeElementTable.NeedsCleanUp; if (this.IsZombied) { // file is removed from the solution. this can happen if a fireevent is enqueued to foreground notification service // but the file itself is removed from the solution before it has a chance to run - return needMoreTime; + return; } if (!TryGetDocument(out var document)) { // file is removed from the solution. this can happen if a fireevent is enqueued to foreground notification service // but the file itself is removed from the solution before it has a chance to run - return needMoreTime; + return; } // TODO(DustinCa): Enqueue unknown change event if a file is closed without being saved. @@ -48,29 +47,29 @@ public bool FireEvents() if (oldTree == newTree || oldTree.IsEquivalentTo(newTree, topLevel: true)) { - return needMoreTime; + return; } var eventQueue = this.CodeModelService.CollectCodeModelEvents(oldTree, newTree); if (eventQueue.Count == 0) { - return needMoreTime; + return; } var projectCodeModel = this.State.ProjectCodeModelFactory.GetProjectCodeModel(document.Project.Id); if (projectCodeModel == null) { - return needMoreTime; + return; } if (!projectCodeModel.TryGetCachedFileCodeModel(this.Workspace.GetFilePath(GetDocumentId()), out _)) { - return needMoreTime; + return; } var extensibility = (EnvDTE80.IVsExtensibility2)this.State.ServiceProvider.GetService(typeof(EnvDTE.IVsExtensibility)); if (extensibility == null) - return false; + return; foreach (var codeModelEvent in eventQueue) { @@ -95,7 +94,7 @@ public bool FireEvents() } } - return needMoreTime; + return; } private EnvDTE80.vsCMChangeKind ConvertToChangeKind(CodeModelEventType eventType) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index f6f79adc207ee..3d679fbd43688 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.Shell; @@ -22,7 +23,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel { [Export(typeof(IProjectCodeModelFactory))] [Export(typeof(ProjectCodeModelFactory))] - internal sealed class ProjectCodeModelFactory : IProjectCodeModelFactory + internal sealed class ProjectCodeModelFactory : ForegroundThreadAffinitizedObject, IProjectCodeModelFactory { private readonly ConcurrentDictionary _projectCodeModels = new ConcurrentDictionary(); @@ -31,8 +32,6 @@ internal sealed class ProjectCodeModelFactory : IProjectCodeModelFactory private readonly IThreadingContext _threadingContext; - private readonly IForegroundNotificationService _notificationService; - private readonly IAsynchronousOperationListener _listener; private readonly AsyncBatchingWorkQueue _documentsToFireEventsFor; [ImportingConstructor] @@ -41,15 +40,14 @@ public ProjectCodeModelFactory( VisualStudioWorkspace visualStudioWorkspace, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IThreadingContext threadingContext, - IForegroundNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider) + : base(threadingContext, assertIsForeground: false) { _visualStudioWorkspace = visualStudioWorkspace; _serviceProvider = serviceProvider; _threadingContext = threadingContext; - _notificationService = notificationService; - _listener = listenerProvider.GetListener(FeatureAttribute.CodeModel); + Listener = listenerProvider.GetListener(FeatureAttribute.CodeModel); // Queue up notifications we hear about docs changing. that way we don't have to fire events multiple times // for the same documents. Once enough time has passed, take the documents that were changed and run @@ -60,47 +58,62 @@ public ProjectCodeModelFactory( // We only care about unique doc-ids, so pass in this comparer to collapse streams of changes for a // single document down to one notification. EqualityComparer.Default, - _listener, + Listener, threadingContext.DisposalToken); _visualStudioWorkspace.WorkspaceChanged += OnWorkspaceChanged; } - private System.Threading.Tasks.Task ProcessNextDocumentBatchAsync( + internal IAsynchronousOperationListener Listener { get; } + + private async Task ProcessNextDocumentBatchAsync( ImmutableArray documentIds, CancellationToken cancellationToken) { + // This logic preserves the previous behavior we had with IForegroundNotificationService. + // Specifically, we don't run on the UI thread for more than 15ms at a time. And once we + // have, we wait 50ms before continuing. These constants are just what we defined from + // legacy, and otherwise have no special meaning. + const int MaxTimeSlice = 15; + var delayBetweenProcessing = TimeSpan.FromMilliseconds(50); + + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var stopwatch = SharedStopwatch.StartNew(); foreach (var documentId in documentIds) { - // Now, enqueue foreground work to actually process these documents in a serialized and incremental - // fashion. FireEventsForDocument will actually limit how much time it spends firing events so that it - // doesn't saturate the UI thread. - _notificationService.RegisterNotification( - () => FireEventsForDocument(documentId), - _listener.BeginAsyncOperation("CodeModelEvent"), - cancellationToken); + FireEventsForDocument(documentId); + + // Keep firing events for this doc, as long as we haven't exceeded the max amount + // of waiting time, and there's no user input that should take precedence. + if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IsInputPending()) + { + await this.Listener.Delay(delayBetweenProcessing, cancellationToken).ConfigureAwait(true); + stopwatch = SharedStopwatch.StartNew(); + } } - return System.Threading.Tasks.Task.CompletedTask; + return; - bool FireEventsForDocument(DocumentId documentId) + void FireEventsForDocument(DocumentId documentId) { // If we've been asked to shutdown, don't bother reporting any more events. if (_threadingContext.DisposalToken.IsCancellationRequested) - return false; + return; var projectCodeModel = this.TryGetProjectCodeModel(documentId.ProjectId); if (projectCodeModel == null) - return false; + return; var filename = _visualStudioWorkspace.GetFilePath(documentId); if (filename == null) - return false; + return; if (!projectCodeModel.TryGetCachedFileCodeModel(filename, out var fileCodeModelHandle)) - return false; + return; var codeModel = fileCodeModelHandle.Object; - return codeModel.FireEvents(); + codeModel.FireEvents(); + return; } } diff --git a/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs b/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs new file mode 100644 index 0000000000000..093df9df5d846 --- /dev/null +++ b/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs @@ -0,0 +1,77 @@ +// 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.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options +{ + public abstract class AbstractAutomationObject + { + private readonly Workspace _workspace; + private readonly string _languageName; + + protected AbstractAutomationObject(Workspace workspace, string languageName) + => (_workspace, _languageName) = (workspace, languageName); + + private protected T GetOption(PerLanguageOption2 key) + => _workspace.Options.GetOption(key, _languageName); + + private protected void SetOption(PerLanguageOption2 key, T value) + => _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options + .WithChangedOption(key, _languageName, value))); + + private protected T GetOption(Option2 key) + => _workspace.Options.GetOption(key); + + private protected void SetOption(Option2 key, T value) + => _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options + .WithChangedOption(key, value))); + + private protected int GetBooleanOption(PerLanguageOption2 key) + => NullableBooleanToInteger(GetOption(key)); + + private protected void SetBooleanOption(PerLanguageOption2 key, int value) + => SetOption(key, IntegerToNullableBoolean(value)); + + private protected int GetBooleanOption(Option2 key) + => NullableBooleanToInteger(GetOption(key)); + + private protected void SetBooleanOption(Option2 key, int value) + => SetOption(key, IntegerToNullableBoolean(value)); + + private protected string GetXmlOption(Option2> option) + => GetOption(option).ToXElement().ToString(); + + private protected string GetXmlOption(PerLanguageOption2> option) + => GetOption(option).ToXElement().ToString(); + + private protected void SetXmlOption(Option2> option, string value) + { + var convertedValue = CodeStyleOption2.FromXElement(XElement.Parse(value)); + SetOption(option, convertedValue); + } + + private protected void SetXmlOption(PerLanguageOption2> option, string value) + { + var convertedValue = CodeStyleOption2.FromXElement(XElement.Parse(value)); + SetOption(option, convertedValue); + } + + private static int NullableBooleanToInteger(bool? value) + { + if (!value.HasValue) + { + return -1; + } + + return value.Value ? 1 : 0; + } + + private static bool? IntegerToNullableBoolean(int value) + => (value < 0) ? null : (value > 0); + } +} diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs index 08c5bbf2a404e..b955d02ba0fae 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs @@ -23,7 +23,7 @@ public abstract class AbstractOptionPageControl : UserControl internal readonly OptionStore OptionStore; private readonly List _bindingExpressions = new List(); - public AbstractOptionPageControl(OptionStore optionStore) + protected AbstractOptionPageControl(OptionStore optionStore) { InitializeStyles(); diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs index 7d2aa93f7c6f2..302171e13e329 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.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.ComponentModel.Composition; using System.Threading; @@ -43,25 +41,33 @@ public CPSProjectFactory( _serviceProvider = (Shell.IAsyncServiceProvider)serviceProvider; } - IWorkspaceProjectContext IWorkspaceProjectContextFactory.CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath) + IWorkspaceProjectContext IWorkspaceProjectContextFactory.CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath) + { + return _threadingContext.JoinableTaskFactory.Run(() => + this.CreateProjectContextAsync(languageName, projectUniqueName, projectFilePath, projectGuid, hierarchy, binOutputPath, assemblyName: null, CancellationToken.None)); + } + + IWorkspaceProjectContext IWorkspaceProjectContextFactory.CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath, string? assemblyName) { return _threadingContext.JoinableTaskFactory.Run(() => - this.CreateProjectContextAsync(languageName, projectUniqueName, projectFilePath, projectGuid, hierarchy, binOutputPath, CancellationToken.None)); + this.CreateProjectContextAsync(languageName, projectUniqueName, projectFilePath, projectGuid, hierarchy, binOutputPath, assemblyName, CancellationToken.None)); } public async Task CreateProjectContextAsync( string languageName, string projectUniqueName, - string projectFilePath, + string? projectFilePath, Guid projectGuid, - object hierarchy, - string binOutputPath, + object? hierarchy, + string? binOutputPath, + string? assemblyName, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var creationInfo = new VisualStudioProjectCreationInfo { + AssemblyName = assemblyName, FilePath = projectFilePath, Hierarchy = hierarchy as IVsHierarchy, ProjectGuid = projectGuid, @@ -70,8 +76,10 @@ public async Task CreateProjectContextAsync( var visualStudioProject = await _projectFactory.CreateAndAddToWorkspaceAsync( projectUniqueName, languageName, creationInfo, cancellationToken).ConfigureAwait(true); +#pragma warning disable IDE0059 // Unnecessary assignment of a value // At this point we've mutated the workspace. So we're no longer cancellable. cancellationToken = CancellationToken.None; +#pragma warning restore IDE0059 // Unnecessary assignment of a value if (languageName == LanguageNames.FSharp) { diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index 945de21385027..2baadb03afb48 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -61,7 +61,7 @@ public bool LastDesignTimeBuildSucceeded set => _visualStudioProject.HasAllInformation = value; } - public CPSProject(VisualStudioProject visualStudioProject, VisualStudioWorkspaceImpl visualStudioWorkspace, IProjectCodeModelFactory projectCodeModelFactory, Guid projectGuid, string binOutputPath) + public CPSProject(VisualStudioProject visualStudioProject, VisualStudioWorkspaceImpl visualStudioWorkspace, IProjectCodeModelFactory projectCodeModelFactory, Guid projectGuid, string? binOutputPath) { _visualStudioProject = visualStudioProject; _visualStudioWorkspace = visualStudioWorkspace; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSourceProvider.cs index 81f089f141ac1..248921a775a57 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSourceProvider.cs @@ -16,7 +16,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore [Export(typeof(IAttachedCollectionSourceProvider))] [Name(nameof(AnalyzerItemSourceProvider))] [Order] - [AppliesToProject("(CSharp | VisualBasic) & !CPS")] // in the CPS case, the Analyzers items are created by the project system + [AppliesToProject("(CSharp | VB) & !CPS")] // in the CPS case, the Analyzers items are created by the project system internal sealed class AnalyzerItemSourceProvider : AttachedCollectionSourceProvider { [Import(typeof(AnalyzersCommandHandler))] diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs index e8eea190f8aea..f9cbaa7b1d212 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Implementation; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.Internal.VisualStudio.PlatformUI; @@ -27,6 +28,7 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using VSLangProj140; using Workspace = Microsoft.CodeAnalysis.Workspace; @@ -425,7 +427,7 @@ private void SetSeverityHandler(object sender, EventArgs args) } var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); - var waitIndicator = componentModel.GetService(); + var uiThreadOperationExecutor = componentModel.GetService(); var editHandlerService = componentModel.GetService(); try @@ -437,21 +439,23 @@ private void SetSeverityHandler(object sender, EventArgs args) // If project is using the default built-in ruleset or no ruleset, then prefer .editorconfig for severity configuration. if (pathToAnalyzerConfigDoc != null) { - waitIndicator.Wait( + uiThreadOperationExecutor.Execute( title: ServicesVSResources.Updating_severity, - message: ServicesVSResources.Updating_severity, - allowCancel: true, - action: waitContext => + defaultDescription: "", + allowCancellation: true, + showProgress: true, + action: context => { - var newSolution = selectedDiagnostic.GetSolutionWithUpdatedAnalyzerConfigSeverityAsync(selectedAction.Value, project, waitContext.CancellationToken).WaitAndGetResult(waitContext.CancellationToken); + var scope = context.AddScope(allowCancellation: true, ServicesVSResources.Updating_severity); + var newSolution = selectedDiagnostic.GetSolutionWithUpdatedAnalyzerConfigSeverityAsync(selectedAction.Value, project, context.UserCancellationToken).WaitAndGetResult(context.UserCancellationToken); var operations = ImmutableArray.Create(new ApplyChangesOperation(newSolution)); editHandlerService.Apply( _workspace, fromDocument: null, operations: operations, title: ServicesVSResources.Updating_severity, - progressTracker: waitContext.ProgressTracker, - cancellationToken: waitContext.CancellationToken); + progressTracker: new UIThreadOperationContextProgressTracker(scope), + cancellationToken: context.UserCancellationToken); }); continue; } @@ -474,10 +478,11 @@ private void SetSeverityHandler(object sender, EventArgs args) fileInfo.IsReadOnly = false; } - waitIndicator.Wait( + uiThreadOperationExecutor.Execute( title: SolutionExplorerShim.Rule_Set, - message: string.Format(SolutionExplorerShim.Checking_out_0_for_editing, Path.GetFileName(pathToRuleSet)), - allowCancel: false, + defaultDescription: string.Format(SolutionExplorerShim.Checking_out_0_for_editing, Path.GetFileName(pathToRuleSet)), + allowCancellation: false, + showProgress: false, action: c => { if (envDteProject.DTE.SourceControl.IsItemUnderSCC(pathToRuleSet)) diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersFolderItem/AnalyzersFolderItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersFolderItem/AnalyzersFolderItemSourceProvider.cs index 064a81bd42ad2..ebc4cc7b610e2 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersFolderItem/AnalyzersFolderItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersFolderItem/AnalyzersFolderItemSourceProvider.cs @@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore [Export(typeof(IAttachedCollectionSourceProvider))] [Name(nameof(AnalyzersFolderItemSourceProvider))] [Order(Before = HierarchyItemsProviderNames.Contains)] - [AppliesToProject("(CSharp | VisualBasic) & !CPS")] // in the CPS case, the Analyzers folder is created by the project system + [AppliesToProject("(CSharp | VB) & !CPS")] // in the CPS case, the Analyzers folder is created by the project system internal class AnalyzersFolderItemSourceProvider : AttachedCollectionSourceProvider { private readonly IAnalyzersCommandHandler _commandHandler; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs index 3fb86fd897416..fa6568f0d5c11 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs @@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore [Export(typeof(IAttachedCollectionSourceProvider))] [Name(nameof(CpsDiagnosticItemSourceProvider))] [Order] - [AppliesToProject("(CSharp | VisualBasic) & CPS")] + [AppliesToProject("(CSharp | VB) & CPS")] internal sealed class CpsDiagnosticItemSourceProvider : AttachedCollectionSourceProvider { private readonly IAnalyzersCommandHandler _commandHandler; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs index 10dd24063a3e3..c48962188a1f3 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs @@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore [Export(typeof(IAttachedCollectionSourceProvider))] [Name(nameof(LegacyDiagnosticItemSourceProvider))] [Order] - [AppliesToProject("(CSharp | VisualBasic) & !CPS")] + [AppliesToProject("(CSharp | VB) & !CPS")] internal sealed class LegacyDiagnosticItemSourceProvider : AttachedCollectionSourceProvider { private readonly IAnalyzersCommandHandler _commandHandler; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.BrowseObject.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.BrowseObject.cs index 92b3f70aa1c7a..1f6cf53d80bda 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.BrowseObject.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.BrowseObject.cs @@ -16,7 +16,7 @@ public BrowseObject(SourceGeneratorItem sourceGeneratorItem) } [BrowseObjectDisplayName(nameof(SolutionExplorerShim.Type_Name))] - public string TypeName => _sourceGeneratorItem.Generator.GetType().FullName; + public string TypeName => _sourceGeneratorItem.GeneratorTypeName; [BrowseObjectDisplayName(nameof(SolutionExplorerShim.Path))] public string? Path => _sourceGeneratorItem.AnalyzerReference.FullPath; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.cs index 1bbeae8558b42..acaeee97168be 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/SourceGeneratorItem.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.Internal.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Imaging.Interop; @@ -13,14 +12,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore internal sealed partial class SourceGeneratorItem : BaseItem { public ProjectId ProjectId { get; } - public ISourceGenerator Generator { get; } + + public string GeneratorAssemblyName { get; } + + // Since the type name is also used for the display text, we can just reuse that. We'll still have an explicit + // property so the assembly name/type name pair that is used in other places is also used here. + public string GeneratorTypeName => base.Text; public AnalyzerReference AnalyzerReference { get; } public SourceGeneratorItem(ProjectId projectId, ISourceGenerator generator, AnalyzerReference analyzerReference) - : base(name: generator.GetType().FullName) + : base(name: SourceGeneratedDocumentIdentity.GetGeneratorTypeName(generator)) { ProjectId = projectId; - Generator = generator; + GeneratorAssemblyName = SourceGeneratedDocumentIdentity.GetGeneratorAssemblyName(generator); AnalyzerReference = analyzerReference; } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index ce9a3a34c4740..a81eb307dff16 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -70,7 +70,10 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel } var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - var sourceGeneratedDocumentsForGeneratorById = sourceGeneratedDocuments.Where(d => d.SourceGenerator == _parentGeneratorItem.Generator).ToDictionary(d => d.Id); + var sourceGeneratedDocumentsForGeneratorById = + sourceGeneratedDocuments.Where(d => d.SourceGeneratorAssemblyName == _parentGeneratorItem.GeneratorAssemblyName && + d.SourceGeneratorTypeName == _parentGeneratorItem.GeneratorTypeName) + .ToDictionary(d => d.Id); // We must update the list on the UI thread, since the WPF elements bound to our list expect that await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); @@ -98,7 +101,7 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel } } - for (int i = 0; i < _items.Count; i++) + for (var i = 0; i < _items.Count; i++) { // If this item that we already have is still a generated document, we'll remove it from our list; the list when we're // done is going to have the new items remaining. If it no longer exists, remove it from list. @@ -121,12 +124,12 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel foreach (var document in sourceGeneratedDocumentsForGeneratorById.Values) { // Binary search to figure out where to insert - int low = 0; - int high = _items.Count; + var low = 0; + var high = _items.Count; while (low < high) { - int mid = (low + high) / 2; + var mid = (low + high) / 2; if (StringComparer.OrdinalIgnoreCase.Compare(document.HintName, ((SourceGeneratedFileItem)_items[mid]).HintName) < 0) { diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSourceProvider.cs index 6b2421d41168e..5387e7674c712 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSourceProvider.cs @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore [Export(typeof(IAttachedCollectionSourceProvider))] [Name(nameof(SourceGeneratedFileItemSourceProvider))] [Order] - [AppliesToProject("CSharp | VisualBasic")] + [AppliesToProject("CSharp | VB")] internal sealed class SourceGeneratedFileItemSourceProvider : AttachedCollectionSourceProvider { private readonly Workspace _workspace; diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 60a74982c36ea..48893c86d8ccc 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -216,7 +216,7 @@ internal async Task VerifyAssetSerializationAsync( internal async Task VerifySolutionStateSerializationAsync(Solution solution, Checksum solutionChecksum) { var solutionObjectFromSyncObject = await GetValueAsync(solutionChecksum); - Assert.True(solution.State.TryGetStateChecksums(out var solutionObjectFromSolution)); + Contract.ThrowIfFalse(solution.State.TryGetStateChecksums(out var solutionObjectFromSolution)); SolutionStateEqual(solutionObjectFromSolution, solutionObjectFromSyncObject); } diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index 29e4f69f8ff90..36aaecc9aaf47 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -24,6 +24,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests; using Roslyn.Test.Utilities; +using Roslyn.Test.Utilities.TestGenerators; using Roslyn.Utilities; using Xunit; diff --git a/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj b/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj index ff45a718eac65..cae53df8bb283 100644 --- a/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj +++ b/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj @@ -28,6 +28,7 @@ global,hub + diff --git a/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs b/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs index ba34bcc7bbca1..0067a33d28229 100644 --- a/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs @@ -28,7 +28,6 @@ using StreamJsonRpc; using Xunit; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; -using Shell = Microsoft.VisualStudio.Shell; namespace Roslyn.VisualStudio.Next.UnitTests.Services { @@ -57,6 +56,23 @@ public async Task AddDiagnosticTestAsync() Assert.Equal("id", result.Diagnostics.Single().Code); } + [Fact] + public async Task NoDiagnosticsWhenInPullMode() + { + using var workspace = CreateTestLspServer("", out _).TestWorkspace; + workspace.SetOptions(workspace.Options.WithChangedOption( + InternalDiagnosticsOptions.NormalDiagnosticMode, DiagnosticMode.Pull)); + + var document = workspace.CurrentSolution.Projects.First().Documents.First(); + + var diagnosticsMock = new Mock(MockBehavior.Strict); + // Create a mock that returns a diagnostic for the document. + SetupMockWithDiagnostics(diagnosticsMock, document.Id, await CreateMockDiagnosticDataAsync(document, "id").ConfigureAwait(false)); + + var (testAccessor, results) = await RunPublishDiagnosticsAsync(workspace, diagnosticsMock.Object, 0, document).ConfigureAwait(false); + Assert.Empty(results); + } + [Fact] public async Task AddDiagnosticWithMappedFilesTestAsync() { @@ -348,18 +364,21 @@ public async Task ClearAllDiagnosticsForMappedFileToManyDocumentsTestAsync() Assert.Empty(testAccessor.GetFileUrisInPublishDiagnostics()); } - private async Task<(InProcLanguageServer.TestAccessor, List)> RunPublishDiagnosticsAsync( + private async Task<(VisualStudioInProcLanguageServer.TestAccessor, List)> RunPublishDiagnosticsAsync( TestWorkspace workspace, IDiagnosticService diagnosticService, int expectedNumberOfCallbacks, params Document[] documentsToPublish) { var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var languageServer = await CreateLanguageServerAsync(serverStream, serverStream, workspace, diagnosticService).ConfigureAwait(false); + var languageServer = CreateLanguageServer(serverStream, serverStream, workspace, diagnosticService); // Notification target for tests to receive the notification details var callback = new Callback(expectedNumberOfCallbacks); - using var jsonRpc = new JsonRpc(clientStream, clientStream, callback); + using var jsonRpc = new JsonRpc(clientStream, clientStream, callback) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; // The json rpc messages won't necessarily come back in order by default. // So use a synchronization context to preserve the original ordering. @@ -368,32 +387,39 @@ public async Task ClearAllDiagnosticsForMappedFileToManyDocumentsTestAsync() jsonRpc.StartListening(); // Triggers language server to send notifications. - foreach (var document in documentsToPublish) - await languageServer.PublishDiagnosticsAsync(diagnosticService, document, CancellationToken.None).ConfigureAwait(false); + await languageServer.ProcessDiagnosticUpdatedBatchAsync( + diagnosticService, documentsToPublish.SelectAsArray(d => d.Id), CancellationToken.None); // Waits for all notifications to be received. - await callback.CallbackCompletedTask.Task.ConfigureAwait(false); + await callback.CallbackCompletedTask.ConfigureAwait(false); return (languageServer.GetTestAccessor(), callback.Results); - static async Task CreateLanguageServerAsync(Stream inputStream, Stream outputStream, TestWorkspace workspace, IDiagnosticService mockDiagnosticService) + static VisualStudioInProcLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, TestWorkspace workspace, IDiagnosticService mockDiagnosticService) { var dispatcherFactory = workspace.ExportProvider.GetExportedValue(); var listenerProvider = workspace.ExportProvider.GetExportedValue(); var lspWorkspaceRegistrationService = workspace.ExportProvider.GetExportedValue(); + var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var languageServer = await InProcLanguageServer.CreateAsync( - languageClient: new TestLanguageClient(), - inputStream, - outputStream, - dispatcherFactory.CreateRequestDispatcher(), - workspace, - mockDiagnosticService, - listenerProvider, + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream)) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + var languageServer = new VisualStudioInProcLanguageServer( + dispatcherFactory, + jsonRpc, + capabilitiesProvider, lspWorkspaceRegistrationService, - asyncServiceProvider: null, + listenerProvider, + NoOpLspLogger.Instance, + mockDiagnosticService, clientName: null, - CancellationToken.None).ConfigureAwait(false); + userVisibleServerName: string.Empty, + telemetryServerTypeName: string.Empty); + + jsonRpc.StartListening(); return languageServer; } } @@ -514,10 +540,11 @@ private async Task ProcessQueueAsync() private class Callback { + private readonly TaskCompletionSource _callbackCompletedTaskSource = new(); /// /// Task that can be awaited for the all callbacks to complete. /// - public TaskCompletionSource CallbackCompletedTask { get; } + public Task CallbackCompletedTask => _callbackCompletedTaskSource.Task; /// /// Serialized results of all publish diagnostic notifications received by this callback. @@ -527,7 +554,7 @@ private class Callback /// /// Lock to guard concurrent callbacks. /// - private readonly object _lock = new object(); + private readonly object _lock = new(); /// /// The expected number of times this callback should be hit. @@ -543,10 +570,12 @@ private class Callback public Callback(int expectedNumberOfCallbacks) { - CallbackCompletedTask = new TaskCompletionSource(); Results = new List(); _expectedNumberOfCallbacks = expectedNumberOfCallbacks; _currentNumberOfCallbacks = 0; + + if (expectedNumberOfCallbacks == 0) + _callbackCompletedTaskSource.SetResult(null); } [JsonRpcMethod(LSP.Methods.TextDocumentPublishDiagnosticsName)] @@ -561,9 +590,7 @@ public Task OnDiagnosticsPublished(JToken input) Results.Add(diagnosticParams); if (_currentNumberOfCallbacks == _expectedNumberOfCallbacks) - { - CallbackCompletedTask.SetResult(new object()); - } + _callbackCompletedTaskSource.SetResult(null); return Task.CompletedTask; } @@ -579,8 +606,7 @@ public TestLanguageClient() public override string Name => nameof(LspDiagnosticsTests); - protected internal override LSP.VSServerCapabilities GetCapabilities() => new(); - + public override LSP.ServerCapabilities GetCapabilities(LSP.ClientCapabilities clientCapabilities) => new(); } } } diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index f67c082e14326..10339a51a8702 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Remote; @@ -473,6 +474,32 @@ public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionVal Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); } + [Fact] + public async Task TestFrozenSourceGeneratedDocument() + { + using var workspace = TestWorkspace.CreateCSharp(@""); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution + .Projects.Single() + .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) + .Solution; + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetSolutionAsync(assetProvider, solutionChecksum, fromPrimaryBranch: true, workspaceVersion: 0, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + + var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; + var frozenText = SourceText.From("// Hello, World!"); + solution = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetSolutionAsync(assetProvider, solutionChecksum, fromPrimaryBranch: false, workspaceVersion: 1, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); diff --git a/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb b/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb index 1e278d7709711..dc9bb381a0551 100644 --- a/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb +++ b/src/VisualStudio/Core/Test/CommonControls/MemberSelectionViewModelTests.vb @@ -14,6 +14,7 @@ Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.LanguageServices.Implementation.CommonControls Imports Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp Imports Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CommonControls <[UseExportProvider]> @@ -184,7 +185,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CommonControls Function(member) New PullMemberUpSymbolViewModel(member, glyphService:=Nothing) With {.IsChecked = member.Equals(memberSymbol), .IsCheckable = True, .MakeAbstract = False}) Dim memberToDependents = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, workspaceDoc.Project, CancellationToken.None) Return New MemberSelectionViewModel( - workspace.GetService(Of IWaitIndicator), + workspace.GetService(Of IUIThreadOperationExecutor), membersViewModel, memberToDependents) End Using diff --git a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb index 2a2827dd931c1..563ecdd2b5a28 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb @@ -52,7 +52,6 @@ class 123 { } Dim buffer = workspace.Documents.First().GetTextBuffer() - WpfTestRunner.RequireWpfFact($"This test uses {NameOf(IForegroundNotificationService)}") Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Dim provider = workspace.ExportProvider.GetExportedValues(Of ITaggerProvider)().OfType(Of DiagnosticsSquiggleTaggerProvider)().Single() diff --git a/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb new file mode 100644 index 0000000000000..d8c2d3b1bf24c --- /dev/null +++ b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb @@ -0,0 +1,334 @@ +' 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.Collections.Immutable +Imports System.Text +Imports System.Threading +Imports System.Windows +Imports System.Windows.Controls +Imports System.Windows.Documents +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.InheritanceMargin +Imports Microsoft.CodeAnalysis.Shared.Extensions +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.Imaging +Imports Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +Imports Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +Imports Microsoft.VisualStudio.Text.Classification +Imports Microsoft.VisualStudio.Threading +Imports Roslyn.Test.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.InheritanceMargin + + + + Public Class InheritanceMarginViewModelTests + + Private Shared s_defaultMargin As Thickness = New Thickness(4, 1, 4, 1) + + Private Shared s_indentMargin As Thickness = New Thickness(20, 1, 4, 1) + + Private Shared Async Function VerifyAsync(markup As String, languageName As String, expectedViewModels As Dictionary(Of Integer, InheritanceMarginViewModel)) As Task + ' Add an lf before the document so that the line number starts + ' with 1, which meets the line number in the editor (but in fact all things start from 0) + Dim workspaceFile = + + CommonReferences="true"> + <%= vbLf %> + <%= markup.Replace(vbCrLf, vbLf) %> + + + + + Dim cancellationToken As CancellationToken = CancellationToken.None + Using workspace = TestWorkspace.Create(workspaceFile) + Dim testDocument = workspace.Documents.Single() + Dim document = workspace.CurrentSolution.GetDocument(testDocument.Id) + Dim service = document.GetRequiredLanguageService(Of IInheritanceMarginService) + + Dim classificationTypeMap = workspace.ExportProvider.GetExportedValue(Of ClassificationTypeMap) + Dim classificationFormatMap = workspace.ExportProvider.GetExportedValue(Of IClassificationFormatMapService) + + ' For these tests, we need to be on UI thread, so don't call ConfigureAwait(False) + Dim root = Await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(True) + Dim inheritanceItems = Await service.GetInheritanceMemberItemsAsync(document, root.FullSpan, cancellationToken).ConfigureAwait(True) + + Dim acutalLineToTagDictionary = inheritanceItems.GroupBy(Function(item) item.LineNumber) _ + .ToDictionary(Function(grouping) grouping.Key, + Function(grouping) + Dim lineNumber = grouping.Key + Dim items = grouping.Select(Function(g) g).ToImmutableArray() + Return New InheritanceMarginTag(workspace, lineNumber, items) + End Function) + Assert.Equal(expectedViewModels.Count, acutalLineToTagDictionary.Count) + + For Each kvp In expectedViewModels + Dim lineNumber = kvp.Key + Dim expectedViewModel = kvp.Value + Assert.True(acutalLineToTagDictionary.ContainsKey(lineNumber)) + + Dim acutalTag = acutalLineToTagDictionary(lineNumber) + Dim actualViewModel = InheritanceMarginViewModel.Create( + classificationTypeMap, classificationFormatMap.GetClassificationFormatMap("tooltip"), acutalTag, 1) + + VerifyTwoViewModelAreSame(expectedViewModel, actualViewModel) + Next + + End Using + End Function + + Private Shared Sub VerifyTwoViewModelAreSame(expected As InheritanceMarginViewModel, acutal As InheritanceMarginViewModel) + Assert.Equal(expected.ImageMoniker, acutal.ImageMoniker) + Dim actualTextGetFromTextBlock = acutal.ToolTipTextBlock.Inlines _ + .OfType(Of Run).Select(Function(run) run.Text) _ + .Aggregate(Function(text1, text2) text1 + text2) + ' When the text block is created, a unicode 'left to right' would be inserted between the space. + ' Make sure it is removed. + Dim leftToRightMarker = Char.ConvertFromUtf32(&H200E) + Dim actualText = actualTextGetFromTextBlock.Replace(leftToRightMarker, String.Empty) + Assert.Equal(expected.ToolTipTextBlock.Text, actualText) + Assert.Equal(expected.AutomationName, acutal.AutomationName) + Assert.Equal(expected.MenuItemViewModels.Length, acutal.MenuItemViewModels.Length) + + For i = 0 To expected.MenuItemViewModels.Length - 1 + Dim expectedMenuItem = expected.MenuItemViewModels(i) + Dim actualMenuItem = acutal.MenuItemViewModels(i) + Next + + End Sub + + Private Shared Sub VerifyMenuItem(expected As InheritanceMenuItemViewModel, actual As InheritanceMenuItemViewModel) + Assert.Equal(expected.AutomationName, actual.AutomationName) + Assert.Equal(expected.DisplayContent, actual.DisplayContent) + Assert.Equal(expected.ImageMoniker, actual.ImageMoniker) + + Dim expectedTargetMenuItem = TryCast(expected, TargetMenuItemViewModel) + Dim acutalTargetMenuItem = TryCast(actual, TargetMenuItemViewModel) + If expectedTargetMenuItem IsNot Nothing AndAlso acutalTargetMenuItem IsNot Nothing Then + Assert.Equal(expectedTargetMenuItem.Margin, acutalTargetMenuItem.Margin) + Return + End If + + Dim expectedMemberMenuItem = TryCast(expected, MemberMenuItemViewModel) + Dim acutalMemberMenuItem = TryCast(actual, MemberMenuItemViewModel) + If expectedTargetMenuItem IsNot Nothing AndAlso acutalTargetMenuItem IsNot Nothing Then + Assert.Equal(expectedMemberMenuItem.Targets.Length, acutalMemberMenuItem.Targets.Length) + For i = 0 To expectedMemberMenuItem.Targets.Length + VerifyMenuItem(expectedMemberMenuItem.Targets(i), acutalMemberMenuItem.Targets(i)) + Next + Return + End If + + ' At this stage, both of the items should be header + Assert.True(TypeOf expected Is HeaderMenuItemViewModel) + Assert.True(TypeOf actual Is HeaderMenuItemViewModel) + End Sub + + Private Shared Function CreateTextBlock(text As String) As TextBlock + Return New TextBlock With { + .Text = text + } + End Function + + + Public Function TestClassImplementsInterfaceRelationship() As Task + Dim markup = " +public interface IBar +{ +} +public class Bar : IBar +{ +}" + Dim tooltipTextForIBar = String.Format(ServicesVSResources._0_is_inherited, "interface IBar") + Dim targetForIBar = ImmutableArray.Create( + New TargetMenuItemViewModel("Bar", KnownMonikers.ClassPublic, "Bar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForBar = String.Format(ServicesVSResources._0_is_inherited, "class Bar") + Dim targetForBar = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar", KnownMonikers.InterfacePublic, "IBar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {2, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar), + tooltipTextForIBar, + 1, + targetForIBar)}, + {5, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForBar), + tooltipTextForBar, + 1, + targetForBar)}}) + End Function + + + Public Function TestClassOverridesAbstractClassRelationship() As Task + Dim markup = " +public abstract class AbsBar +{ + public abstract void Foo(); +} + +public class Bar : AbsBar +{ + public override void Foo(); +}" + + Dim tooltipTextForAbsBar = String.Format(ServicesVSResources._0_is_inherited, "class AbsBar") + Dim targetForAbsBar = ImmutableArray.Create( + New TargetMenuItemViewModel("Bar", KnownMonikers.Class, "Bar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForAbstractFoo = String.Format(ServicesVSResources._0_is_inherited, "abstract void AbsBar.Foo()") + Dim targetForAbsFoo = ImmutableArray.Create( + New TargetMenuItemViewModel("AbsBar.Foo()", KnownMonikers.MethodPublic, "AbsBar.Foo()", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForBar = String.Format(ServicesVSResources._0_is_inherited, "class Bar") + Dim targetForBar = ImmutableArray.Create( + New TargetMenuItemViewModel("AbsBar", KnownMonikers.Class, "AbsBar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForOverrideFoo = String.Format(ServicesVSResources._0_is_inherited, "override void Bar.Foo()") + Dim targetForOverrideFoo = ImmutableArray.Create( + New TargetMenuItemViewModel("Bar.Foo()", KnownMonikers.MethodPublic, "Bar.Foo()", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {2, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForAbsBar), + tooltipTextForAbsBar, + 1, + targetForAbsBar)}, + {4, New InheritanceMarginViewModel( + KnownMonikers.Overridden, + CreateTextBlock(tooltipTextForAbstractFoo), + tooltipTextForAbstractFoo, + 1, + targetForAbsFoo)}, + {7, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForBar), + tooltipTextForBar, + 1, + targetForBar)}, + {9, New InheritanceMarginViewModel( + KnownMonikers.Overriding, + CreateTextBlock(tooltipTextForOverrideFoo), + tooltipTextForOverrideFoo, + 1, + targetForOverrideFoo)}}) + End Function + + + Public Function TestInterfaceImplementsInterfaceRelationship() As Task + Dim markup = " +public interface IBar1 { } +public interface IBar2 : IBar1 { } +public interface IBar3 : IBar2 { } +" + Dim tooltipTextForIBar1 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar1") + Dim targetForIBar1 = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing, s_defaultMargin), + New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForIBar2 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar2") + Dim targetForIBar2 = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_members, KnownMonikers.Implementing, ServicesVSResources.Implementing_members), + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_indentMargin), + New HeaderMenuItemViewModel(ServicesVSResources.Implemented_members, KnownMonikers.Implemented, ServicesVSResources.Implemented_members), + New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing, s_indentMargin)) + + Dim tooltipTextForIBar3 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar3") + Dim targetForIBar3 = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_defaultMargin), + New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {2, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar1), + tooltipTextForIBar1, + 1, + targetForIBar1)}, + {3, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar2), + tooltipTextForIBar2, + 1, + targetForIBar2)}, + {4, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForIBar3), + tooltipTextForIBar3, + 1, + targetForIBar3)}}) + End Function + + + Public Function TestMutipleMemberOnSameline() As Task + Dim markup = " +using System; +interface IBar1 +{ + public event EventHandler e1, e2; +} + +public class BarSample : IBar1 +{ + public virtual event EventHandler e1, e2; +}" + + Dim tooltipTextForIBar1 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar1") + Dim targetForIBar1 = ImmutableArray.Create( + New TargetMenuItemViewModel("BarSample", KnownMonikers.ClassPublic, "BarSample", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForE1AndE2InInterface = ServicesVSResources.Multiple_members_are_inherited + Dim targetForE1AndE2InInterface = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New MemberMenuItemViewModel("event EventHandler e1", KnownMonikers.EventPublic, "event EventHandler e1", ImmutableArray.Create( + New TargetMenuItemViewModel("BarSample.e1", KnownMonikers.EventPublic, "BarSample.e1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel)), + New MemberMenuItemViewModel("event EventHandler e2", KnownMonikers.EventPublic, "event EventHandler e2", ImmutableArray.Create( + New TargetMenuItemViewModel("BarSample.e2", KnownMonikers.EventPublic, "BarSample.e2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel))) _ + .CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForBarSample = String.Format(ServicesVSResources._0_is_inherited, "class BarSample") + Dim targetForBarSample = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForE1AndE2InBarSample = ServicesVSResources.Multiple_members_are_inherited + Dim targetForE1AndE2InInBarSample = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New MemberMenuItemViewModel("event EventHandler e1", KnownMonikers.EventPublic, "event EventHandler e1", ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1.BarSample.e1", KnownMonikers.EventPublic, "IBar1.BarSample.e1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel)), + New MemberMenuItemViewModel("event EventHandler e2", KnownMonikers.EventPublic, "event EventHandler e2", ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1.BarSample.e2", KnownMonikers.EventPublic, "IBar1.BarSample.e2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel))) _ + .CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {3, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar1), + tooltipTextForIBar1, + 1, + targetForIBar1)}, + {5, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForE1AndE2InInterface), + String.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, 5), + 1, + targetForE1AndE2InInterface)}, + {8, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForBarSample), + tooltipTextForBarSample, + 1, + targetForBarSample)}, + {10, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForE1AndE2InBarSample), + String.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, 10), + 1, + targetForE1AndE2InInBarSample)}}) + End Function + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb index d259ae8730756..b2bc3eb08c6d8 100644 --- a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb @@ -99,6 +99,7 @@ visual_basic_preferred_modifier_order = partial,default,private,protected,public # Expression-level preferences visual_basic_style_prefer_isnot_expression = true +visual_basic_style_prefer_simplified_object_creation = true visual_basic_style_unused_value_assignment_preference = unused_local_variable visual_basic_style_unused_value_expression_statement_preference = unused_local_variable @@ -236,6 +237,7 @@ visual_basic_preferred_modifier_order = partial,default,private,protected,public # Expression-level preferences visual_basic_style_prefer_isnot_expression = true +visual_basic_style_prefer_simplified_object_creation = true visual_basic_style_unused_value_assignment_preference = unused_local_variable visual_basic_style_unused_value_expression_statement_preference = unused_local_variable diff --git a/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb index 6c84ddc7fe5c8..31d3adaa44277 100644 --- a/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb +++ b/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb @@ -23,6 +23,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression struct S { } record R1 { } record R2; + record class R3; + record struct R4 { } ) @@ -40,6 +42,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression + + @@ -48,6 +52,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression + + diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb index 80fe5af3d256d..fafb1ab5b1548 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb @@ -5,6 +5,7 @@ Imports System.IO Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities @@ -48,7 +49,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim fileChangeService = New MockVsFileChangeEx Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsAsyncFileChangeEx)(fileChangeService)) - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)(), AsynchronousOperationListenerProvider.NullListener) + Dim ruleSetManager = New VisualStudioRuleSetManager(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), fileChangeWatcher, AsynchronousOperationListenerProvider.NullListener) Using visualStudioRuleSet = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. @@ -92,7 +93,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using workspace = New TestWorkspace() Dim fileChangeService = New MockVsFileChangeEx Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsAsyncFileChangeEx)(fileChangeService)) - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)(), AsynchronousOperationListenerProvider.NullListener) + Dim ruleSetManager = New VisualStudioRuleSetManager(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), fileChangeWatcher, AsynchronousOperationListenerProvider.NullListener) Using visualStudioRuleSet = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. @@ -140,7 +141,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Dim listener = listenerProvider.GetListener("test") - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)(), listener) + Dim ruleSetManager = New VisualStudioRuleSetManager(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), fileChangeWatcher, listener) Using ruleSet1 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) Dim handlerCalled As Boolean = False AddHandler ruleSet1.Target.Value.UpdatedOnDisk, Sub() handlerCalled = True @@ -181,7 +182,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Dim listener = listenerProvider.GetListener("test") - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)(), listener) + Dim ruleSetManager = New VisualStudioRuleSetManager(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), fileChangeWatcher, listener) Using ruleSet1 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. @@ -226,7 +227,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using workspace = New TestWorkspace() Dim fileChangeService = New MockVsFileChangeEx Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsAsyncFileChangeEx)(fileChangeService)) - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)(), AsynchronousOperationListenerProvider.NullListener) + Dim ruleSetManager = New VisualStudioRuleSetManager(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), fileChangeWatcher, AsynchronousOperationListenerProvider.NullListener) Using ruleSet1 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. @@ -264,7 +265,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using workspace = New TestWorkspace() Dim fileChangeService = New MockVsFileChangeEx Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsAsyncFileChangeEx)(fileChangeService)) - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)(), AsynchronousOperationListenerProvider.NullListener) + Dim ruleSetManager = New VisualStudioRuleSetManager(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), fileChangeWatcher, AsynchronousOperationListenerProvider.NullListener) Using ruleSet = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) Dim generalDiagnosticOption = ruleSet.Target.Value.GetGeneralDiagnosticOption() diff --git a/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb b/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb index 8dd89b0f211a8..85d1ba5e6d7c0 100644 --- a/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb +++ b/src/VisualStudio/Core/Test/PullMemberUp/PullMemberUpViewModelTest.vb @@ -5,7 +5,6 @@ Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Editor.Host Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.LanguageServices Imports Microsoft.CodeAnalysis.PullMemberUp @@ -13,6 +12,7 @@ Imports Microsoft.CodeAnalysis.Shared.Extensions Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp Imports Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp.MainDialog +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.PullMemberUp <[UseExportProvider]> @@ -46,6 +46,10 @@ class MyClass : Level1BaseClass, Level1Interface Assert.Equal("Level2Interface", baseTypeTree(1).BaseTypeNodes(0).SymbolName) Assert.Empty(baseTypeTree(0).BaseTypeNodes(0).BaseTypeNodes) Assert.Empty(baseTypeTree(1).BaseTypeNodes(0).BaseTypeNodes) + + Assert.False(viewModel.OkButtonEnabled) + viewModel.SelectedDestination = baseTypeTree(0) + Assert.True(viewModel.OkButtonEnabled) End Function @@ -253,7 +257,7 @@ class MyClass : Level1BaseClass, Level1Interface Function(member) New PullMemberUpSymbolViewModel(member, glyphService:=Nothing) With {.IsChecked = member.Equals(memberSymbol), .IsCheckable = True, .MakeAbstract = False}) Dim memberToDependents = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, workspaceDoc.Project, CancellationToken.None) Return New PullMemberUpDialogViewModel( - workspace.GetService(Of IWaitIndicator), + workspace.GetService(Of IUIThreadOperationExecutor), membersViewModel, baseTypeTree, memberToDependents) diff --git a/src/VisualStudio/Core/Test/UnusedReferences/ProjectAssetsReaderTests.vb b/src/VisualStudio/Core/Test/UnusedReferences/ProjectAssetsReaderTests.vb new file mode 100644 index 0000000000000..5bd5ab3a59998 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnusedReferences/ProjectAssetsReaderTests.vb @@ -0,0 +1,90 @@ +' 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.Collections.Immutable +Imports System.IO +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.UnusedReferences +Imports Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences.ProjectAssets + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnusedReferences + Public Class ProjectAssetsReaderTests + Private Const TargetFramework = ".NETCoreApp,Version=v3.1" + Private Const Version3 = 3 + + + + + + + + Public Sub NoReferencesReadWhenProjectAssetsVersionNot3(version As Integer) + Dim myPackage = PackageReference("MyPackage.dll") + Dim references = ImmutableArray.Create(myPackage) + Dim projectAssets = TestProjectAssetsFile.Create(version, TargetFramework, references) + + Dim realizedReferences = ProjectAssetsReader.ReadReferences(references, projectAssets) + + Assert.Empty(realizedReferences) + End Sub + + + Public Sub ReferencesReadWhenProjectAssetsVersionIs3() + Dim myPackage = PackageReference("MyPackage.dll") + Dim references = ImmutableArray.Create(myPackage) + Dim projectAssets = TestProjectAssetsFile.Create(Version3, TargetFramework, references) + + Dim realizedReferences = ProjectAssetsReader.ReadReferences(references, projectAssets) + + Dim realizedReference = Assert.Single(realizedReferences) + Assert.Equal(myPackage.ItemSpecification, realizedReference.ItemSpecification) + End Sub + + + Public Sub ReferenceNotReadWhenReferenceNotPresent() + Dim references = ImmutableArray.Create(PackageReference("MyPackage.dll")) + Dim projectAssets = TestProjectAssetsFile.Create(Version3, TargetFramework, references) + + Dim differentReference = ImmutableArray.Create(ProjectReference("MyProject.csproj")) + + Dim realizedReferences = ProjectAssetsReader.ReadReferences(differentReference, projectAssets) + + Assert.Empty(realizedReferences) + End Sub + + + Public Sub ProjectReferencesReadHaveTheirPathAsTheItemSpecification() + Dim mylibraryPath = ".\Library\MyLibrary.csproj" + Dim references = ImmutableArray.Create(ProjectReference(mylibraryPath)) + Dim projectAssets = TestProjectAssetsFile.Create(Version3, TargetFramework, references) + + Dim realizedReferences = ProjectAssetsReader.ReadReferences(references, projectAssets) + + Dim realizedReference = Assert.Single(realizedReferences) + Assert.Equal(mylibraryPath, realizedReference.ItemSpecification) + End Sub + + Private Shared Function ProjectReference(projectPath As String, ParamArray dependencies As ReferenceInfo()) As ReferenceInfo + Return ProjectReference(projectPath, False, dependencies) + End Function + Private Shared Function ProjectReference(projectPath As String, treatAsUsed As Boolean, ParamArray dependencies As ReferenceInfo()) As ReferenceInfo + Return New ReferenceInfo(ReferenceType.Project, + projectPath, + treatAsUsed, + ImmutableArray.Create(Path.ChangeExtension(projectPath, "dll")), + dependencies.ToImmutableArray()) + End Function + + Private Shared Function PackageReference(assemblyPath As String, ParamArray dependencies As ReferenceInfo()) As ReferenceInfo + Return PackageReference(assemblyPath, False, dependencies) + End Function + Private Shared Function PackageReference(assemblyPath As String, treatAsUsed As Boolean, ParamArray dependencies As ReferenceInfo()) As ReferenceInfo + Return New ReferenceInfo(ReferenceType.Package, + Path.GetFileNameWithoutExtension(assemblyPath), + treatAsUsed, + ImmutableArray.Create(assemblyPath), + dependencies.ToImmutableArray()) + End Function + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb b/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb new file mode 100644 index 0000000000000..3ec77f7b60a46 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnusedReferences/TestProjectAssetsFile.vb @@ -0,0 +1,92 @@ +' 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.Collections.Immutable +Imports System.IO +Imports Microsoft.CodeAnalysis.UnusedReferences +Imports Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences.ProjectAssets + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnusedReferences + Friend Module TestProjectAssetsFile + Public Function Create( + version As Integer, + targetFramework As String, + references As ImmutableArray(Of ReferenceInfo)) As ProjectAssetsFile + + Dim allReferences = New List(Of ReferenceInfo) + FlattenReferences(references, allReferences) + + Dim libraries = BuildLibraries(allReferences) + Dim targets = BuildTargets(targetFramework, allReferences) + + Dim projectAssets As ProjectAssetsFile = New ProjectAssetsFile With { + .Version = version, + .Targets = targets, + .Libraries = libraries + } + + Return projectAssets + End Function + + Private Sub FlattenReferences(references As ImmutableArray(Of ReferenceInfo), allReferences As List(Of ReferenceInfo)) + For Each reference In references + FlattenReference(reference, allReferences) + Next + End Sub + + Private Sub FlattenReference(reference As ReferenceInfo, allReferences As List(Of ReferenceInfo)) + allReferences.Add(reference) + FlattenReferences(reference.Dependencies, allReferences) + End Sub + + Private Function BuildLibraries(references As List(Of ReferenceInfo)) As Dictionary(Of String, ProjectAssetsLibrary) + Dim libraries = New Dictionary(Of String, ProjectAssetsLibrary) + + For Each reference In references + Dim library = New ProjectAssetsLibrary With { + .Path = reference.ItemSpecification + } + libraries.Add(Path.GetFileNameWithoutExtension(library.Path), library) + Next + + Return libraries + End Function + + Private Function BuildTargets(targetFramework As String, references As List(Of ReferenceInfo)) As Dictionary(Of String, Dictionary(Of String, ProjectAssetsTargetLibrary)) + Dim libraries = New Dictionary(Of String, ProjectAssetsTargetLibrary) + + For Each reference In references + Dim dependencies = BuildDependencies(reference.Dependencies) + Dim library = New ProjectAssetsTargetLibrary With { + .Type = GetLibraryType(reference.ReferenceType), + .Compile = New Dictionary(Of String, ProjectAssetsTargetLibraryCompile) From { + {Path.ChangeExtension(reference.ItemSpecification, "dll"), Nothing} + }, + .Dependencies = dependencies + } + libraries(Path.GetFileNameWithoutExtension(reference.ItemSpecification)) = library + Next + + Return New Dictionary(Of String, Dictionary(Of String, ProjectAssetsTargetLibrary)) From { + {targetFramework, libraries} + } + End Function + + Private Function GetLibraryType(referenceType As ReferenceType) As String + If referenceType = ReferenceType.Package Then Return "package" + If referenceType = ReferenceType.Project Then Return "project" + Return "assembly" + End Function + + Private Function BuildDependencies(references As ImmutableArray(Of ReferenceInfo)) As Dictionary(Of String, String) + Return references.ToDictionary( + Function(reference) + Return Path.GetFileNameWithoutExtension(reference.ItemSpecification) + End Function, + Function(reference) + Return String.Empty + End Function) + End Function + End Module +End Namespace diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 1da2f3c125d4a..41967d525b60b 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -67,7 +67,8 @@ class {|Definition:C1|} Using workspace = TestWorkspace.Create(input, composition:=composition, documentServiceProvider:=TestDocumentServiceProvider.Instance) Dim presenter = New StreamingFindUsagesPresenter(workspace, workspace.ExportProvider.AsExportProvider()) - Dim context = presenter.StartSearch("test", supportsReferences:=True, CancellationToken.None) + Dim tuple = presenter.StartSearch("test", supportsReferences:=True) + Dim context = tuple.context Dim cursorDocument = workspace.Documents.First(Function(d) d.CursorPosition.HasValue) Dim cursorPosition = cursorDocument.CursorPosition.Value @@ -76,7 +77,7 @@ class {|Definition:C1|} Assert.NotNull(startDocument) Dim findRefsService = startDocument.GetLanguageService(Of IFindUsagesService) - Await findRefsService.FindReferencesAsync(startDocument, cursorPosition, context) + Await findRefsService.FindReferencesAsync(startDocument, cursorPosition, context, CancellationToken.None) Dim definitionDocument = workspace.Documents.First(Function(d) d.AnnotatedSpans.ContainsKey("Definition")) Dim definitionText = Await workspace.CurrentSolution.GetDocument(definitionDocument.Id).GetTextAsync() @@ -290,6 +291,12 @@ class { } Return results.ToImmutableArray() End Function + + Public Function GetMappedTextChangesAsync(oldDocument As Document, newDocument As Document, cancellationToken As CancellationToken) _ + As Task(Of ImmutableArray(Of (mappedFilePath As String, mappedTextChange As Microsoft.CodeAnalysis.Text.TextChange))) _ + Implements ISpanMappingService.GetMappedTextChangesAsync + Throw New NotImplementedException() + End Function End Class Private Class Excerpter diff --git a/src/VisualStudio/IntegrationTest/IntegrationService/IntegrationService.cs b/src/VisualStudio/IntegrationTest/IntegrationService/IntegrationService.cs index 29f0f01de1c9f..56ade360c0bbb 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationService/IntegrationService.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationService/IntegrationService.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.Collections.Concurrent; using System.Diagnostics; @@ -49,7 +47,7 @@ public static IntegrationService GetInstanceFromHostProcess(Process hostProcess) return (IntegrationService)Activator.GetObject(typeof(IntegrationService), uri); } - public string Execute(string assemblyFilePath, string typeFullName, string methodName) + public string? Execute(string assemblyFilePath, string typeFullName, string methodName) { var assembly = Assembly.LoadFrom(assemblyFilePath); var type = assembly.GetType(typeFullName); @@ -76,7 +74,7 @@ public string Execute(string assemblyFilePath, string typeFullName, string metho } // Ensure InProcComponents live forever - public override object InitializeLifetimeService() + public override object? InitializeLifetimeService() => null; } } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs index 2fc5e97a8f339..a79503f8baaaf 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs @@ -5,6 +5,7 @@ #nullable disable using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; using Roslyn.Test.Utilities; @@ -59,6 +60,11 @@ public override async Task InitializeAsync() VisualStudio.Editor.SetUseSuggestionMode(false); ClearEditor(); } + + // Work around potential hangs in 16.10p2 caused by the roslyn LSP server not completing initialization before solution closed. + // By waiting for the async operation tracking roslyn LSP server activation to complete we should never + // encounter the scenario where the solution closes while activation is incomplete. + VisualStudio.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.LanguageServer); } } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs index 99237db591d1b..219bebc36f675 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpArgumentProvider.cs @@ -78,6 +78,9 @@ public void Method() VisualStudio.Editor.SendKeys(VirtualKey.Tab); VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.SignatureHelp); VisualStudio.Editor.Verify.CurrentLineText("object.Equals(null$$)", assertCaretPosition: true); + + VisualStudio.Editor.SendKeys(VirtualKey.Tab); + VisualStudio.Editor.Verify.CurrentLineText("object.Equals(null)$$", assertCaretPosition: true); } [WpfFact] diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpQuickInfo.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpQuickInfo.cs index e7166062ff9d2..aebb8bc519628 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpQuickInfo.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpQuickInfo.cs @@ -40,7 +40,7 @@ static void Main(string$$[] args) VisualStudio.Editor.GetQuickInfo()); } - [WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] public void QuickInfo_Documentation() { SetUpEditor(@" @@ -55,7 +55,7 @@ static void Main(string[] args) Assert.Equal("class Program\r\nHello!", VisualStudio.Editor.GetQuickInfo()); } - [WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] public void International() { SetUpEditor(@" @@ -74,7 +74,7 @@ static void Main() This is an XML doc comment defined in code.", VisualStudio.Editor.GetQuickInfo()); } - [WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] public void SectionOrdering() { SetUpEditor(@" diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs index 32d0faeedd951..b1c4d35bdfe28 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs @@ -54,8 +54,9 @@ public static void Main() Assert.Equal(HelloWorldGenerator.GeneratedEnglishClassName, VisualStudio.Editor.GetSelectedText()); } - [WpfFact, Trait(Traits.Feature, Traits.Features.SourceGenerators)] - public void FindReferencesForFileWithDefinitionInSourceGeneratedFile() + [WpfTheory, Trait(Traits.Feature, Traits.Features.SourceGenerators)] + [CombinatorialData] + public void FindReferencesForFileWithDefinitionInSourceGeneratedFile(bool invokeFromSourceGeneratedFile) { VisualStudio.Editor.SetText(@"using System; internal static class Program @@ -67,9 +68,16 @@ public static void Main() }"); VisualStudio.Editor.PlaceCaret(HelloWorldGenerator.GeneratedEnglishClassName); + + if (invokeFromSourceGeneratedFile) + { + VisualStudio.Workspace.SetEnableOpeningSourceGeneratedFilesInWorkspaceExperiment(true); + VisualStudio.Editor.GoToDefinition($"{HelloWorldGenerator.GeneratedEnglishClassName}.cs {ServicesVSResources.generated_suffix}"); + } + VisualStudio.Editor.SendKeys(Shift(VirtualKey.F12)); - string programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; + var programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; var results = VisualStudio.FindReferencesWindow.GetContents(programReferencesCaption).OrderBy(r => r.Line).ToArray(); Assert.Collection( @@ -112,7 +120,7 @@ public static void Main() VisualStudio.Editor.PlaceCaret(HelloWorldGenerator.GeneratedEnglishClassName); VisualStudio.Editor.SendKeys(Shift(VirtualKey.F12)); - string programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; + var programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; var results = VisualStudio.FindReferencesWindow.GetContents(programReferencesCaption); var referenceInGeneratedFile = results.Single(r => r.Code.Contains("")); VisualStudio.FindReferencesWindow.NavigateTo(programReferencesCaption, referenceInGeneratedFile, isPreview: isPreview); diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs index 3e026ba6f2184..0396b709a5b54 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs @@ -11,7 +11,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.CSharp { public abstract class CSharpSquigglesCommon : AbstractEditorTest { - public CSharpSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) + protected CSharpSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) : base(instanceFactory, nameof(CSharpSquigglesCommon), projectTemplate) { } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicGenerateConstructorDialog.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicGenerateConstructorDialog.cs index 6382997b47466..36f5cdef38cf7 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicGenerateConstructorDialog.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicGenerateConstructorDialog.cs @@ -104,6 +104,7 @@ Dim k as Boolean VisualStudio.Editor.Verify.CodeAction("Generate constructor...", applyFix: true, blockUntilComplete: false); VerifyDialog(isOpen: true); VisualStudio.Editor.DialogSendKeys(DialogName, VirtualKey.Tab); + VisualStudio.Editor.DialogSendKeys(DialogName, VirtualKey.Tab); VisualStudio.Editor.PressDialogButton(DialogName, "Down"); Dialog_ClickOk(); VisualStudio.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.LightBulb); @@ -140,6 +141,7 @@ Dim k as Boolean VisualStudio.Editor.Verify.CodeAction("Generate constructor...", applyFix: true, blockUntilComplete: false); VerifyDialog(isOpen: true); VisualStudio.Editor.DialogSendKeys(DialogName, VirtualKey.Tab); + VisualStudio.Editor.DialogSendKeys(DialogName, VirtualKey.Tab); VisualStudio.Editor.DialogSendKeys(DialogName, VirtualKey.Space); Dialog_ClickOk(); VisualStudio.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.LightBulb); diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs index 74851dee01c39..fdf19bf35c4da 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs @@ -9,7 +9,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.VisualBasic { public abstract class BasicSquigglesCommon : AbstractEditorTest { - public BasicSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) + protected BasicSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) : base(instanceFactory, nameof(BasicSquigglesCommon), projectTemplate) { } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs index ff9076bd72256..b481269f5abf1 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs @@ -16,7 +16,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.Workspace { public abstract class WorkspaceBase : AbstractEditorTest { - public WorkspaceBase(VisualStudioInstanceFactory instanceFactory, string projectTemplate) + protected WorkspaceBase(VisualStudioInstanceFactory instanceFactory, string projectTemplate) : base(instanceFactory, nameof(WorkspaceBase), projectTemplate) { DefaultProjectTemplate = projectTemplate; diff --git a/src/VisualStudio/IntegrationTest/TestSetup/AsyncCompletionTracker.cs b/src/VisualStudio/IntegrationTest/TestSetup/AsyncCompletionTracker.cs index 0988ca435e01e..2f669a3dd7a65 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/AsyncCompletionTracker.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/AsyncCompletionTracker.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.Composition; using System.Threading; @@ -56,7 +54,7 @@ private void HandleAsyncCompletionTriggered(object sender, CompletionTriggeredEv // Local function void ReleaseToken(object sender, EventArgs e) - => Interlocked.Exchange(ref token, null)?.Dispose(); + => Interlocked.Exchange(ref token, null)?.Dispose(); } } } diff --git a/src/VisualStudio/IntegrationTest/TestSetup/EmptyFeatureController.cs b/src/VisualStudio/IntegrationTest/TestSetup/EmptyFeatureController.cs index 1e60bc55ce1cf..74bf71aec8e16 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/EmptyFeatureController.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/EmptyFeatureController.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 Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.IntegrationTest.Setup diff --git a/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServiceCommands.cs b/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServiceCommands.cs index b929cd5a24c6b..9a472ccfbc420 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServiceCommands.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServiceCommands.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.ComponentModel.Design; using System.Diagnostics; @@ -44,38 +42,39 @@ internal sealed class IntegrationTestServiceCommands : IDisposable private readonly MenuCommand _startMenuCmd; private readonly MenuCommand _stopMenuCmd; - private IntegrationService _service; - private IpcServerChannel _serviceChannel; + private IntegrationService? _service; + private IpcServerChannel? _serviceChannel; #pragma warning disable IDE0052 // Remove unread private members - used to hold the marshalled integration test service - private ObjRef _marshalledService; + private ObjRef? _marshalledService; #pragma warning restore IDE0052 // Remove unread private members private IntegrationTestServiceCommands(Package package) { _package = package ?? throw new ArgumentNullException(nameof(package)); + var startMenuCmdId = new CommandID(guidTestWindowCmdSet, cmdidStartIntegrationTestService); + _startMenuCmd = new MenuCommand(StartServiceCallback, startMenuCmdId) + { + Enabled = true, + Visible = true + }; + + var stopMenuCmdId = new CommandID(guidTestWindowCmdSet, cmdidStopIntegrationTestService); + _stopMenuCmd = new MenuCommand(StopServiceCallback, stopMenuCmdId) + { + Enabled = false, + Visible = false + }; + if (ServiceProvider.GetService(typeof(IMenuCommandService)) is OleMenuCommandService menuCommandService) { - var startMenuCmdId = new CommandID(guidTestWindowCmdSet, cmdidStartIntegrationTestService); - _startMenuCmd = new MenuCommand(StartServiceCallback, startMenuCmdId) - { - Enabled = true, - Visible = true - }; menuCommandService.AddCommand(_startMenuCmd); - - var stopMenuCmdId = new CommandID(guidTestWindowCmdSet, cmdidStopIntegrationTestService); - _stopMenuCmd = new MenuCommand(StopServiceCallback, stopMenuCmdId) - { - Enabled = false, - Visible = false - }; menuCommandService.AddCommand(_stopMenuCmd); } } - public static IntegrationTestServiceCommands Instance { get; private set; } + public static IntegrationTestServiceCommands? Instance { get; private set; } private IServiceProvider ServiceProvider => _package; diff --git a/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServicePackage.cs b/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServicePackage.cs index e81e8d3f286d3..f154d273ee5c1 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServicePackage.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/IntegrationTestServicePackage.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.Runtime.InteropServices; using System.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs b/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs index e0f87226415ad..e4c69f185a01d 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.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.Composition; using System.Threading.Tasks; diff --git a/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionManager.cs b/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionManager.cs index 66b6a21e29764..6691f1744b4aa 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionManager.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionManager.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.Composition; using Microsoft.CodeAnalysis.Extensions; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementExtensions.cs b/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementExtensions.cs index a33a0d6c3ae97..c65acfe624bf5 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementExtensions.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementExtensions.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Threading; using UIAutomationClient; @@ -275,10 +274,11 @@ public static IUIAutomationElement FindDescendantByPath(this IUIAutomationElemen return item; } + [DoesNotReturn] private static void ThrowUnableToFindChildException(string path, IUIAutomationElement item) { // if not found, build a list of available children for debugging purposes - var validChildren = new List(); + var validChildren = new List(); try { @@ -298,7 +298,7 @@ private static void ThrowUnableToFindChildException(string path, IUIAutomationEl string.Join(", ", validChildren))); } - private static string SimpleControlTypeName(IUIAutomationElement element) + private static string? SimpleControlTypeName(IUIAutomationElement element) { var type = ControlType.LookupById((int)element.GetCurrentPropertyValue(AutomationElementIdentifiers.ControlTypeProperty.Id)); return type?.LocalizedControlType; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementHelper.cs b/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementHelper.cs index ad5ad9ddab771..c6037ddfe7775 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementHelper.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/AutomationElementHelper.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Threading.Tasks; +using Roslyn.Utilities; using UIAutomationClient; using AutomationElementIdentifiers = System.Windows.Automation.AutomationElementIdentifiers; @@ -23,14 +22,14 @@ public static async Task ClickAutomationElementAsync(string elementName, bool re if (element != null) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); Helper.Automation.AddAutomationEventHandler( UIA_EventIds.UIA_Invoke_InvokedEventId, element, TreeScope.TreeScope_Element, cacheRequest: null, - new AutomationEventHandler((src, e) => tcs.SetResult(null))); + new AutomationEventHandler((src, e) => tcs.SetResult(default))); element.Invoke(); await tcs.Task; @@ -45,7 +44,7 @@ public static async Task ClickAutomationElementAsync(string elementName, bool re public static async Task FindAutomationElementAsync(string elementName, bool recursive = false) { - IUIAutomationElement element = null; + IUIAutomationElement? element = null; var scope = recursive ? TreeScope.TreeScope_Descendants : TreeScope.TreeScope_Children; var condition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.NameProperty.Id, elementName); @@ -55,6 +54,7 @@ await IntegrationHelper.WaitForResultAsync( () => (element = Helper.Automation.GetRootElement().FindFirst(scope, condition)) != null, expectedResult: true ).ConfigureAwait(false); + Contract.ThrowIfNull(element); return element; } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/CaptureTestNameAttribute.cs b/src/VisualStudio/IntegrationTest/TestUtilities/CaptureTestNameAttribute.cs index 0ab941add6130..ec4f414c23127 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/CaptureTestNameAttribute.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/CaptureTestNameAttribute.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.Reflection; using Xunit.Sdk; @@ -20,7 +18,7 @@ public class CaptureTestNameAttribute : BeforeAfterTestAttribute /// The name of the currently running test, or null if no test is running. /// The format is test_class_name.method_name. /// - public static string CurrentName { get; set; } + public static string? CurrentName { get; set; } public override void Before(MethodInfo methodUnderTest) { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/ClassifiedToken.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/ClassifiedToken.cs index 8b2d412c2287d..158cdda7c39be 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/ClassifiedToken.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/ClassifiedToken.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Runtime.Serialization; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common { - [System.Serializable] + [Serializable] public struct ClassifiedToken { public ClassifiedToken(string text, string classification) diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Comparison.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Comparison.cs index afe2fd56cd99d..4bac65622b25b 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Comparison.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Comparison.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.Collections.Generic; @@ -11,11 +9,17 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common { internal static class Comparison { - public static bool AreStringValuesEqual(string str1, string str2) + public static bool AreStringValuesEqual(string? str1, string? str2) => (str1 ?? "") == (str2 ?? ""); - public static bool AreArraysEqual(T[] array1, T[] array2) where T : IEquatable + public static bool AreArraysEqual(T[]? array1, T[]? array2) where T : IEquatable { + if (array1 is null || array2 is null) + { + // both must be null + return array1 == array2; + } + if (array1.Length != array2.Length) { return false; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/ErrorListItem.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/ErrorListItem.cs index abbb9219f5079..dd565f7dcec29 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/ErrorListItem.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/ErrorListItem.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 Roslyn.Utilities; @@ -29,7 +27,7 @@ public ErrorListItem(string severity, string description, string project, string Column = column; } - public bool Equals(ErrorListItem other) + public bool Equals(ErrorListItem? other) => other != null && Comparison.AreStringValuesEqual(Severity, other.Severity) && Comparison.AreStringValuesEqual(Description, other.Description) @@ -38,7 +36,7 @@ public bool Equals(ErrorListItem other) && Line == other.Line && Column == other.Column; - public override bool Equals(object obj) + public override bool Equals(object? obj) => Equals(obj as ErrorListItem); public override int GetHashCode() diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Expression.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Expression.cs index 53364d031e24b..3217bda1d6436 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Expression.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Expression.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; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Parameter.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Parameter.cs index 8440758e7faa6..54792b78cb7fb 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Parameter.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Parameter.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 Microsoft.VisualStudio.Language.Intellisense; using Roslyn.Utilities; @@ -13,9 +11,9 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common [Serializable] public class Parameter : IEquatable { - public string Name { get; set; } + public string? Name { get; set; } - public string Documentation { get; set; } + public string? Documentation { get; set; } public Parameter() { } @@ -25,7 +23,7 @@ public Parameter(IParameter actual) Documentation = actual.Documentation; } - public bool Equals(Parameter other) + public bool Equals(Parameter? other) { if (other == null) { @@ -36,13 +34,13 @@ public bool Equals(Parameter other) && Comparison.AreStringValuesEqual(Documentation, other.Documentation); } - public override bool Equals(object obj) + public override bool Equals(object? obj) => Equals(obj as Parameter); public override int GetHashCode() => Hash.Combine(Name, Hash.Combine(Documentation, 0)); public override string ToString() - => !string.IsNullOrEmpty(Documentation) ? $"{Name} ({Documentation})" : Name; + => !string.IsNullOrEmpty(Documentation) ? $"{Name} ({Documentation})" : $"{Name}"; } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/ProjectUtilities.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/ProjectUtilities.cs index a09e69fe1b54b..09c80fd0a06a4 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/ProjectUtilities.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/ProjectUtilities.cs @@ -2,23 +2,25 @@ // 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.IO; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common.ProjectUtils { public abstract class Identity { - public string Name { get; protected set; } + protected Identity(string name) + { + Name = name; + } + + public string Name { get; } } public class Project : Identity { - public Project(string name, string projectExtension = ".csproj", string relativePath = null) + public Project(string name, string projectExtension = ".csproj", string? relativePath = null) + : base(name) { - Name = name; - if (string.IsNullOrWhiteSpace(relativePath)) { RelativePath = Path.Combine(name, name + projectExtension); @@ -32,22 +34,22 @@ public Project(string name, string projectExtension = ".csproj", string relative /// /// This path is relative to the Solution file. Default value is set to ProjectName\ProjectName.csproj /// - public string RelativePath { get; } + public string? RelativePath { get; } } public class ProjectReference : Identity { public ProjectReference(string name) + : base(name) { - Name = name; } } public class AssemblyReference : Identity { public AssemblyReference(string name) + : base(name) { - Name = name; } } @@ -56,8 +58,8 @@ public class PackageReference : Identity public string Version { get; } public PackageReference(string name, string version) + : base(name) { - Name = name; Version = version; } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Reference.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Reference.cs index d58b49a904b2c..35ee94087064e 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Reference.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Reference.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 Roslyn.Utilities; @@ -15,27 +13,27 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common [Serializable] public class Reference : IEquatable { - public string FilePath { get; set; } + public string? FilePath { get; set; } public int Line { get; set; } public int Column { get; set; } - public string Code { get; set; } + public string? Code { get; set; } public Reference() { } - public bool Equals(Reference other) + public bool Equals(Reference? other) { if (other == null) { return false; } - return FilePath.Equals(other.FilePath, StringComparison.OrdinalIgnoreCase) + return string.Equals(FilePath, other.FilePath, StringComparison.OrdinalIgnoreCase) && Line == other.Line && Column == other.Column - && Code.Equals(other.Code); + && string.Equals(Code, other.Code, StringComparison.Ordinal); } - public override bool Equals(object obj) + public override bool Equals(object? obj) => Equals(obj as Reference); public override int GetHashCode() diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Signature.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Signature.cs index c29bc5e968d8b..b2cdb6e535282 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Common/Signature.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Common/Signature.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.Linq; using System.Text; @@ -15,15 +13,15 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Common [Serializable] public class Signature : IEquatable { - public string Content { get; set; } + public string? Content { get; set; } - public Parameter CurrentParameter { get; set; } + public Parameter? CurrentParameter { get; set; } - public string Documentation { get; set; } + public string? Documentation { get; set; } - public Parameter[] Parameters { get; set; } + public Parameter[]? Parameters { get; set; } - public string PrettyPrintedContent { get; set; } + public string? PrettyPrintedContent { get; set; } public Signature() { } @@ -41,7 +39,7 @@ public Signature(ISignature actual) PrettyPrintedContent = actual.PrettyPrintedContent; } - public bool Equals(Signature other) + public bool Equals(Signature? other) => other != null && Comparison.AreStringValuesEqual(Content, other.Content) && Equals(CurrentParameter, other.CurrentParameter) @@ -49,7 +47,7 @@ public bool Equals(Signature other) && Comparison.AreStringValuesEqual(Documentation, other.Documentation) && Comparison.AreArraysEqual(Parameters, other.Parameters); - public override bool Equals(object obj) + public override bool Equals(object? obj) => Equals(obj as Signature); public override int GetHashCode() diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/DialogHelpers.cs b/src/VisualStudio/IntegrationTest/TestUtilities/DialogHelpers.cs index 54ceb8e422127..480999c0d0d2a 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/DialogHelpers.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/DialogHelpers.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.Runtime.InteropServices; using System.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/EventLogCollector.cs b/src/VisualStudio/IntegrationTest/TestUtilities/EventLogCollector.cs index 450c1fbb4c710..ad6010d7a3605 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/EventLogCollector.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/EventLogCollector.cs @@ -2,11 +2,10 @@ // 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 System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Eventing.Reader; using System.IO; using System.Linq; @@ -211,8 +210,7 @@ internal static void TryWriteDotNetEntriesToFile(string filePath) if (IsLastDayOrLastFiveRecentEntry(eventLogRecord, dotNetEntries.Count)) { // Filter the entries by .NetRuntime specific ones - FeedbackItemDotNetEntry entry = null; - if (IsValidDotNetEntry(eventLogRecord, ref entry)) + if (IsValidDotNetEntry(eventLogRecord, out var entry)) { dotNetEntries.Add(entry); } @@ -285,7 +283,7 @@ private static bool IsValidWatsonEntry(EventRecord eventLogRecord) /// the provider, if it's for certain event log IDs and for VS related EXEs /// /// Entry to be checked - private static bool IsValidDotNetEntry(EventRecord eventLogRecord, ref FeedbackItemDotNetEntry dotNetEntry) + private static bool IsValidDotNetEntry(EventRecord eventLogRecord, [NotNullWhen(true)] out FeedbackItemDotNetEntry? dotNetEntry) { if (StringComparer.InvariantCultureIgnoreCase.Equals(eventLogRecord.ProviderName, DotNetProviderName) && s_dotNetEventId.Contains(eventLogRecord.Id)) @@ -300,6 +298,7 @@ private static bool IsValidDotNetEntry(EventRecord eventLogRecord, ref FeedbackI } } + dotNetEntry = null; return false; } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemDotNetEntry.cs b/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemDotNetEntry.cs index 4374d6abbc974..c1ba6eb005dc1 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemDotNetEntry.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemDotNetEntry.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.Diagnostics.Eventing.Reader; using System.Linq; @@ -45,7 +43,7 @@ internal class FeedbackItemDotNetEntry /// public FeedbackItemDotNetEntry(EventRecord eventLogRecord) { - EventTime = eventLogRecord.TimeCreated.Value.ToUniversalTime(); + EventTime = eventLogRecord.TimeCreated?.ToUniversalTime() ?? DateTime.MinValue; EventId = eventLogRecord.Id; Data = string.Join(";", eventLogRecord.Properties.Select(pr => pr.Value ?? string.Empty)); } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemWatsonEntry.cs b/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemWatsonEntry.cs index d585254b0bdad..3f2c264a8326a 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemWatsonEntry.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/FeedbackItemWatsonEntry.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.Diagnostics.Eventing.Reader; using System.Runtime.Serialization; @@ -126,7 +124,7 @@ internal class FeedbackItemWatsonEntry /// public FeedbackItemWatsonEntry(EventRecord eventLogRecord) { - EventTime = eventLogRecord.TimeCreated.Value.ToUniversalTime(); + EventTime = eventLogRecord.TimeCreated?.ToUniversalTime() ?? DateTime.MinValue; FaultBucket = EventLogCollector.GetEventRecordPropertyToString(eventLogRecord, FaultBucketIndex); HashedBucket = EventLogCollector.GetEventRecordPropertyToString(eventLogRecord, HashedBucketIndex); WatsonReportId = EventLogCollector.GetEventRecordPropertyToString(eventLogRecord, WatsonReportIdIndex); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilter.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilter.cs index 81b56fb265724..45114cd2a9230 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilter.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilter.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 IMessageFilter = Microsoft.VisualStudio.OLE.Interop.IMessageFilter; using INTERFACEINFO = Microsoft.VisualStudio.OLE.Interop.INTERFACEINFO; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilterSafeHandle.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilterSafeHandle.cs index f435175ad9d7f..42cb503aa8373 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilterSafeHandle.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Harness/MessageFilterSafeHandle.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.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Helper.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Helper.cs index d6b2d42439305..8d6473ffc2eef 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Helper.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Helper.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.Runtime.InteropServices; using System.Threading; @@ -19,7 +17,7 @@ public static class Helper /// public static readonly TimeSpan HangMitigatingTimeout = TimeSpan.FromMinutes(1); - private static IUIAutomation2 _automation; + private static IUIAutomation2? _automation; public static IUIAutomation2 Automation { @@ -43,7 +41,7 @@ public static IUIAutomation2 Automation /// the amount of time to wait between retries in milliseconds /// type of return value /// the return value of 'action' - public static T Retry(Func action, int delay) + public static T? Retry(Func action, int delay) => Retry(action, TimeSpan.FromMilliseconds(delay)); /// @@ -55,7 +53,7 @@ public static T Retry(Func action, int delay) /// the amount of time to wait between retries in milliseconds /// type of return value /// the return value of 'action' - public static T RetryIgnoringExceptions(Func action, int delay) + public static T? RetryIgnoringExceptions(Func action, int delay) => RetryIgnoringExceptions(action, TimeSpan.FromMilliseconds(delay)); /// @@ -67,7 +65,7 @@ public static T RetryIgnoringExceptions(Func action, int delay) /// the amount of time to wait between retries /// type of return value /// the return value of 'action' - public static T Retry(Func action, TimeSpan delay, int retryCount = -1) + public static T? Retry(Func action, TimeSpan delay, int retryCount = -1) { return RetryHelper(() => { @@ -94,7 +92,7 @@ public static T Retry(Func action, TimeSpan delay, int retryCount = -1) /// the amount of time to wait between retries /// type of return value /// the return value of - public static Task RetryAsync(Func> action, TimeSpan delay) + public static Task RetryAsync(Func> action, TimeSpan delay) { return RetryAsyncHelper(async () => { @@ -120,7 +118,7 @@ public static Task RetryAsync(Func> action, TimeSpan delay) /// the amount of time to wait between retries in milliseconds /// type of return value /// the return value of 'action' - public static T RetryIgnoringExceptions(Func action, TimeSpan delay, int retryCount = -1) + public static T? RetryIgnoringExceptions(Func action, TimeSpan delay, int retryCount = -1) { return RetryHelper(() => { @@ -152,7 +150,7 @@ private static T RetryHelper(Func action, TimeSpan delay, int retryCount) return retval; } - System.Threading.Thread.Sleep(delay); + Thread.Sleep(delay); } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AbstractCodeRefactorDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AbstractCodeRefactorDialog_InProc.cs index 8475dcb7a865b..bcff276937e08 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AbstractCodeRefactorDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AbstractCodeRefactorDialog_InProc.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.Linq; using System.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AddParameterDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AddParameterDialog_InProc.cs index c565093fcb4c0..4c54c66dc5316 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AddParameterDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/AddParameterDialog_InProc.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.Threading; using Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature; using Microsoft.VisualStudio.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/CSharpInteractiveWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/CSharpInteractiveWindow_InProc.cs index c06ea169cfedf..2a88e9a3f5760 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/CSharpInteractiveWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/CSharpInteractiveWindow_InProc.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 Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.LanguageServices.CSharp.Interactive; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ChangeSignatureDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ChangeSignatureDialog_InProc.cs index f4c697c14488d..a302d36f1ea61 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ChangeSignatureDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ChangeSignatureDialog_InProc.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.Linq; using System.Threading; using Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Debugger_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Debugger_InProc.cs index bb347a9efa8de..27c4eb4f87dca 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Debugger_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Debugger_InProc.cs @@ -2,12 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Globalization; using System.Runtime.InteropServices; -using EnvDTE; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc.cs index 3336d43532170..dbed8576e1e39 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc.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.Collections.Generic; using System.ComponentModel; @@ -34,6 +32,7 @@ using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; using UIAutomationClient; +using Xunit; using ThreadHelper = Microsoft.VisualStudio.Shell.ThreadHelper; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess @@ -77,10 +76,12 @@ private static IWpfTextViewHost GetActiveTextViewHost() { var (textViewHost, hr) = TryGetActiveTextViewHost(); Marshal.ThrowExceptionForHR(hr); + Contract.ThrowIfNull(textViewHost); + return textViewHost; } - private static (IWpfTextViewHost textViewHost, int hr) TryGetActiveTextViewHost() + private static (IWpfTextViewHost? textViewHost, int hr) TryGetActiveTextViewHost() { // The active text view might not have finished composing yet, waiting for the application to 'idle' // means that it is done pumping messages (including WM_PAINT) and the window should return the correct text view @@ -258,6 +259,8 @@ public string GetSelectedText() => ExecuteOnActiveView(view => { var subjectBuffer = view.GetBufferContainingCaret(); + Contract.ThrowIfNull(subjectBuffer); + var selectedSpan = view.Selection.SelectedSpans[0]; return subjectBuffer.CurrentSnapshot.GetText(selectedSpan); }); @@ -266,6 +269,8 @@ public void MoveCaret(int position) => ExecuteOnActiveView(view => { var subjectBuffer = view.GetBufferContainingCaret(); + Contract.ThrowIfNull(subjectBuffer); + var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position); view.Caret.MoveTo(point); @@ -291,7 +296,7 @@ public string[] GetHighlightTags() private string PrintSpan(SnapshotSpan span) => $"'{span.GetText().Replace("\\", "\\\\").Replace("\r", "\\r").Replace("\n", "\\n")}'[{span.Start.Position}-{span.Start.Position + span.Length}]"; - private string[] GetTags(Predicate filter = null) + private string[] GetTags(Predicate? filter = null) where TTag : ITag { bool Filter(TTag tag) @@ -412,7 +417,7 @@ private async Task GetLightbulbPreviewClassificationsAsync( string.Format("ISuggestionAction {0} not found. Buffer content type={1}", menuText, bufferType)); } - IWpfTextView preview = null; + IWpfTextView? preview = null; var pane = await set.GetPreviewAsync(CancellationToken.None).ConfigureAwait(true); if (pane is System.Windows.Controls.UserControl) { @@ -436,7 +441,7 @@ private async Task GetLightbulbPreviewClassificationsAsync( return Array.Empty(); } - private static IEnumerable FindDescendants(DependencyObject rootObject) where T : DependencyObject + private static IEnumerable FindDescendants(DependencyObject? rootObject) where T : DependencyObject { if (rootObject != null) { @@ -595,7 +600,7 @@ void ComponentRemoved(object sender, ComponentEventArgs e) } } - public void EditWinFormButtonProperty(string buttonName, string propertyName, string propertyValue, string propertyTypeName = null) + public void EditWinFormButtonProperty(string buttonName, string propertyName, string propertyValue, string? propertyTypeName = null) { using (var waitHandle = new ManualResetEvent(false)) { @@ -693,7 +698,7 @@ void ComponentChanged(object sender, ComponentChangedEventArgs e) } } - public string GetWinFormButtonPropertyValue(string buttonName, string propertyName) + public string? GetWinFormButtonPropertyValue(string buttonName, string propertyName) { var designerHost = (IDesignerHost)GetDTE().ActiveWindow.Object; var button = designerHost.Container.Components[buttonName]; @@ -746,7 +751,7 @@ public List GetF1Keywords() GetActiveVsTextView().GetBuffer(out var textLines); Marshal.ThrowExceptionForHR(textLines.GetLanguageServiceID(out var languageServiceGuid)); Marshal.ThrowExceptionForHR(Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.QueryService(languageServiceGuid, out var languageService)); - var languageContextProvider = languageService as IVsLanguageContextProvider; + var languageContextProvider = (IVsLanguageContextProvider)languageService; var monitorUserContext = GetGlobalService(); Marshal.ThrowExceptionForHR(monitorUserContext.CreateEmptyContext(out var emptyUserContext)); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc_NavigationBar.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc_NavigationBar.cs index 465437a745959..1fd2b6dc82951 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc_NavigationBar.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Editor_InProc_NavigationBar.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.Collections.Generic; using System.Linq; @@ -12,12 +10,13 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using Microsoft.CodeAnalysis.UnitTests; using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess.ReflectionExtensions; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; +using Roslyn.Utilities; using IObjectWithSite = Microsoft.VisualStudio.OLE.Interop.IObjectWithSite; using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; @@ -25,7 +24,7 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess { internal partial class Editor_InProc { - public string GetSelectedNavBarItem(int comboBoxIndex) + public string? GetSelectedNavBarItem(int comboBoxIndex) => ExecuteOnActiveView(v => GetNavigationBarComboBoxes(v)[comboBoxIndex].SelectedItem?.ToString()); public string[] GetNavBarItems(int comboBoxIndex) @@ -92,23 +91,28 @@ private List GetNavigationBarComboBoxes(IWpfTextView textView) return combos; } - private static UIElement GetNavbar(IWpfTextView textView) + private static UIElement? GetNavbar(IWpfTextView textView) { // Visual Studio 2019 var editorAdaptersFactoryService = GetComponentModelService(); var viewAdapter = editorAdaptersFactoryService.GetViewAdapter(textView); + Contract.ThrowIfNull(viewAdapter); // Make sure we have the top pane // // The docs are wrong. When a secondary view exists, it is the secondary view which is on top. The primary // view is only on top when there is no secondary view. var codeWindow = TryGetCodeWindow(viewAdapter); + Contract.ThrowIfNull(codeWindow); + if (ErrorHandler.Succeeded(codeWindow.GetSecondaryView(out var secondaryViewAdapter))) { viewAdapter = secondaryViewAdapter; } var textViewHost = editorAdaptersFactoryService.GetWpfTextViewHost(viewAdapter); + Contract.ThrowIfNull(textViewHost); + var dropDownMargin = textViewHost.GetTextViewMargin("DropDownMargin"); if (dropDownMargin != null) { @@ -132,7 +136,7 @@ private static UIElement GetNavbar(IWpfTextView textView) return vsDropDownBarAdapterMargin; } - private static IVsCodeWindow TryGetCodeWindow(IVsTextView textView) + private static IVsCodeWindow? TryGetCodeWindow(IVsTextView textView) { if (textView == null) { @@ -151,7 +155,7 @@ private static IVsCodeWindow TryGetCodeWindow(IVsTextView textView) return null; } - IOleServiceProvider oleServiceProvider = null; + IOleServiceProvider? oleServiceProvider = null; try { oleServiceProvider = Marshal.GetObjectForIUnknown(ppvSite) as IOleServiceProvider; @@ -173,10 +177,10 @@ private static IVsCodeWindow TryGetCodeWindow(IVsTextView textView) return null; } - IVsWindowFrame frame = null; + IVsWindowFrame? frame = null; try { - frame = Marshal.GetObjectForIUnknown(ppvObject) as IVsWindowFrame; + frame = (IVsWindowFrame)Marshal.GetObjectForIUnknown(ppvObject); } finally { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ErrorList_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ErrorList_InProc.cs index f268f5db7cfdd..babab50dd447b 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ErrorList_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ErrorList_InProc.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.Collections.Generic; using System.Diagnostics; @@ -150,7 +148,7 @@ public static string GetDescription(this IVsTaskItem item) public static string GetProject(this IVsTaskItem item) { - var errorItem = item as IVsErrorItem; + var errorItem = (IVsErrorItem)item; ErrorHandler.ThrowOnFailure(errorItem.GetHierarchy(out var hierarchy)); ErrorHandler.ThrowOnFailure(hierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Name, out var name)); return (string)name; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ExtractInterfaceDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ExtractInterfaceDialog_InProc.cs index 9d902d17914ab..a9ac3c062c215 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ExtractInterfaceDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ExtractInterfaceDialog_InProc.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.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs index f6c9cea989552..21a550b58ec2f 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/FindReferencesWindow_InProc.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.Collections.Generic; using System.Linq; using EnvDTE80; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/GenerateTypeDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/GenerateTypeDialog_InProc.cs index b512a7dbd362e..932e600e5d2a8 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/GenerateTypeDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/GenerateTypeDialog_InProc.cs @@ -2,15 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System; using System.Linq; using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls.Primitives; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.LanguageServices.Implementation.GenerateType; using Roslyn.Utilities; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ImmediateWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ImmediateWindow_InProc.cs index 7925cd231df19..387653fe82a04 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ImmediateWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ImmediateWindow_InProc.cs @@ -2,9 +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 Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InProcComponent.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InProcComponent.cs index 49a3214b73548..f8f625d699d7c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InProcComponent.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InProcComponent.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.Threading; using System.Windows; @@ -29,7 +27,7 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess /// internal abstract class InProcComponent : MarshalByRefObject { - private static JoinableTaskFactory _joinableTaskFactory; + private static JoinableTaskFactory? _joinableTaskFactory; protected InProcComponent() { } @@ -114,6 +112,6 @@ protected static void WaitForSystemIdle() #pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs // Ensure InProcComponents live forever - public override object InitializeLifetimeService() => null; + public override object? InitializeLifetimeService() => null; } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs index 56119fd4a31d8..4fda3ae839c3b 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.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.Threading; using System.Threading.Tasks; @@ -34,7 +32,7 @@ internal abstract class InteractiveWindow_InProc : TextViewWindow_InProc private readonly string _viewCommand; private readonly Guid _windowId; - private IInteractiveWindow _interactiveWindow; + private IInteractiveWindow? _interactiveWindow; protected InteractiveWindow_InProc(string viewCommand, Guid windowId) { @@ -56,16 +54,31 @@ public void Initialize() protected abstract IInteractiveWindow AcquireInteractiveWindow(); public bool IsInitializing - => InvokeOnUIThread(cancellationToken => _interactiveWindow.IsInitializing); + { + get + { + Contract.ThrowIfNull(_interactiveWindow); + return InvokeOnUIThread(cancellationToken => _interactiveWindow.IsInitializing); + } + } public string GetReplText() - => InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView.TextBuffer.CurrentSnapshot.GetText()); + { + Contract.ThrowIfNull(_interactiveWindow); + return InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView.TextBuffer.CurrentSnapshot.GetText()); + } protected override bool HasActiveTextView() - => InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView) is object; + { + Contract.ThrowIfNull(_interactiveWindow); + return InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView) is object; + } protected override IWpfTextView GetActiveTextView() - => InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView); + { + Contract.ThrowIfNull(_interactiveWindow); + return InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView); + } /// /// Gets the contents of the REPL window without the prompt text. @@ -170,6 +183,8 @@ public void Reset(bool waitForPrompt = true) public void SubmitText(string text) { + Contract.ThrowIfNull(_interactiveWindow); + using var cts = new CancellationTokenSource(Helper.HangMitigatingTimeout); _interactiveWindow.SubmitAsync(new[] { text }).WithCancellation(cts.Token).Wait(); } @@ -206,7 +221,10 @@ public void ClearScreen() => ExecuteCommand(WellKnownCommandNames.InteractiveConsole_ClearScreen); public void InsertCode(string text) - => InvokeOnUIThread(cancellationToken => _interactiveWindow.InsertCode(text)); + { + Contract.ThrowIfNull(_interactiveWindow); + InvokeOnUIThread(cancellationToken => _interactiveWindow.InsertCode(text)); + } public void WaitForLastReplOutput(string outputText) => WaitForPredicate(GetLastReplOutput, outputText, s_contains, "contain"); @@ -246,6 +264,7 @@ private void WaitForPredicate(Func getValue, string expectedValue, Func< protected override ITextBuffer GetBufferContainingCaret(IWpfTextView view) { + Contract.ThrowIfNull(_interactiveWindow); return InvokeOnUIThread(cancellationToken => _interactiveWindow.TextView.TextBuffer); } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/LocalsWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/LocalsWindow_InProc.cs index 78d1d1497e02f..142c00d85f151 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/LocalsWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/LocalsWindow_InProc.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.Collections.Generic; using System.Linq; @@ -36,7 +34,7 @@ public Common.Expression GetEntry(params string[] entryNames) } var expressions = dte.Debugger.CurrentStackFrame.Locals; - EnvDTE.Expression entry = null; + EnvDTE.Expression? entry = null; var i = 0; while (i < entryNames.Length && TryGetEntryInternal(entryNames[i], expressions, out entry)) diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/MoveToNamespaceDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/MoveToNamespaceDialog_InProc.cs index a669908e97ef0..c57d309bf91fc 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/MoveToNamespaceDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/MoveToNamespaceDialog_InProc.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.Threading; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.LanguageServices.Implementation.MoveToNamespace; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ObjectBrowserWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ObjectBrowserWindow_InProc.cs index 247e18e795940..47d08427912f4 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ObjectBrowserWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ObjectBrowserWindow_InProc.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 Microsoft.VisualStudio.Shell.Interop; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/PickMembersDialog_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/PickMembersDialog_InProc.cs index 38f57325333cb..a1b19c0d4997e 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/PickMembersDialog_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/PickMembersDialog_InProc.cs @@ -2,15 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls.Primitives; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers; using Roslyn.Utilities; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/QuickInfoToStringConverter.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/QuickInfoToStringConverter.cs index e5557e40cd012..4741033404013 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/QuickInfoToStringConverter.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/QuickInfoToStringConverter.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.Collections.Generic; using System.Linq; @@ -22,7 +20,7 @@ public static string GetStringFromBulkContent(IEnumerable content) return string.Join(Environment.NewLine, content.Select(GetStringFromItem)); } - private static string GetStringFromItem(object item) + private static string? GetStringFromItem(object item) { switch (item) { @@ -91,7 +89,7 @@ private static void BuildStringFromInlineCollection(InlineCollection inlines, St } } - private static string GetStringFromInline(Inline currentInline) + private static string? GetStringFromInline(Inline currentInline) { if (currentInline is LineBreak) { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Shell_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Shell_InProc.cs index eb04a7c65654a..ccdf86d5dec11 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Shell_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/Shell_InProc.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 Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs index d36c4ea0a618a..4a4c5637182da 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.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.Collections.Generic; using System.IO; @@ -32,8 +30,8 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess internal class SolutionExplorer_InProc : InProcComponent { private readonly SendKeys_InProc _sendKeys; - private Solution2 _solution; - private string _fileName; + private Solution2? _solution; + private string? _fileName; private static readonly Lazy> _csharpProjectTemplates = new Lazy>(InitializeCSharpProjectTemplates); private static readonly Lazy> _visualBasicProjectTemplates = new Lazy>(InitializeVisualBasicProjectTemplates); @@ -116,6 +114,9 @@ public string SolutionFileFullPath { get { + Contract.ThrowIfNull(_solution); + Contract.ThrowIfNull(_fileName); + var solutionFullName = _solution.FullName; return string.IsNullOrEmpty(solutionFullName) @@ -245,6 +246,7 @@ private void CreateProject(XElement projectElement) var projectPath = Path.Combine(DirectoryName, projectName); var projectTemplatePath = GetProjectTemplatePath(projectTemplate, ConvertLanguageName(languageName)); + Contract.ThrowIfNull(_solution); _solution.AddFromTemplate(projectTemplatePath, projectPath, projectName, Exclusive: false); foreach (var documentElement in projectElement.Elements("Document")) { @@ -350,6 +352,7 @@ public void AddProject(string projectName, string projectTemplate, string langua var projectTemplatePath = GetProjectTemplatePath(projectTemplate, ConvertLanguageName(languageName)); + Contract.ThrowIfNull(_solution); _solution.AddFromTemplate(projectTemplatePath, projectPath, projectName, Exclusive: false); } @@ -361,12 +364,15 @@ public void AddCustomProject(string projectName, string projectFileExtension, st var projectFilePath = Path.Combine(projectPath, projectName + projectFileExtension); File.WriteAllText(projectFilePath, projectFileContent); + Contract.ThrowIfNull(_solution); _solution.AddFromFile(projectFilePath); } // TODO: Adjust language name based on whether we are using a web template private string GetProjectTemplatePath(string projectTemplate, string languageName) { + Contract.ThrowIfNull(_solution); + if (languageName.Equals("csharp", StringComparison.OrdinalIgnoreCase) && _csharpProjectTemplates.Value.TryGetValue(projectTemplate, out var csharpProjectTemplate)) { @@ -511,7 +517,7 @@ public SolutionEvents(IVsSolution solution) ErrorHandler.ThrowOnFailure(solution.AdviseSolutionEvents(this, out _cookie)); } - public event EventHandler AfterCloseSolution; + public event EventHandler? AfterCloseSolution; public void Dispose() { @@ -574,9 +580,12 @@ public int OnAfterCloseSolution(object pUnkReserved) } private EnvDTE.Project GetProject(string nameOrFileName) - => _solution.Projects.OfType().First(p - => string.Compare(p.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) == 0 - || string.Compare(p.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase) == 0); + { + Contract.ThrowIfNull(_solution); + return _solution.Projects.OfType().First( + p => string.Compare(p.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) == 0 + || string.Compare(p.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase) == 0); + } /// /// Update the given file if it already exists in the project, otherwise add a new file to the project. @@ -585,7 +594,7 @@ private EnvDTE.Project GetProject(string nameOrFileName) /// The name of the file to update or add. /// The contents of the file to overwrite if the file already exists or set if the file it created. Empty string is used if null is passed. /// Whether to open the file after it has been updated/created. - public void UpdateOrAddFile(string projectName, string fileName, string contents = null, bool open = false) + public void UpdateOrAddFile(string projectName, string fileName, string? contents = null, bool open = false) { var project = GetProject(projectName); if (project.ProjectItems.Cast().Any(x => x.Name == fileName)) @@ -605,7 +614,7 @@ public void UpdateOrAddFile(string projectName, string fileName, string contents /// The name of the file to update or add. /// The contents of the file to overwrite. Empty string is used if null is passed. /// Whether to open the file after it has been updated. - public void UpdateFile(string projectName, string fileName, string contents = null, bool open = false) + public void UpdateFile(string projectName, string fileName, string? contents = null, bool open = false) { void SetText(string text) { @@ -646,7 +655,7 @@ void SetText(string text) /// The name of the file to add. /// The contents of the file to overwrite. An empty file is create if null is passed. /// Whether to open the file after it has been updated. - public void AddFile(string projectName, string fileName, string contents = null, bool open = false) + public void AddFile(string projectName, string fileName, string? contents = null, bool open = false) { var project = GetProject(projectName); var projectDirectory = Path.GetDirectoryName(project.FullName); @@ -771,13 +780,13 @@ internal sealed class UpdateSolutionEvents : IVsUpdateSolutionEvents, IVsUpdateS internal delegate void UpdateProjectConfigDoneEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig, int success); internal delegate void UpdateProjectConfigBeginEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig); - public event UpdateSolutionDoneEvent OnUpdateSolutionDone; - public event UpdateSolutionBeginEvent OnUpdateSolutionBegin; - public event UpdateSolutionStartUpdateEvent OnUpdateSolutionStartUpdate; - public event Action OnActiveProjectConfigurationChange; - public event Action OnUpdateSolutionCancel; - public event UpdateProjectConfigDoneEvent OnUpdateProjectConfigDone; - public event UpdateProjectConfigBeginEvent OnUpdateProjectConfigBegin; + public event UpdateSolutionDoneEvent? OnUpdateSolutionDone; + public event UpdateSolutionBeginEvent? OnUpdateSolutionBegin; + public event UpdateSolutionStartUpdateEvent? OnUpdateSolutionStartUpdate; + public event Action? OnActiveProjectConfigurationChange; + public event Action? OnUpdateSolutionCancel; + public event UpdateProjectConfigDoneEvent? OnUpdateProjectConfigDone; + public event UpdateProjectConfigBeginEvent? OnUpdateProjectConfigBegin; internal UpdateSolutionEvents(IVsSolutionBuildManager2 solutionBuildManager) { @@ -970,6 +979,7 @@ private EnvDTE.Document GetOpenDocument(string projectName, string relativeFileP private EnvDTE.ProjectItem GetProjectItem(string projectName, string relativeFilePath) { + Contract.ThrowIfNull(_solution); var projects = _solution.Projects.Cast(); var project = projects.FirstOrDefault(x => x.Name == projectName); @@ -1011,6 +1021,7 @@ private static void SaveFileWithExtraValidation(EnvDTE.Document document) private string GetAbsolutePathForProjectRelativeFilePath(string projectName, string relativeFilePath) { + Contract.ThrowIfNull(_solution); var project = _solution.Projects.Cast().First(x => x.Name == projectName); var projectPath = Path.GetDirectoryName(project.FullName); return Path.Combine(projectPath, relativeFilePath); @@ -1018,6 +1029,7 @@ private string GetAbsolutePathForProjectRelativeFilePath(string projectName, str public void ReloadProject(string projectRelativePath) { + Contract.ThrowIfNull(_solution); var solutionPath = Path.GetDirectoryName(_solution.FullName); var projectPath = Path.Combine(solutionPath, projectRelativePath); _solution.AddFromFile(projectPath); @@ -1044,8 +1056,9 @@ public void ShowOutputWindow() public void UnloadProject(string projectName) { + Contract.ThrowIfNull(_solution); var projects = _solution.Projects; - EnvDTE.Project project = null; + EnvDTE.Project? project = null; for (var i = 1; i <= projects.Count; i++) { project = projects.Item(i); @@ -1064,6 +1077,8 @@ public void SelectItem(string itemName) var solutionExplorer = dte.ToolWindows.SolutionExplorer; var item = FindFirstItemRecursively(solutionExplorer.UIHierarchyItems, itemName); + Contract.ThrowIfNull(item); + item.Select(EnvDTE.vsUISelectionType.vsUISelectionTypeSelect); solutionExplorer.Parent.Activate(); } @@ -1074,6 +1089,8 @@ public void SelectItemAtPath(params string[] path) var solutionExplorer = dte.ToolWindows.SolutionExplorer; var item = FindItemAtPath(solutionExplorer.UIHierarchyItems, path); + Contract.ThrowIfNull(item); + item.Select(EnvDTE.vsUISelectionType.vsUISelectionTypeSelect); solutionExplorer.Parent.Activate(); } @@ -1084,6 +1101,7 @@ public string[] GetChildrenOfItem(string itemName) var solutionExplorer = dte.ToolWindows.SolutionExplorer; var item = FindFirstItemRecursively(solutionExplorer.UIHierarchyItems, itemName); + Contract.ThrowIfNull(item); return item.UIHierarchyItems .Cast() @@ -1097,6 +1115,7 @@ public string[] GetChildrenOfItemAtPath(params string[] path) var solutionExplorer = dte.ToolWindows.SolutionExplorer; var item = FindItemAtPath(solutionExplorer.UIHierarchyItems, path); + Contract.ThrowIfNull(item); return item.UIHierarchyItems .Cast() @@ -1104,11 +1123,11 @@ public string[] GetChildrenOfItemAtPath(params string[] path) .ToArray(); } - private static EnvDTE.UIHierarchyItem FindItemAtPath( + private static EnvDTE.UIHierarchyItem? FindItemAtPath( EnvDTE.UIHierarchyItems currentItems, string[] path) { - EnvDTE.UIHierarchyItem item = null; + EnvDTE.UIHierarchyItem? item = null; foreach (var name in path) { item = currentItems.Cast().FirstOrDefault(i => i.Name == name); @@ -1124,7 +1143,7 @@ private static EnvDTE.UIHierarchyItem FindItemAtPath( return item; } - private static EnvDTE.UIHierarchyItem FindFirstItemRecursively( + private static EnvDTE.UIHierarchyItem? FindFirstItemRecursively( EnvDTE.UIHierarchyItems currentItems, string itemName) { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs index ab351adf9ab43..ab3c5d248dcbe 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.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 Microsoft.VisualStudio.Shell.Interop; using vsStartUp = EnvDTE.vsStartUp; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs index ee710be09fb48..b4106b5aaede0 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/TextViewWindow_InProc.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.Collections.Generic; using System.Linq; @@ -76,7 +74,7 @@ public void ShowLightBulb() const VSConstants.VSStd2KCmdID ECMD_SMARTTASKS = (VSConstants.VSStd2KCmdID)147; var cmdID = ECMD_SMARTTASKS; - object obj = null; + object? obj = null; shell.PostExecCommand(cmdGroup, (uint)cmdID, (uint)cmdExecOpt, ref obj); }); } @@ -116,7 +114,7 @@ public bool IsCompletionActive() public string[] GetCurrentClassifications() => InvokeOnUIThread(cancellationToken => { - IClassifier classifier = null; + IClassifier? classifier = null; try { var textView = GetActiveTextView(); @@ -264,7 +262,8 @@ public void InvokeQuickInfo() await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var broker = GetComponentModelService(); - await broker.TriggerQuickInfoAsync(GetActiveTextView()); + var session = await broker.TriggerQuickInfoAsync(GetActiveTextView()); + Contract.ThrowIfNull(session); }); } @@ -279,6 +278,9 @@ public string GetQuickInfo() var session = broker.GetSession(view); + // GetSession will not return null if preceded by a call to InvokeQuickInfo + Contract.ThrowIfNull(session); + using var cts = new CancellationTokenSource(Helper.HangMitigatingTimeout); while (session.State != QuickInfoSessionState.Visible) { @@ -420,12 +422,14 @@ private Func> GetLightBulbApplicationAction(string acti } var actionSetsForAction = await action.GetActionSetsAsync(CancellationToken.None); - action = await GetFixAllSuggestedActionAsync(actionSetsForAction, fixAllScope.Value); - if (action == null) + var fixAllAction = await GetFixAllSuggestedActionAsync(actionSetsForAction, fixAllScope.Value); + if (fixAllAction == null) { throw new InvalidOperationException($"Unable to find FixAll in {fixAllScope.ToString()} code fix for suggested action '{action.DisplayText}'."); } + action = fixAllAction; + if (willBlockUntilComplete && action is FixAllSuggestedAction fixAllSuggestedAction && fixAllSuggestedAction.CodeAction is FixSomeCodeAction fixSomeCodeAction) @@ -477,7 +481,7 @@ private async Task> SelectActionsAsync(IEnumerable return actions; } - private static async Task GetFixAllSuggestedActionAsync(IEnumerable actionSets, FixAllScope fixAllScope) + private static async Task GetFixAllSuggestedActionAsync(IEnumerable actionSets, FixAllScope fixAllScope) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -497,10 +501,10 @@ private static async Task GetFixAllSuggestedActionAsync(I if (action.HasActionSets) { var nestedActionSets = await action.GetActionSetsAsync(CancellationToken.None); - fixAllSuggestedAction = await GetFixAllSuggestedActionAsync(nestedActionSets, fixAllScope); - if (fixAllSuggestedAction != null) + var fixAllCodeAction = await GetFixAllSuggestedActionAsync(nestedActionSets, fixAllScope); + if (fixAllCodeAction != null) { - return fixAllSuggestedAction; + return fixAllCodeAction; } } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs index 4049fe63c9973..bba4ed0287583 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.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.ComponentModel; using System.Linq; @@ -101,7 +99,7 @@ private static object GetValue(object value, IOption option) private IOption GetOption(string optionName, string feature) { - var optionService = _visualStudioWorkspace.Services.GetService(); + var optionService = _visualStudioWorkspace.Services.GetRequiredService(); var option = optionService.GetRegisteredOptions().FirstOrDefault(o => o.Feature == feature && o.Name == optionName); if (option == null) { @@ -111,7 +109,7 @@ private IOption GetOption(string optionName, string feature) return option; } - private void SetOption(OptionKey optionKey, object result) + private void SetOption(OptionKey optionKey, object? result) => _visualStudioWorkspace.SetOptions(_visualStudioWorkspace.Options.WithChangedOption(optionKey, result)); private static TestingOnly_WaitingService GetWaitingService() @@ -215,7 +213,7 @@ public void CleanUpWaitingService() GetWaitingService().EnableActiveTokenTracking(true); }); - public void SetFeatureOption(string feature, string optionName, string language, string valueString) + public void SetFeatureOption(string feature, string optionName, string language, string? valueString) => InvokeOnUIThread(cancellationToken => { var option = GetOption(optionName, feature); @@ -228,7 +226,7 @@ public void SetFeatureOption(string feature, string optionName, string language, SetOption(optionKey, value); }); - public string GetWorkingFolder() + public string? GetWorkingFolder() { var service = _visualStudioWorkspace.Services.GetRequiredService(); return service.TryGetStorageLocation(_visualStudioWorkspace.CurrentSolution); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc.cs index af2318476e154..dec131e3bc3fb 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc.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.Collections.Generic; using System.Diagnostics; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc_Telemetry.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc_Telemetry.cs index b3b1d08aec982..12ea62632b038 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc_Telemetry.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudio_InProc_Telemetry.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; +using System.Threading; using Microsoft.VisualStudio.Telemetry; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess @@ -35,38 +33,34 @@ public void DisableTestTelemetryChannel() }); } - public void WaitForTelemetryEvents(string[] names) - => LoggerTestChannel.Instance.WaitForEvents(names); + public bool TryWaitForTelemetryEvents(string[] names) + => LoggerTestChannel.Instance.TryWaitForEvents(names); private sealed class LoggerTestChannel : ITelemetryTestChannel { public static readonly LoggerTestChannel Instance = new LoggerTestChannel(); - private ConcurrentBag eventsQueue = - new ConcurrentBag(); + private BlockingCollection eventsQueue = + new BlockingCollection(); /// /// Waits for one or more events with the specified names /// /// - public void WaitForEvents(string[] events) + public bool TryWaitForEvents(string[] events) { + if (!TelemetryService.DefaultSession.IsOptedIn) + return false; + + using var cancellationTokenSource = new CancellationTokenSource(Helper.HangMitigatingTimeout); var set = new HashSet(events); - while (true) + while (set.Count > 0) { - if (eventsQueue.TryTake(out var result)) - { - set.Remove(result.Name); - if (set.Count == 0) - { - return; - } - } - else - { - System.Threading.Thread.Sleep(1000); - } + var result = eventsQueue.Take(cancellationTokenSource.Token); + set.Remove(result.Name); } + + return true; } /// @@ -74,7 +68,8 @@ public void WaitForEvents(string[] events) /// public void Clear() { - this.eventsQueue = new ConcurrentBag(); + this.eventsQueue.CompleteAdding(); + this.eventsQueue = new BlockingCollection(); } /// diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/AbstractSendKeys.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/AbstractSendKeys.cs index 877180e3b4b63..af7729e3ce7b5 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/AbstractSendKeys.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/AbstractSendKeys.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.Collections.Generic; using System.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/ButtonBaseExtensions.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/ButtonBaseExtensions.cs index 09e79345f4abb..45c4b8445b80e 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/ButtonBaseExtensions.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/ButtonBaseExtensions.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.Reflection; using System.Threading.Tasks; using System.Windows; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/ComboBoxExtensions.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/ComboBoxExtensions.cs index 8564dab5870dc..ea08bf45a58ac 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/ComboBoxExtensions.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/ComboBoxExtensions.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.Linq; using System.Threading.Tasks; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/KeyPress.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/KeyPress.cs index 65ceca43a5822..61d5d3f04abf5 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/KeyPress.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/KeyPress.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Input { - public struct KeyPress + public readonly struct KeyPress { public readonly VirtualKey VirtualKey; public readonly ShiftState ShiftState; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys.cs index 01bd6b7298458..43b80cc5897e4 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys.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.Threading; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Input diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys_InProc.cs index 510c772c0145f..4c9ecc887d8c1 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/SendKeys_InProc.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.Threading; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/ShiftState.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/ShiftState.cs index e71d9582616ab..0d3a2f848d4e6 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/ShiftState.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/ShiftState.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; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Input diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Input/VirtualKey.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Input/VirtualKey.cs index 6a1e994b7d4f2..1485f5648e571 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Input/VirtualKey.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Input/VirtualKey.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 - namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Input { public enum VirtualKey : byte diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/IntegrationHelper.cs b/src/VisualStudio/IntegrationTest/TestUtilities/IntegrationHelper.cs index 25958a4bdb82b..ccaf219f7993c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/IntegrationHelper.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/IntegrationHelper.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.Collections.Generic; using System.Diagnostics; @@ -346,7 +344,7 @@ private static void AppendPrintableChar(char ch, StringBuilder builder) } } - private static string GetPrintableCharText(char ch) + private static string? GetPrintableCharText(char ch) { switch (ch) { @@ -404,10 +402,10 @@ public static bool TryDeleteDirectoryRecursively(string path) } /// Locates the DTE object for the specified process. - public static DTE TryLocateDteForProcess(Process process) + public static DTE? TryLocateDteForProcess(Process process) { - object dte = null; - var monikers = new IMoniker[1]; + object? dte = null; + var monikers = new IMoniker?[1]; NativeMethods.GetRunningObjectTable(0, out var runningObjectTable); runningObjectTable.EnumRunning(out var enumMoniker); @@ -429,6 +427,8 @@ public static DTE TryLocateDteForProcess(Process process) } var moniker = monikers[0]; + Contract.ThrowIfNull(moniker); + moniker.GetDisplayName(bindContext, null, out var fullDisplayName); // FullDisplayName will look something like: : @@ -450,6 +450,7 @@ public static DTE TryLocateDteForProcess(Process process) } public static async Task WaitForResultAsync(Func action, T expectedResult) + where T : notnull { while (!action().Equals(expectedResult)) { @@ -457,7 +458,7 @@ public static async Task WaitForResultAsync(Func action, T expectedResult) } } - public static async Task WaitForNotNullAsync(Func action) where T : class + public static async Task WaitForNotNullAsync(Func action) where T : class { var result = action(); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Interop/NativeMethods.cs b/src/VisualStudio/IntegrationTest/TestUtilities/Interop/NativeMethods.cs index 110aa44fe92b4..b663610217b70 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/Interop/NativeMethods.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/Interop/NativeMethods.cs @@ -2,14 +2,10 @@ // 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.Drawing; using System.Runtime.InteropServices; using System.Text; using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Setup.Configuration; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.Interop { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/LightBulbHelper.cs b/src/VisualStudio/IntegrationTest/TestUtilities/LightBulbHelper.cs index 5ca20ced5d29e..dfae5b60282e3 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/LightBulbHelper.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/LightBulbHelper.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.Threading.Tasks; using Microsoft.VisualStudio.Language.Intellisense; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/AddParameterDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/AddParameterDialog_OutOfProc.cs index aa787b8fbaadd..77df3e912bf85 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/AddParameterDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/AddParameterDialog_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/CSharpInteractiveWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/CSharpInteractiveWindow_OutOfProc.cs index 422b932d841ab..18b99a9999ba1 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/CSharpInteractiveWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/CSharpInteractiveWindow_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ChangeSignatureDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ChangeSignatureDialog_OutOfProc.cs index 339605160680b..04e6dba1eb538 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ChangeSignatureDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ChangeSignatureDialog_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Debugger_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Debugger_OutOfProc.cs index 98fbadf6ab6f9..130035d53be94 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Debugger_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Debugger_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; using Xunit; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Dialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Dialog_OutOfProc.cs index 4cef122feb505..13fbdb8565ea1 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Dialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Dialog_OutOfProc.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.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.Verifier.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.Verifier.cs index 5919710755d7e..bfbf6cc7f1bf4 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.Verifier.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.Verifier.cs @@ -2,12 +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. -#nullable disable - using System; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess @@ -177,6 +176,8 @@ public void CurrentParameter( string documentation) { var currentParameter = _textViewWindow.GetCurrentSignature().CurrentParameter; + Contract.ThrowIfNull(currentParameter); + Assert.Equal(name, currentParameter.Name); Assert.Equal(documentation, currentParameter.Documentation); } @@ -185,6 +186,8 @@ public void Parameters( params (string name, string documentation)[] parameters) { var currentParameters = _textViewWindow.GetCurrentSignature().Parameters; + Contract.ThrowIfNull(currentParameters); + for (var i = 0; i < parameters.Length; i++) { var (expectedName, expectedDocumentation) = parameters[i]; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.cs index a163505701597..e8feb704368de 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Editor_OutOfProc.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.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -165,13 +163,13 @@ public void AddWinFormButton(string buttonName) public void DeleteWinFormButton(string buttonName) => _editorInProc.DeleteWinFormButton(buttonName); - public void EditWinFormButtonProperty(string buttonName, string propertyName, string propertyValue, string propertyTypeName = null) + public void EditWinFormButtonProperty(string buttonName, string propertyName, string propertyValue, string? propertyTypeName = null) => _editorInProc.EditWinFormButtonProperty(buttonName, propertyName, propertyValue, propertyTypeName); public void EditWinFormButtonEvent(string buttonName, string eventName, string eventHandlerName) => _editorInProc.EditWinFormButtonEvent(buttonName, eventName, eventHandlerName); - public string GetWinFormButtonPropertyValue(string buttonName, string propertyName) + public string? GetWinFormButtonPropertyValue(string buttonName, string propertyName) => _editorInProc.GetWinFormButtonPropertyValue(buttonName, propertyName); /// @@ -300,19 +298,19 @@ public string[] GetMemberNavBarItems() return _editorInProc.GetNavBarItems(2); } - public string GetProjectNavBarSelection() + public string? GetProjectNavBarSelection() { _instance.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.NavigationBar); return _editorInProc.GetSelectedNavBarItem(0); } - public string GetTypeNavBarSelection() + public string? GetTypeNavBarSelection() { _instance.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.NavigationBar); return _editorInProc.GetSelectedNavBarItem(1); } - public string GetMemberNavBarSelection() + public string? GetMemberNavBarSelection() { _instance.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.NavigationBar); return _editorInProc.GetSelectedNavBarItem(2); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/EncapsulateField_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/EncapsulateField_OutOfProc.cs index 01d5238eeb010..5846b853487e3 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/EncapsulateField_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/EncapsulateField_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.Input; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.Verifier.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.Verifier.cs index 3f5e93c3b9dba..fc86fc60d1053 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.Verifier.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.Verifier.cs @@ -2,11 +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 Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; -using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; using Xunit; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.cs index e44d360bae255..333217d485792 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ErrorList_OutOfProc.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 Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ExtractInterfaceDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ExtractInterfaceDialog_OutOfProc.cs index fa8f4311bfcfa..e710ad93c40dd 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ExtractInterfaceDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ExtractInterfaceDialog_OutOfProc.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 Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/FindReferencesWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/FindReferencesWindow_OutOfProc.cs index 09805b607d3a3..7e16a73f79d6d 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/FindReferencesWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/FindReferencesWindow_OutOfProc.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 Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/GenerateTypeDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/GenerateTypeDialog_OutOfProc.cs index b69fc4cba50ba..d467f7ce2c1e1 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/GenerateTypeDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/GenerateTypeDialog_OutOfProc.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 Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ImmediateWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ImmediateWindow_OutOfProc.cs index aecd1fc900dfb..b52b638f49e86 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ImmediateWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ImmediateWindow_OutOfProc.cs @@ -2,9 +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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InlineRenameDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InlineRenameDialog_OutOfProc.cs index 1f6984d09d4f8..b46f969088198 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InlineRenameDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InlineRenameDialog_OutOfProc.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 Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.HighlightTags; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.Verifier.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.Verifier.cs index 87879abdb4a29..3b77683f1d469 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.Verifier.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.Verifier.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 Xunit; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.cs index 5899d3ed44531..34a4af4eb456c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/InteractiveWindow_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.Verifier.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.Verifier.cs index 7b735dcf6a6a2..1c934f87f94f8 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.Verifier.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.Verifier.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 Xunit; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.cs index be2236bb0eb35..9e1c13ae29fb9 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/LocalsWindow_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/MoveToNamespaceDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/MoveToNamespaceDialog_OutOfProc.cs index 83fd98b2898bb..0e2efbf6e17cb 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/MoveToNamespaceDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/MoveToNamespaceDialog_OutOfProc.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.Threading; using Microsoft.CodeAnalysis.Shared.TestHooks; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ObjectBrowserWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ObjectBrowserWindow_OutOfProc.cs index db111dc8e3603..1466a81d50b40 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ObjectBrowserWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/ObjectBrowserWindow_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/OutOfProcComponent.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/OutOfProcComponent.cs index 4a4ab31b50d7d..ed20466b6168c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/OutOfProcComponent.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/OutOfProcComponent.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 Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PickMembersDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PickMembersDialog_OutOfProc.cs index dbc74c57b1acc..c7186d9f24205 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PickMembersDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PickMembersDialog_OutOfProc.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 Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PreviewChangesDialog_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PreviewChangesDialog_OutOfProc.cs index 518b1f290b1cf..c000a992a2552 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PreviewChangesDialog_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/PreviewChangesDialog_OutOfProc.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.Threading; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Shell_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Shell_OutOfProc.cs index 2db48f02f8435..cfede2a63d54f 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Shell_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/Shell_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.Verifier.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.Verifier.cs index e32938046490f..f5dba7ec6d222 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.Verifier.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.Verifier.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 Xunit; using ProjectUtils = Microsoft.VisualStudio.IntegrationTest.Utilities.Common.ProjectUtils; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs index 0503459873bf9..d1b4510f8708c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs @@ -2,12 +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. -#nullable disable - using System; using System.Xml.Linq; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; +using Roslyn.Utilities; using ProjectUtils = Microsoft.VisualStudio.IntegrationTest.Utilities.Common.ProjectUtils; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess @@ -118,7 +117,7 @@ public void RemovePackageReference(ProjectUtils.Project project, ProjectUtils.Pa public void CleanUpOpenSolution() => _inProc.CleanUpOpenSolution(); - public void AddFile(ProjectUtils.Project project, string fileName, string contents = null, bool open = false) + public void AddFile(ProjectUtils.Project project, string fileName, string? contents = null, bool open = false) => _inProc.AddFile(project.Name, fileName, contents, open); public void SetFileContents(ProjectUtils.Project project, string fileName, string contents) @@ -167,7 +166,10 @@ public void SaveFile(ProjectUtils.Project project, string fileName) => _inProc.SaveFile(project.Name, fileName); public void ReloadProject(ProjectUtils.Project project) - => _inProc.ReloadProject(project.RelativePath); + { + Contract.ThrowIfNull(project.RelativePath); + _inProc.ReloadProject(project.RelativePath); + } public void RestoreNuGetPackages(ProjectUtils.Project project) => _inProc.RestoreNuGetPackages(project.Name); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/StartPage_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/StartPage_OutOfProc.cs index b43766368a19a..9191027ffdfa7 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/StartPage_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/StartPage_OutOfProc.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 Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.Verifier.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.Verifier.cs index fec9f74af5342..a3c6a2cf7b4f2 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.Verifier.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.Verifier.cs @@ -2,14 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Roslyn.Utilities; using Xunit; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess @@ -58,7 +57,7 @@ public void CodeAction( /// public bool? CodeActions( IEnumerable expectedItems, - string applyFix = null, + string? applyFix = null, bool verifyNotShowing = false, bool ensureExpectedItemsAreOrdered = false, FixAllScope? fixAllScope = null, @@ -91,7 +90,12 @@ public void CodeAction( } } - if (!string.IsNullOrEmpty(applyFix) || fixAllScope.HasValue) + if (fixAllScope.HasValue) + { + Contract.ThrowIfNull(applyFix); + } + + if (!RoslynString.IsNullOrEmpty(applyFix)) { var result = _textViewWindow.ApplyLightBulbAction(applyFix, fixAllScope, blockUntilComplete); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.cs index 514759057762f..c5ab996a22783 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/TextViewWindow_OutOfProc.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 Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs index 197ed7abd8115..b10b93e4536c4 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.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 Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; @@ -119,9 +117,17 @@ public void SetFullSolutionAnalysis(bool value) value: value ? BackgroundAnalysisScope.FullSolution : BackgroundAnalysisScope.Default); } - public void SetFeatureOption(string feature, string optionName, string language, string valueString) + public void SetEnableOpeningSourceGeneratedFilesInWorkspaceExperiment(bool value) + { + SetOption( + optionName: LanguageServices.Implementation.SourceGeneratedFileManager.EnableOpeningInWorkspace.Name, + feature: LanguageServices.Implementation.SourceGeneratedFileManager.EnableOpeningInWorkspace.Feature, + value: value); + } + + public void SetFeatureOption(string feature, string optionName, string language, string? valueString) => _inProc.SetFeatureOption(feature, optionName, language, valueString); - public string GetWorkingFolder() => _inProc.GetWorkingFolder(); + public string? GetWorkingFolder() => _inProc.GetWorkingFolder(); } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/ScreenshotService.cs b/src/VisualStudio/IntegrationTest/TestUtilities/ScreenshotService.cs index ea841fbfbad8f..59598089782bb 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/ScreenshotService.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/ScreenshotService.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.Drawing; using System.Drawing.Imaging; using System.IO; @@ -56,7 +54,7 @@ public static void TakeScreenshot(string fullPath) /// A containing the screen capture of the desktop, or null if a screen /// capture can't be created. /// - private static BitmapSource TryCaptureFullScreen() + private static BitmapSource? TryCaptureFullScreen() { var width = Screen.PrimaryScreen.Bounds.Width; var height = Screen.PrimaryScreen.Bounds.Height; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/TemporaryTextFile.cs b/src/VisualStudio/IntegrationTest/TestUtilities/TemporaryTextFile.cs index 5a5f4e3645e90..a9209f3865104 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/TemporaryTextFile.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/TemporaryTextFile.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; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/TestUtilities.cs b/src/VisualStudio/IntegrationTest/TestUtilities/TestUtilities.cs index 1fa0835aae92e..571fdf71a1c55 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/TestUtilities.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/TestUtilities.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.Collections.Generic; using System.Linq; @@ -105,6 +103,7 @@ public static void ThrowIfExpectedItemNotFoundInOrder(IEnumerable(IEnumerable actual, IEnumerable unexpected) + where TCollection : notnull { var shouldThrow = false; var sb = new StringBuilder(); @@ -127,7 +126,7 @@ public static void ThrowIfUnExpectedItemFound(IEnumerable(IEnumerable expectedList, IEnumerable actualList, - IEqualityComparer comparer = null) + IEqualityComparer? comparer = null) where TListItem : IEquatable { if (!expectedList.SequenceEqual(actualList, comparer)) @@ -137,6 +136,7 @@ public static void CompareAsSequenceAndThrowIfNotEqual(IEnumerable(IEnumerable list) + where TElement : notnull => string.Join(Environment.NewLine, list.Select(item => item.ToString()).ToArray()); } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs index 67fca07709bfc..ffe0dd9bad2fa 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.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.Collections.Immutable; using System.Linq; @@ -15,6 +13,7 @@ using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess; using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess; +using Xunit; using Process = System.Diagnostics.Process; namespace Microsoft.VisualStudio.IntegrationTest.Utilities @@ -264,7 +263,7 @@ public void Close(bool exitHostProcess = true) } } - private static DTE GetDebuggerHostDte() + private static DTE? GetDebuggerHostDte() { var currentProcessId = Process.GetCurrentProcess().Id; foreach (var process in Process.GetProcessesByName("devenv")) @@ -333,8 +332,14 @@ public TelemetryVerifier EnableTestTelemetryChannel() private void DisableTestTelemetryChannel() => _inProc.DisableTestTelemetryChannel(); - private void WaitForTelemetryEvents(string[] names) - => _inProc.WaitForTelemetryEvents(names); + /// + /// Waits for specific telemetry events to occur. + /// + /// The telemetry events to wait for. + /// if the telemetry events occurred; otherwise, if + /// telemetry is disabled. + private bool TryWaitForTelemetryEvents(string[] names) + => _inProc.TryWaitForTelemetryEvents(names); public class TelemetryVerifier : IDisposable { @@ -354,7 +359,12 @@ public TelemetryVerifier(VisualStudioInstance instance) /// public void VerifyFired(params string[] expectedEventNames) { - _instance.WaitForTelemetryEvents(expectedEventNames); + var telemetryEnabled = _instance.TryWaitForTelemetryEvents(expectedEventNames); + if (string.Equals(Environment.GetEnvironmentVariable("ROSLYN_TEST_CI"), "true", StringComparison.OrdinalIgnoreCase)) + { + // Telemetry verification is optional for developer machines, but required for CI. + Assert.True(telemetryEnabled); + } } } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceContext.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceContext.cs index 16c5576a54992..aa3d1dd512441 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceContext.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceContext.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; namespace Microsoft.VisualStudio.IntegrationTest.Utilities diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs index 0a54d6109baa8..2428414f901d7 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.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.Collections.Generic; using System.Collections.Immutable; @@ -36,7 +34,7 @@ public sealed class VisualStudioInstanceFactory : IDisposable /// /// The instance that has already been launched by this factory and can be reused. /// - private VisualStudioInstance _currentlyRunningInstance; + private VisualStudioInstance? _currentlyRunningInstance; /// /// Identifies the first time a Visual Studio instance is launched during an integration test run. @@ -103,7 +101,7 @@ private static void FirstChanceExceptionHandler(object sender, FirstChanceExcept // Depending on the manner in which the assembly was originally loaded, this may end up actually trying to load the assembly a second // time and it can fail if the standard assembly resolution logic fails. This ensures that we 'succeed' this secondary load by returning // the assembly that is already loaded. - private static Assembly AssemblyResolveHandler(object sender, ResolveEventArgs eventArgs) + private static Assembly? AssemblyResolveHandler(object sender, ResolveEventArgs eventArgs) { Debug.WriteLine($"'{eventArgs.RequestingAssembly}' is attempting to resolve '{eventArgs.Name}'"); var resolvedAssembly = AppDomain.CurrentDomain.GetAssemblies().Where((assembly) => assembly.FullName.Equals(eventArgs.Name)).SingleOrDefault(); @@ -125,6 +123,7 @@ public async Task GetNewOrUsedInstanceAsync(Immutab { var shouldStartNewInstance = ShouldStartNewInstance(requiredPackageIds); await UpdateCurrentlyRunningInstanceAsync(requiredPackageIds, shouldStartNewInstance).ConfigureAwait(true); + Contract.ThrowIfNull(_currentlyRunningInstance); return new VisualStudioInstanceContext(_currentlyRunningInstance, this); } @@ -174,7 +173,7 @@ private async Task UpdateCurrentlyRunningInstanceAsync(ImmutableHashSet // We are starting a new instance, so ensure we close the currently running instance, if it exists _currentlyRunningInstance?.Close(); - var instance = LocateVisualStudioInstance(requiredPackageIds) as ISetupInstance2; + var instance = (ISetupInstance2)LocateVisualStudioInstance(requiredPackageIds); supportedPackageIds = ImmutableHashSet.CreateRange(instance.GetPackages().Select((supportedPackage) => supportedPackage.GetId())); installationPath = instance.GetInstallationPath(); @@ -200,7 +199,7 @@ private async Task UpdateCurrentlyRunningInstanceAsync(ImmutableHashSet // We create a new DTE instance in the current context since the COM object could have been separated // from its RCW during the previous test. - Debug.Assert(_currentlyRunningInstance != null); + Contract.ThrowIfNull(_currentlyRunningInstance); hostProcess = _currentlyRunningInstance.HostProcess; dte = await IntegrationHelper.WaitForNotNullAsync(() => IntegrationHelper.TryLocateDteForProcess(hostProcess)).ConfigureAwait(true); @@ -332,6 +331,16 @@ private static Process StartNewVisualStudioProcess(string installationPath, int // Disable roaming settings to avoid interference from the online user profile Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"ApplicationPrivateSettings\\Microsoft\\VisualStudio\" RoamingEnabled string \"1*System.Boolean*False\"")).WaitForExit(); + // HACK: 16.10P2 contains an LSP client bug where on solution closed, server activation tasks that are not already completed / cancelled + // do not properly get cancelled. When a new solution is opened these incomplete server ativation tasks are not cleared. + // Any feature that waits for LSP server activations to complete will hang on the old incomplete server activation tasks. + // + // The roslyn C# always active server and intellicode's refactorings LSP server are the only LSP servers active on C# files in 16.10p2. + // To work around potential hangs where the intellicode server activation does not complete before solution close, we disable their LSP server entirely. + // To work around potential hangs in the roslyn C# server, we wait for the async listener around the LSP server activation to complete before proceeding. + // Editor tracking bug (to be fixed in 16.10P3) - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1322125 + Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"ApplicationPrivateSettings\\Microsoft\\VisualStudio\\IntelliCode\" Refactorings string \"0*System.Int32*2\"")).WaitForExit(); + // Disable background download UI to avoid toasts Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"FeatureFlags\\Setup\\BackgroundDownload\" Value dword 0")).WaitForExit(); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownCommandNames.cs b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownCommandNames.cs index 3c66ada6b0d6e..9bcd21c2cbcd8 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownCommandNames.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownCommandNames.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 - namespace Microsoft.VisualStudio.IntegrationTest.Utilities { public static class WellKnownCommandNames diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownProjectTemplates.cs b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownProjectTemplates.cs index 09ab81f0e9d18..e31873fc08206 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownProjectTemplates.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownProjectTemplates.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 - namespace Microsoft.VisualStudio.IntegrationTest.Utilities { public static class WellKnownProjectTemplates diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownTagNames.cs b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownTagNames.cs index 1b3f27843b013..1df1ac1169131 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownTagNames.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownTagNames.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 Microsoft.CodeAnalysis.Editor.ReferenceHighlighting; @@ -15,7 +13,7 @@ public class WellKnownTagNames public const string MarkerFormatDefinition_HighlightedDefinition = "MarkerFormatDefinition/HighlightedDefinition"; public const string MarkerFormatDefinition_HighlightedWrittenReference = "MarkerFormatDefinition/HighlightedWrittenReference"; - public static Type GetTagTypeByName(string typeName) + public static Type? GetTagTypeByName(string typeName) { switch (typeName) { diff --git a/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs b/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs index 4888888e9b424..0e9319c1aeede 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.LanguageServices.LiveShare.Client { [ExportWorkspaceService(typeof(ITextBufferSupportsFeatureService), WorkspaceKind.CloudEnvironmentClientWorkspace), Shared] - class CloudEnvironmentSupportsFeatureService : ITextBufferSupportsFeatureService + internal class CloudEnvironmentSupportsFeatureService : ITextBufferSupportsFeatureService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] diff --git a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb index 96c66d32b7186..bc4b23cdde4b6 100644 --- a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb @@ -58,7 +58,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Dim project = workspace.CurrentSolution.Projects.Single() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) - Dim notificationService = workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService) Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider)() Dim state = New CodeModelState( @@ -70,7 +69,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel mockVisualStudioWorkspace, mockServiceProvider, threadingContext, - notificationService, listenerProvider)) Dim projectCodeModel = DirectCast(state.ProjectCodeModelFactory.CreateProjectCodeModel(project.Id, Nothing), ProjectCodeModel) diff --git a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb index 55fdb7ed82b66..e8090351abb28 100644 --- a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb +++ b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb @@ -5,10 +5,8 @@ Imports Microsoft.CodeAnalysis.Editor.Host Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Notification -Imports Microsoft.CodeAnalysis.Remote Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.LanguageServices.CSharp -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Utilities Imports Microsoft.VisualStudio.LanguageServices.Remote Imports Microsoft.VisualStudio.LanguageServices.VisualBasic @@ -28,7 +26,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests GetType(VisualStudioRemoteHostClientProvider.Factory), ' Do not use ServiceHub in VS unit tests, run services locally. GetType(IStreamingFindUsagesPresenter), ' TODO: should we be using the actual implementation (https://github.com/dotnet/roslyn/issues/46380)? GetType(HACK_ThemeColorFixer), - GetType(INotificationService), ' EditorNotificationServiceFactory is used - GetType(VisualStudioWaitIndicator)) ' TestWaitIndicator is used instead + GetType(INotificationService)) End Class End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx index cefbd3e299467..e0a2f6d0078e6 100644 --- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx +++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx @@ -322,4 +322,7 @@ Sort imports {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized + + Prefer simplified object creation + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb index 84503c73dfe7f..d1df51e52f392 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicCodeCleanupFixerProvider.vb @@ -11,7 +11,7 @@ Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeCleanup - + Friend Class VisualBasicCodeCleanUpFixerProvider Inherits AbstractCodeCleanUpFixerProvider diff --git a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj index 21614068421a5..f3fb8c06b1dd7 100644 --- a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj +++ b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj @@ -14,14 +14,8 @@ false - - + + diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml index 368b73ff6cc85..bf0bfc8251a35 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml @@ -32,6 +32,8 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_use_64bit_analysis_process}" /> + + + @@ -236,6 +242,14 @@ + + + + + diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb index 3c9a85517bd56..67cd97f66541a 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb @@ -4,6 +4,8 @@ Imports System.Windows Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Classification +Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.DocumentationComments Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Editor.Implementation.SplitComment @@ -37,75 +39,102 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options InitializeComponent() + ' Keep this code in sync with the actual order options appear in Tools | Options + + ' Analysis BindToOption(Background_analysis_scope_active_file, SolutionCrawlerOptions.BackgroundAnalysisScopeOption, BackgroundAnalysisScope.ActiveFile, LanguageNames.VisualBasic) BindToOption(Background_analysis_scope_open_files, SolutionCrawlerOptions.BackgroundAnalysisScopeOption, BackgroundAnalysisScope.OpenFilesAndProjects, LanguageNames.VisualBasic) BindToOption(Background_analysis_scope_full_solution, SolutionCrawlerOptions.BackgroundAnalysisScopeOption, BackgroundAnalysisScope.FullSolution, LanguageNames.VisualBasic) BindToOption(Use_64bit_analysis_process, RemoteHostOptions.OOP64Bit) - BindToOption(Show_Remove_Unused_References_command_in_Solution_Explorer_experimental, FeatureOnOffOptions.OfferRemoveUnusedReferences, Function() - ' If the option has Not been set by the user, check if the option to remove unused references - ' Is enabled from experimentation. If so, default to that. Otherwise default to disabled - If experimentationService Is Nothing Then - Return False - End If - - Return experimentationService.IsExperimentEnabled(WellKnownExperimentNames.RemoveUnusedReferences) - End Function) - + BindToOption(Enable_file_logging_for_diagnostics, InternalDiagnosticsOptions.EnableFileLoggingForDiagnostics) + BindToOption(Show_Remove_Unused_References_command_in_Solution_Explorer_experimental, FeatureOnOffOptions.OfferRemoveUnusedReferences, + Function() + ' If the option has Not been set by the user, check if the option to remove unused references + ' Is enabled from experimentation. If so, default to that. Otherwise default to disabled + If experimentationService Is Nothing Then + Return False + End If + + Return experimentationService.IsExperimentEnabled(WellKnownExperimentNames.RemoveUnusedReferences) + End Function) + + ' Import directives BindToOption(PlaceSystemNamespaceFirst, GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic) BindToOption(SeparateImportGroups, GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.VisualBasic) BindToOption(SuggestForTypesInReferenceAssemblies, SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, LanguageNames.VisualBasic) BindToOption(SuggestForTypesInNuGetPackages, SymbolSearchOptions.SuggestForTypesInNuGetPackages, LanguageNames.VisualBasic) - BindToOption(AddMissingImportsOnPaste, FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.VisualBasic, Function() - ' If the option has Not been set by the user, check if the option to enable imports on paste - ' Is enabled from experimentation. If so, default to that. Otherwise default to disabled - If experimentationService Is Nothing Then - Return False - End If - - Return experimentationService.IsExperimentEnabled(WellKnownExperimentNames.ImportsOnPasteDefaultEnabled) - End Function) + BindToOption(AddMissingImportsOnPaste, FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.VisualBasic, + Function() + ' If the option has Not been set by the user, check if the option to enable imports on paste + ' Is enabled from experimentation. If so, default to that. Otherwise default to disabled + If experimentationService Is Nothing Then + Return False + End If + + Return experimentationService.IsExperimentEnabled(WellKnownExperimentNames.ImportsOnPasteDefaultEnabled) + End Function) + + ' Highlighting + BindToOption(EnableHighlightReferences, FeatureOnOffOptions.ReferenceHighlighting, LanguageNames.VisualBasic) + BindToOption(EnableHighlightKeywords, FeatureOnOffOptions.KeywordHighlighting, LanguageNames.VisualBasic) + ' Outlining BindToOption(EnableOutlining, FeatureOnOffOptions.Outlining, LanguageNames.VisualBasic) + BindToOption(DisplayLineSeparators, FeatureOnOffOptions.LineSeparator, LanguageNames.VisualBasic) BindToOption(Show_outlining_for_declaration_level_constructs, BlockStructureOptions.ShowOutliningForDeclarationLevelConstructs, LanguageNames.VisualBasic) BindToOption(Show_outlining_for_code_level_constructs, BlockStructureOptions.ShowOutliningForCodeLevelConstructs, LanguageNames.VisualBasic) BindToOption(Show_outlining_for_comments_and_preprocessor_regions, BlockStructureOptions.ShowOutliningForCommentsAndPreprocessorRegions, LanguageNames.VisualBasic) BindToOption(Collapse_regions_when_collapsing_to_definitions, BlockStructureOptions.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.VisualBasic) + ' Fading BindToOption(Fade_out_unused_imports, FadingOptions.FadeOutUnusedImports, LanguageNames.VisualBasic) + ' Block structure guides BindToOption(Show_guides_for_declaration_level_constructs, BlockStructureOptions.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.VisualBasic) BindToOption(Show_guides_for_code_level_constructs, BlockStructureOptions.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.VisualBasic) + ' Comments BindToOption(GenerateXmlDocCommentsForTripleApostrophes, DocumentationCommentOptions.AutoXmlDocCommentGeneration, LanguageNames.VisualBasic) BindToOption(InsertApostropheAtTheStartOfNewLinesWhenWritingApostropheComments, SplitCommentOptions.Enabled, LanguageNames.VisualBasic) + ' Editor help BindToOption(EnableEndConstruct, FeatureOnOffOptions.EndConstruct, LanguageNames.VisualBasic) BindToOption(EnableLineCommit, FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic) BindToOption(AutomaticInsertionOfInterfaceAndMustOverrideMembers, FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, LanguageNames.VisualBasic) - BindToOption(DisplayLineSeparators, FeatureOnOffOptions.LineSeparator, LanguageNames.VisualBasic) - BindToOption(EnableHighlightReferences, FeatureOnOffOptions.ReferenceHighlighting, LanguageNames.VisualBasic) - BindToOption(EnableHighlightKeywords, FeatureOnOffOptions.KeywordHighlighting, LanguageNames.VisualBasic) BindToOption(RenameTrackingPreview, FeatureOnOffOptions.RenameTrackingPreview, LanguageNames.VisualBasic) BindToOption(ShowRemarksInQuickInfo, QuickInfoOptions.ShowRemarksInQuickInfo, LanguageNames.VisualBasic) + BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, ValidateFormatStringOption.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.VisualBasic) + BindToOption(Underline_reassigned_variables, ClassificationOptions.ClassifyReassignedVariables, LanguageNames.VisualBasic) + + ' Go To Definition BindToOption(NavigateToObjectBrowser, VisualStudioNavigationOptions.NavigateToObjectBrowser, LanguageNames.VisualBasic) + BindToOption(Enable_all_features_in_opened_files_from_source_generators, SourceGeneratedFileManager.EnableOpeningInWorkspace, + Function() + ' If the option has not been set by the user, check if the option Is enabled from experimentation. + ' If so, default to that. Otherwise default to disabled + Return If(experimentationService?.IsExperimentEnabled(WellKnownExperimentNames.SourceGeneratorsEnableOpeningInWorkspace), False) + End Function) + + ' Regular expressions + BindToOption(Colorize_regular_expressions, RegularExpressionsOptions.ColorizeRegexPatterns, LanguageNames.VisualBasic) + BindToOption(Report_invalid_regular_expressions, RegularExpressionsOptions.ReportInvalidRegexPatterns, LanguageNames.VisualBasic) + BindToOption(Highlight_related_components_under_cursor, RegularExpressionsOptions.HighlightRelatedRegexComponentsUnderCursor, LanguageNames.VisualBasic) + BindToOption(Show_completion_list, RegularExpressionsOptions.ProvideRegexCompletions, LanguageNames.VisualBasic) + + ' Editor color scheme + BindToOption(Editor_color_scheme, ColorSchemeOptions.ColorScheme) + ' Extract method BindToOption(DontPutOutOrRefOnStruct, ExtractMethodOptions.DontPutOutOrRefOnStruct, LanguageNames.VisualBasic) + ' Implement Interface or Abstract Class BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.VisualBasic) BindToOption(at_the_end, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.VisualBasic) BindToOption(prefer_throwing_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.VisualBasic) BindToOption(prefer_auto_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.VisualBasic) - BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, ValidateFormatStringOption.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.VisualBasic) - - BindToOption(Colorize_regular_expressions, RegularExpressionsOptions.ColorizeRegexPatterns, LanguageNames.VisualBasic) - BindToOption(Report_invalid_regular_expressions, RegularExpressionsOptions.ReportInvalidRegexPatterns, LanguageNames.VisualBasic) - BindToOption(Highlight_related_components_under_cursor, RegularExpressionsOptions.HighlightRelatedRegexComponentsUnderCursor, LanguageNames.VisualBasic) - BindToOption(Show_completion_list, RegularExpressionsOptions.ProvideRegexCompletions, LanguageNames.VisualBasic) - - BindToOption(Editor_color_scheme, ColorSchemeOptions.ColorScheme) - + ' Inline hints BindToOption(DisplayAllHintsWhilePressingAltF1, InlineHintsOptions.DisplayAllHintsWhilePressingAltF1) BindToOption(ColorHints, InlineHintsOptions.ColorHints, LanguageNames.VisualBasic) @@ -115,6 +144,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BindToOption(ShowHintsForEverythingElse, InlineHintsOptions.ForOtherParameters, LanguageNames.VisualBasic) BindToOption(SuppressHintsWhenParameterNameMatchesTheMethodsIntent, InlineHintsOptions.SuppressForParametersThatMatchMethodIntent, LanguageNames.VisualBasic) BindToOption(SuppressHintsWhenParameterNamesDifferOnlyBySuffix, InlineHintsOptions.SuppressForParametersThatDifferOnlyBySuffix, LanguageNames.VisualBasic) + + BindToOption(ShowInheritanceMargin, FeatureOnOffOptions.ShowInheritanceMargin, LanguageNames.VisualBasic) End Sub ' Since this dialog is constructed once for the lifetime of the application and VS Theme can be changed after the application has started, diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb index da9c218310795..e3e5208d35774 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb @@ -33,6 +33,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Public ReadOnly Property Option_DisplayLineSeparators As String = BasicVSResources.Show_procedure_line_separators + Public ReadOnly Property Option_Underline_reassigned_variables As String = + ServicesVSResources.Underline_reassigned_variables + Public ReadOnly Property Option_Display_all_hints_while_pressing_Alt_F1 As String = ServicesVSResources.Display_all_hints_while_pressing_Alt_F1 @@ -283,5 +286,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Public ReadOnly Property Option_Show_Remove_Unused_References_command_in_Solution_Explorer_experimental As String = ServicesVSResources.Show_Remove_Unused_References_command_in_Solution_Explorer_experimental + + Public ReadOnly Property Enable_all_features_in_opened_files_from_source_generators_experimental As String = + ServicesVSResources.Enable_all_features_in_opened_files_from_source_generators_experimental + + Public ReadOnly Property Option_Enable_file_logging_for_diagnostics As String = + ServicesVSResources.Enable_file_logging_for_diagnostics + + Public ReadOnly Property Show_inheritance_margin As String = + ServicesVSResources.Show_inheritance_margin + + Public ReadOnly Property Inheritance_Margin_experimental As String = + ServicesVSResources.Inheritance_Margin_experimental End Module End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb deleted file mode 100644 index 5212f1930bbbf..0000000000000 --- a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject.vb +++ /dev/null @@ -1,333 +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. - -Imports System.Runtime.InteropServices -Imports System.Xml.Linq -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeStyle -Imports Microsoft.CodeAnalysis.Completion -Imports Microsoft.CodeAnalysis.DocumentationComments -Imports Microsoft.CodeAnalysis.Editing -Imports Microsoft.CodeAnalysis.Editor.Shared.Options -Imports Microsoft.CodeAnalysis.ExtractMethod -Imports Microsoft.CodeAnalysis.Options -Imports Microsoft.CodeAnalysis.Shared.Options -Imports Microsoft.CodeAnalysis.Simplification -Imports Microsoft.CodeAnalysis.SymbolSearch - -Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options - - Public Class AutomationObject - Private ReadOnly _workspace As Workspace - - Friend Sub New(workspace As Workspace) - _workspace = workspace - End Sub - - Public Property AutoComment As Boolean - Get - Return GetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration) - End Get - Set(value As Boolean) - SetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration, value) - End Set - End Property - - Public Property AutoEndInsert As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.EndConstruct) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.EndConstruct, value) - End Set - End Property - - Public Property AutoRequiredMemberInsert As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, value) - End Set - End Property - - - Public Property ClosedFileDiagnostics As Boolean - Get - Return False - End Get - Set(value As Boolean) - End Set - End Property - - - Public Property BasicClosedFileDiagnostics As Integer - Get - Return 0 - End Get - Set(value As Integer) - End Set - End Property - - Public Property RenameTrackingPreview As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview, value) - End Set - End Property - - Public Property DisplayLineSeparators As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.LineSeparator) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.LineSeparator, value) - End Set - End Property - - Public Property EnableHighlightReferences As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting, value) - End Set - End Property - - Public Property EnableHighlightRelatedKeywords As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.KeywordHighlighting) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.KeywordHighlighting, value) - End Set - End Property - - Public Property ExtractMethod_DoNotPutOutOrRefOnStruct As Boolean - Get - Return GetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct) - End Get - Set(value As Boolean) - SetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct, value) - End Set - End Property - - Public Property Outlining As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.Outlining) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.Outlining, value) - End Set - End Property - - Public Property PrettyListing As Boolean - Get - Return GetBooleanOption(FeatureOnOffOptions.PrettyListing) - End Get - Set(value As Boolean) - SetBooleanOption(FeatureOnOffOptions.PrettyListing, value) - End Set - End Property - - Public Property Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration_CodeStyle As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, value) - End Set - End Property - - Public Property Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess_CodeStyle As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, value) - End Set - End Property - - Public Property Style_QualifyFieldAccess As String - Get - Return GetXmlOption(CodeStyleOptions2.QualifyFieldAccess) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.QualifyFieldAccess, value) - End Set - End Property - - Public Property Style_QualifyPropertyAccess As String - Get - Return GetXmlOption(CodeStyleOptions2.QualifyPropertyAccess) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.QualifyPropertyAccess, value) - End Set - End Property - - Public Property Style_QualifyMethodAccess As String - Get - Return GetXmlOption(CodeStyleOptions2.QualifyMethodAccess) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.QualifyMethodAccess, value) - End Set - End Property - - Public Property Style_QualifyEventAccess As String - Get - Return GetXmlOption(CodeStyleOptions2.QualifyEventAccess) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.QualifyEventAccess, value) - End Set - End Property - - Public Property Style_PreferObjectInitializer As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferObjectInitializer) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferObjectInitializer, value) - End Set - End Property - - Public Property Style_PreferCollectionInitializer As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferCollectionInitializer) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferCollectionInitializer, value) - End Set - End Property - - Public Property Style_PreferCoalesceExpression As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferCoalesceExpression) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferCoalesceExpression, value) - End Set - End Property - - Public Property Style_PreferNullPropagation As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferNullPropagation) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferNullPropagation, value) - End Set - End Property - - Public Property Style_PreferInferredTupleNames As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferInferredTupleNames) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferInferredTupleNames, value) - End Set - End Property - - Public Property Style_PreferInferredAnonymousTypeMemberNames As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames, value) - End Set - End Property - - Public Property Style_PreferExplicitTupleNames As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames, value) - End Set - End Property - - Public Property Style_PreferReadonly As String - Get - Return GetXmlOption(CodeStyleOptions2.PreferReadonly) - End Get - Set(value As String) - SetXmlOption(CodeStyleOptions2.PreferReadonly, value) - End Set - End Property - - Public Property Option_PlaceSystemNamespaceFirst As Boolean - Get - Return GetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst) - End Get - Set(value As Boolean) - SetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst, value) - End Set - End Property - - Public Property Option_SuggestImportsForTypesInReferenceAssemblies As Boolean - Get - Return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies) - End Get - Set(value As Boolean) - SetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, value) - End Set - End Property - - Public Property Option_SuggestImportsForTypesInNuGetPackages As Boolean - Get - Return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages) - End Get - Set(value As Boolean) - SetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages, value) - End Set - End Property - - Public Property Option_ShowItemsFromUnimportedNamespaces As Integer - Get - Return GetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces) - End Get - Set(value As Integer) - SetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, value) - End Set - End Property - - Private Function GetBooleanOption(key As [PerLanguageOption2](Of Boolean)) As Boolean - Return _workspace.Options.GetOption(key, LanguageNames.VisualBasic) - End Function - - Private Function GetXmlOption(key As PerLanguageOption2(Of CodeStyleOption2(Of Boolean))) As String - Return _workspace.Options.GetOption(key, LanguageNames.VisualBasic).ToXElement().ToString() - End Function - - Private Sub SetBooleanOption(key As [PerLanguageOption2](Of Boolean), value As Boolean) - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options _ - .WithChangedOption(key, LanguageNames.VisualBasic, value))) - End Sub - - Private Function GetBooleanOption(key As PerLanguageOption2(Of Boolean?)) As Integer - Dim [option] = _workspace.Options.GetOption(key, LanguageNames.VisualBasic) - If Not [option].HasValue Then - Return -1 - End If - - Return If([option].Value, 1, 0) - End Function - - Private Sub SetBooleanOption(key As PerLanguageOption2(Of Boolean?), value As Integer) - Dim boolValue As Boolean? = If(value < 0, Nothing, value > 0) - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options _ - .WithChangedOption(key, LanguageNames.VisualBasic, boolValue))) - End Sub - - Private Sub SetXmlOption(key As PerLanguageOption2(Of CodeStyleOption2(Of Boolean)), value As String) - Dim convertedValue = CodeStyleOption2(Of Boolean).FromXElement(XElement.Parse(value)) - _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options _ - .WithChangedOption(key, LanguageNames.VisualBasic, convertedValue))) - End Sub - - End Class -End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Completion.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Completion.vb new file mode 100644 index 0000000000000..417971fe34c59 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Completion.vb @@ -0,0 +1,72 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Completion + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property Option_TriggerOnTypingLetters As Boolean + Get + Return GetBooleanOption(CompletionOptions.TriggerOnTypingLetters2) + End Get + Set(value As Boolean) + SetBooleanOption(CompletionOptions.TriggerOnTypingLetters2, value) + End Set + End Property + + Public Property Option_HighlightMatchingPortionsOfCompletionListItems As Boolean + Get + Return GetBooleanOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems) + End Get + Set(value As Boolean) + SetBooleanOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems, value) + End Set + End Property + + Public Property Option_EnterKeyBehavior As Integer + Get + Return GetOption(CompletionOptions.EnterKeyBehavior) + End Get + Set(value As Integer) + SetOption(CompletionOptions.EnterKeyBehavior, DirectCast(value, EnterKeyRule)) + End Set + End Property + + Public Property Option_SnippetsBehavior As Integer + Get + Return GetOption(CompletionOptions.SnippetsBehavior) + End Get + Set(value As Integer) + SetOption(CompletionOptions.SnippetsBehavior, DirectCast(value, SnippetsRule)) + End Set + End Property + + Public Property Option_ShowItemsFromUnimportedNamespaces As Integer + Get + Return GetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces) + End Get + Set(value As Integer) + SetBooleanOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, value) + End Set + End Property + + Public Property Option_TriggerInArgumentLists As Boolean + Get + Return GetBooleanOption(CompletionOptions.TriggerInArgumentLists) + End Get + Set(value As Boolean) + SetBooleanOption(CompletionOptions.TriggerInArgumentLists, value) + End Set + End Property + + Public Property Option_EnableArgumentCompletionSnippets As Integer + Get + Return GetBooleanOption(CompletionOptions.EnableArgumentCompletionSnippets) + End Get + Set(value As Integer) + SetBooleanOption(CompletionOptions.EnableArgumentCompletionSnippets, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.DocumentationComment.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.DocumentationComment.vb new file mode 100644 index 0000000000000..06b8fdac906de --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.DocumentationComment.vb @@ -0,0 +1,18 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.DocumentationComments + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property AutoComment As Boolean + Get + Return GetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration) + End Get + Set(value As Boolean) + SetBooleanOption(DocumentationCommentOptions.AutoXmlDocCommentGeneration, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.ExtractMethod.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.ExtractMethod.vb new file mode 100644 index 0000000000000..866055c4dd7da --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.ExtractMethod.vb @@ -0,0 +1,27 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.ExtractMethod + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property ExtractMethod_AllowBestEffort As Boolean + Get + Return GetBooleanOption(ExtractMethodOptions.AllowBestEffort) + End Get + Set(value As Boolean) + SetBooleanOption(ExtractMethodOptions.AllowBestEffort, value) + End Set + End Property + + Public Property ExtractMethod_DoNotPutOutOrRefOnStruct As Boolean + Get + Return GetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct) + End Get + Set(value As Boolean) + SetBooleanOption(ExtractMethodOptions.DontPutOutOrRefOnStruct, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Formatting.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Formatting.vb new file mode 100644 index 0000000000000..fd0e372666e6a --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Formatting.vb @@ -0,0 +1,20 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Formatting + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + + Public Property FormatOnPaste As Boolean + Get + Return GetBooleanOption(FormattingOptions2.FormatOnPaste) + End Get + Set(value As Boolean) + SetBooleanOption(FormattingOptions2.FormatOnPaste, value) + End Set + End Property + + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Generation.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Generation.vb new file mode 100644 index 0000000000000..8866a2e875574 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Generation.vb @@ -0,0 +1,27 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Editing + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property Option_PlaceSystemNamespaceFirst As Boolean + Get + Return GetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst) + End Get + Set(value As Boolean) + SetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst, value) + End Set + End Property + + Public Property Option_SeparateImportDirectiveGroups As Boolean + Get + Return GetBooleanOption(GenerationOptions.SeparateImportDirectiveGroups) + End Get + Set(value As Boolean) + SetBooleanOption(GenerationOptions.SeparateImportDirectiveGroups, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Obsolete.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Obsolete.vb new file mode 100644 index 0000000000000..342a6e2308b93 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Obsolete.vb @@ -0,0 +1,25 @@ +' 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.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + + Public Property ClosedFileDiagnostics As Boolean + Get + Return False + End Get + Set(value As Boolean) + End Set + End Property + + + Public Property BasicClosedFileDiagnostics As Integer + Get + Return 0 + End Get + Set(value As Integer) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.OnOff.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.OnOff.vb new file mode 100644 index 0000000000000..a791eca4dca2c --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.OnOff.vb @@ -0,0 +1,117 @@ +' 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 Microsoft.CodeAnalysis.Editor.Shared.Options + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property AutoEndInsert As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.EndConstruct) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.EndConstruct, value) + End Set + End Property + + Public Property AutoRequiredMemberInsert As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, value) + End Set + End Property + + Public Property RenameTrackingPreview As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.RenameTrackingPreview, value) + End Set + End Property + + Public Property DisplayLineSeparators As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.LineSeparator) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.LineSeparator, value) + End Set + End Property + + Public Property EnableHighlightReferences As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.ReferenceHighlighting, value) + End Set + End Property + + Public Property EnableHighlightRelatedKeywords As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.KeywordHighlighting) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.KeywordHighlighting, value) + End Set + End Property + + Public Property Outlining As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.Outlining) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.Outlining, value) + End Set + End Property + + Public Property PrettyListing As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.PrettyListing) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.PrettyListing, value) + End Set + End Property + + Public Property NavigateToDecompiledSources As Boolean + Get + Return GetBooleanOption(FeatureOnOffOptions.NavigateToDecompiledSources) + End Get + Set(value As Boolean) + SetBooleanOption(FeatureOnOffOptions.NavigateToDecompiledSources, value) + End Set + End Property + + Public Property UseEnhancedColorsForManagedLanguages As Integer + Get + Return GetOption(FeatureOnOffOptions.UseEnhancedColors) + End Get + Set(value As Integer) + SetOption(FeatureOnOffOptions.UseEnhancedColors, value) + End Set + End Property + + Public Property AddImportsOnPaste As Integer + Get + Return GetBooleanOption(FeatureOnOffOptions.AddImportsOnPaste) + End Get + Set(value As Integer) + SetBooleanOption(FeatureOnOffOptions.AddImportsOnPaste, value) + End Set + End Property + + Public Property OfferRemoveUnusedReferences As Integer + Get + Return GetBooleanOption(FeatureOnOffOptions.OfferRemoveUnusedReferences) + End Get + Set(value As Integer) + SetBooleanOption(FeatureOnOffOptions.OfferRemoveUnusedReferences, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Style.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Style.vb new file mode 100644 index 0000000000000..da0fa544c6627 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.Style.vb @@ -0,0 +1,316 @@ +' 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 Microsoft.CodeAnalysis.CodeStyle +Imports Microsoft.CodeAnalysis.VisualBasic.CodeStyle + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration_CodeStyle As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, value) + End Set + End Property + + Public Property Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess_CodeStyle As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, value) + End Set + End Property + + Public Property Style_QualifyFieldAccess As String + Get + Return GetXmlOption(CodeStyleOptions2.QualifyFieldAccess) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.QualifyFieldAccess, value) + End Set + End Property + + Public Property Style_QualifyPropertyAccess As String + Get + Return GetXmlOption(CodeStyleOptions2.QualifyPropertyAccess) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.QualifyPropertyAccess, value) + End Set + End Property + + Public Property Style_QualifyMethodAccess As String + Get + Return GetXmlOption(CodeStyleOptions2.QualifyMethodAccess) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.QualifyMethodAccess, value) + End Set + End Property + + Public Property Style_QualifyEventAccess As String + Get + Return GetXmlOption(CodeStyleOptions2.QualifyEventAccess) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.QualifyEventAccess, value) + End Set + End Property + + Public Property Style_PreferObjectInitializer As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferObjectInitializer) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferObjectInitializer, value) + End Set + End Property + + Public Property Style_PreferCollectionInitializer As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferCollectionInitializer) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferCollectionInitializer, value) + End Set + End Property + + Public Property Style_PreferObjectInitializer_FadeOutCode As Boolean + Get + Return GetBooleanOption(CodeStyleOptions2.PreferObjectInitializer_FadeOutCode) + End Get + Set(value As Boolean) + SetBooleanOption(CodeStyleOptions2.PreferObjectInitializer_FadeOutCode, value) + End Set + End Property + + Public Property Style_PreferCollectionInitializer_FadeOutCode As Boolean + Get + Return GetBooleanOption(CodeStyleOptions2.PreferCollectionInitializer_FadeOutCode) + End Get + Set(value As Boolean) + SetBooleanOption(CodeStyleOptions2.PreferCollectionInitializer_FadeOutCode, value) + End Set + End Property + + Public Property Style_PreferSimplifiedBooleanExpressions As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferSimplifiedBooleanExpressions) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferSimplifiedBooleanExpressions, value) + End Set + End Property + + Public Property Style_PreferCoalesceExpression As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferCoalesceExpression) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferCoalesceExpression, value) + End Set + End Property + + Public Property Style_PreferNullPropagation As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferNullPropagation) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferNullPropagation, value) + End Set + End Property + + Public Property Style_PreferAutoProperties As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferAutoProperties) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferAutoProperties, value) + End Set + End Property + + Public Property Style_PreferInferredTupleNames As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferInferredTupleNames) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferInferredTupleNames, value) + End Set + End Property + + Public Property Style_PreferInferredAnonymousTypeMemberNames As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferInferredAnonymousTypeMemberNames, value) + End Set + End Property + + Public Property Style_PreferExplicitTupleNames As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferExplicitTupleNames, value) + End Set + End Property + + Public Property Style_PreferIsNullCheckOverReferenceEqualityMethod As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, value) + End Set + End Property + + Public Property Style_PreferConditionalExpressionOverAssignment As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverAssignment) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverAssignment, value) + End Set + End Property + + Public Property Style_PreferConditionalExpressionOverReturn As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverReturn) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferConditionalExpressionOverReturn, value) + End Set + End Property + + Public Property Style_PreferCompoundAssignment As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferCompoundAssignment) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferCompoundAssignment, value) + End Set + End Property + + Public Property Style_PreferSimplifiedInterpolation As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferSimplifiedInterpolation) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferSimplifiedInterpolation, value) + End Set + End Property + + Public Property Style_RequireAccessibilityModifiers As String + Get + Return GetXmlOption(CodeStyleOptions2.RequireAccessibilityModifiers) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.RequireAccessibilityModifiers, value) + End Set + End Property + + Public Property Style_RemoveUnnecessarySuppressionExclusions As String + Get + Return GetOption(CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions) + End Get + Set(value As String) + SetOption(CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions, value) + End Set + End Property + + Public Property Style_PreferSystemHashCode As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferSystemHashCode) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferSystemHashCode, value) + End Set + End Property + + Public Property Style_PreferNamespaceAndFolderMatchStructure As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferNamespaceAndFolderMatchStructure) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferNamespaceAndFolderMatchStructure, value) + End Set + End Property + + Public Property Style_AllowMultipleBlankLines As String + Get + Return GetXmlOption(CodeStyleOptions2.AllowMultipleBlankLines) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.AllowMultipleBlankLines, value) + End Set + End Property + + Public Property Style_AllowStatementImmediatelyAfterBlock As String + Get + Return GetXmlOption(CodeStyleOptions2.AllowStatementImmediatelyAfterBlock) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.AllowStatementImmediatelyAfterBlock, value) + End Set + End Property + + Public Property Style_PreferReadonly As String + Get + Return GetXmlOption(CodeStyleOptions2.PreferReadonly) + End Get + Set(value As String) + SetXmlOption(CodeStyleOptions2.PreferReadonly, value) + End Set + End Property + + Public Property Style_PreferredModifierOrder As String + Get + Return GetXmlOption(VisualBasicCodeStyleOptions.PreferredModifierOrder) + End Get + Set(value As String) + SetXmlOption(VisualBasicCodeStyleOptions.PreferredModifierOrder, value) + End Set + End Property + + Public Property Style_PreferIsNotExpression As String + Get + Return GetXmlOption(VisualBasicCodeStyleOptions.PreferIsNotExpression) + End Get + Set(value As String) + SetXmlOption(VisualBasicCodeStyleOptions.PreferIsNotExpression, value) + End Set + End Property + + Public Property Style_PreferSimplifiedObjectCreation As String + Get + Return GetXmlOption(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation) + End Get + Set(value As String) + SetXmlOption(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, value) + End Set + End Property + + Public Property Style_UnusedValueAssignment As String + Get + Return GetXmlOption(VisualBasicCodeStyleOptions.UnusedValueAssignment) + End Get + Set(value As String) + SetXmlOption(VisualBasicCodeStyleOptions.UnusedValueAssignment, value) + End Set + End Property + + Public Property Style_UnusedValueExpressionStatement As String + Get + Return GetXmlOption(VisualBasicCodeStyleOptions.UnusedValueExpressionStatement) + End Get + Set(value As String) + SetXmlOption(VisualBasicCodeStyleOptions.UnusedValueExpressionStatement, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.SymbolSearch.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.SymbolSearch.vb new file mode 100644 index 0000000000000..657487c17a899 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.SymbolSearch.vb @@ -0,0 +1,27 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.SymbolSearch + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + Partial Public Class AutomationObject + Public Property Option_SuggestImportsForTypesInReferenceAssemblies As Boolean + Get + Return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies) + End Get + Set(value As Boolean) + SetBooleanOption(SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, value) + End Set + End Property + + Public Property Option_SuggestImportsForTypesInNuGetPackages As Boolean + Get + Return GetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages) + End Get + Set(value As Boolean) + SetBooleanOption(SymbolSearchOptions.SuggestForTypesInNuGetPackages, value) + End Set + End Property + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.vb b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.vb new file mode 100644 index 0000000000000..4a8e600f65efb --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/Options/AutomationObject/AutomationObject.vb @@ -0,0 +1,35 @@ +' 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.Runtime.InteropServices +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Options + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options + + Partial Public Class AutomationObject + Inherits AbstractAutomationObject + + Friend Sub New(workspace As Workspace) + MyBase.New(workspace, LanguageNames.VisualBasic) + End Sub + + Private Overloads Function GetBooleanOption(key As PerLanguageOption2(Of Boolean)) As Boolean + Return GetOption(key) + End Function + + Private Overloads Function GetBooleanOption(key As Option2(Of Boolean)) As Boolean + Return GetOption(key) + End Function + + Private Overloads Sub SetBooleanOption(key As PerLanguageOption2(Of Boolean), value As Boolean) + SetOption(key, value) + End Sub + + Private Overloads Sub SetBooleanOption(key As Option2(Of Boolean), value As Boolean) + SetOption(key, value) + End Sub + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb index dc730c35e3432..44e078b28ed94 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb @@ -465,6 +465,24 @@ Class Customer End Sub End Class" + Private Shared ReadOnly s_preferSimplifiedObjectCreation As String = $" +Imports System + +Class Customer + Sub M1() +//[ + ' {ServicesVSResources.Prefer_colon} + Dim c As New Customer() +//] + End Sub + Sub M2() +//[ + ' {ServicesVSResources.Over_colon} + Dim c As Customer = New Customer() +//] + End Sub +End Class" + #Region "arithmetic binary parentheses" Private Shared ReadOnly s_arithmeticBinaryAlwaysForClarity As String = $" @@ -825,6 +843,7 @@ End Class Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferConditionalExpressionOverReturn, ServicesVSResources.Prefer_conditional_expression_over_if_with_returns, s_preferConditionalExpressionOverIfWithReturns, s_preferConditionalExpressionOverIfWithReturns, Me, optionStore, expressionPreferencesGroupTitle)) Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferCompoundAssignment, ServicesVSResources.Prefer_compound_assignments, s_preferCompoundAssignments, s_preferCompoundAssignments, Me, optionStore, expressionPreferencesGroupTitle)) Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(VisualBasicCodeStyleOptions.PreferIsNotExpression, BasicVSResources.Prefer_IsNot_expression, s_preferIsNotExpression, s_preferIsNotExpression, Me, optionStore, expressionPreferencesGroupTitle)) + Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, BasicVSResources.Prefer_simplified_object_creation, s_preferSimplifiedObjectCreation, s_preferSimplifiedObjectCreation, Me, optionStore, expressionPreferencesGroupTitle)) AddUnusedValueOptions(optionStore, expressionPreferencesGroupTitle) diff --git a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx index fcb7042ca007c..a69f19d3134c8 100644 --- a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx +++ b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx @@ -128,7 +128,7 @@ Visual Basic Editor - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb b/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb index 73439c127261b..3883d6761b99a 100644 --- a/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb +++ b/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb @@ -14,6 +14,7 @@ Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.Venus Imports Microsoft.VisualStudio.Shell.Interop +Imports Microsoft.VisualStudio.Utilities Imports IVsTextBufferCoordinator = Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferCoordinator Imports VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan @@ -45,14 +46,16 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Venus pszUniqueMemberID As String, pszObjectName As String, pszNameOfEvent As String) As Integer Implements IVsContainedLanguageStaticEventBinding.AddStaticEventBinding - Me.ComponentModel.GetService(Of IWaitIndicator)().Wait( + Me.ComponentModel.GetService(Of IUIThreadOperationExecutor)().Execute( BasicVSResources.IntelliSense, - allowCancel:=False, + defaultDescription:="", + allowCancellation:=False, + showProgress:=False, action:=Sub(c) Dim visualStudioWorkspace = ComponentModel.GetService(Of VisualStudioWorkspace)() Dim document = GetThisDocument() ContainedLanguageStaticEventBinding.AddStaticEventBinding( - document, visualStudioWorkspace, pszClassName, pszUniqueMemberID, pszObjectName, pszNameOfEvent, c.CancellationToken) + document, visualStudioWorkspace, pszClassName, pszUniqueMemberID, pszObjectName, pszNameOfEvent, c.UserCancellationToken) End Sub) Return VSConstants.S_OK End Function @@ -100,12 +103,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Venus ppbstrDisplayNames As IntPtr, ppbstrMemberIDs As IntPtr) As Integer Implements IVsContainedLanguageStaticEventBinding.GetStaticEventBindingsForObject Dim members As Integer - Me.ComponentModel.GetService(Of IWaitIndicator)().Wait( + Me.ComponentModel.GetService(Of IUIThreadOperationExecutor)().Execute( BasicVSResources.IntelliSense, - allowCancel:=False, + defaultDescription:="", + allowCancellation:=False, + showProgress:=Nothing, action:=Sub(c) Dim eventNamesAndMemberNamesAndIds = ContainedLanguageStaticEventBinding.GetStaticEventBindings( - GetThisDocument(), pszClassName, pszObjectName, c.CancellationToken) + GetThisDocument(), pszClassName, pszObjectName, c.UserCancellationToken) members = eventNamesAndMemberNamesAndIds.Count() CreateBSTRArray(ppbstrEventNames, eventNamesAndMemberNamesAndIds.Select(Function(e) e.Item1)) CreateBSTRArray(ppbstrDisplayNames, eventNamesAndMemberNamesAndIds.Select(Function(e) e.Item2)) @@ -121,14 +126,16 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Venus pszObjectName As String, pszNameOfEvent As String) As Integer Implements IVsContainedLanguageStaticEventBinding.RemoveStaticEventBinding - Me.ComponentModel.GetService(Of IWaitIndicator)().Wait( + Me.ComponentModel.GetService(Of IUIThreadOperationExecutor)().Execute( BasicVSResources.IntelliSense, - allowCancel:=False, + defaultDescription:="", + allowCancellation:=False, + showProgress:=Nothing, action:=Sub(c) Dim visualStudioWorkspace = ComponentModel.GetService(Of VisualStudioWorkspace)() Dim document = GetThisDocument() ContainedLanguageStaticEventBinding.RemoveStaticEventBinding( - document, visualStudioWorkspace, pszClassName, pszUniqueMemberID, pszObjectName, pszNameOfEvent, c.CancellationToken) + document, visualStudioWorkspace, pszClassName, pszUniqueMemberID, pszObjectName, pszNameOfEvent, c.UserCancellationToken) End Sub) Return VSConstants.S_OK End Function diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf index 450745bd72c5b..36844fd622307 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ U kontrol rovnosti odkazů dávat přednost možnosti Is Nothing + + Prefer simplified object creation + Upřednostňovat zjednodušené vytváření objektů + + Remove unnecessary Imports - Remove unnecessary Imports + Odebrat nepotřebné direktivy Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Seřadit direktivy Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf index 5da45bb62ca8d..e23d308fbc41f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ "Is Nothing" für Verweisübereinstimmungsprüfungen vorziehen + + Prefer simplified object creation + Vereinfachte Objekterstellung bevorzugen + + Remove unnecessary Imports - Remove unnecessary Imports + Unnötige Import-Direktiven entfernen {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Importe sortieren {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf index 598030b04ce5b..a4dd3e8f7bef7 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Preferir “Is Nothing” para comprobaciones de igualdad de referencias + + Prefer simplified object creation + Preferir la creación de objetos simplificados + + Remove unnecessary Imports - Remove unnecessary Imports + Quitar instrucciones Import innecesarias {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Ordenar instrucciones Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf index 2b928f70613b3..dd32e06bf9344 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Préférer 'Is Nothing' pour les vérifications d'égalité de référence + + Prefer simplified object creation + Préférer la création d’objets simplifiée + + Remove unnecessary Imports - Remove unnecessary Imports + Supprimer les Imports inutiles {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Trier les Imports {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf index 8cddfa6355063..a63c58c0fecb2 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Preferisci 'Is Nothing' per i controlli di uguaglianza dei riferimenti + + Prefer simplified object creation + Preferire la creazione semplificata degli oggetti + + Remove unnecessary Imports - Remove unnecessary Imports + Rimuovi direttive Import non necessarie {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Ordina direttive Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf index 78148992afd11..ceb75717ac7a2 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ 参照の等値性のチェックには 'Is Nothing' を優先する + + Prefer simplified object creation + 簡略化されたオブジェクト作成を優先 + + Remove unnecessary Imports - Remove unnecessary Imports + 不要な Import を削除する {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + import を並べ替える {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf index cce625fd8aefa..680ecb945287f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ 참조 같음 검사에 대해 'Is Nothing' 선호 + + Prefer simplified object creation + 간소화된 개체 만들기 선호 + + Remove unnecessary Imports - Remove unnecessary Imports + 불필요한 Import 제거 {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + import 정렬 {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf index bee6fbe5e00b6..94bf7afb36818 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Preferuj wyrażenie „Is Nothing” w przypadku sprawdzeń odwołań pod kątem równości + + Prefer simplified object creation + Preferuj uproszczone tworzenie obiektów + + Remove unnecessary Imports - Remove unnecessary Imports + Usuń niepotrzebne dyrektywy Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Sortuj dyrektywy Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf index 4da14073a6592..45bef70a56f25 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Prefira 'Is Nothing' para as verificações de igualdade de referência + + Prefer simplified object creation + Preferir a criação simplificada de objetos + + Remove unnecessary Imports - Remove unnecessary Imports + Remover as Imports desnecessárias {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Classificar as imports {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf index ecb0eb581d2a4..7e5f298c12b83 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Предпочитать Is Nothing для проверок равенства ссылок + + Prefer simplified object creation + Предпочтение упрощенному созданию объектов + + Remove unnecessary Imports - Remove unnecessary Imports + Удалить ненужные импорты (Import) {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Сортировать импорты {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf index 279bb2bd26987..d50303f616a06 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ Başvuru eşitliği denetimleri için 'Is Nothing'i tercih et + + Prefer simplified object creation + Basitleştirilmiş nesne oluşturmayı tercih et + + Remove unnecessary Imports - Remove unnecessary Imports + Gereksiz Import'ları kaldır {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + Import'ları sırala {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf index 0d2bf0b6c91ca..b00721ad77196 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ 引用相等检查偏好 “Is Nothing” + + Prefer simplified object creation + 更偏好简化的对象创建 + + Remove unnecessary Imports - Remove unnecessary Imports + 删除不必要的 Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + 对 import 进行排序 {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf index f3263a3828c74..6099b9d3e23c0 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -52,9 +52,14 @@ 參考相等檢查最好使用 'Is Nothing' + + Prefer simplified object creation + 偏好簡化物件建立 + + Remove unnecessary Imports - Remove unnecessary Imports + 移除不必要的 Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized @@ -174,7 +179,7 @@ Sort imports - Sort imports + 排序 Import {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf index f1fc32c2ce5f3..f1301cc8ea921 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Zobrazovat vložené nápovědy;Automatické vložení koncových konstruktorů;Změnit nastavení přehledného výpisu;Změnit režim sbalení;Automatické vkládání členů Interface a MustOverride;Zobrazit nebo skrýt oddělovače řádků procedur;Zapnout nebo vypnout návrhy oprav;Zapnout nebo vypnout zvýrazňování odkazů a klíčových slov;Regex;Obarvit regulární výrazy;Zvýrazňovat související komponenty pod kurzorem;Nahlásit neplatné regulární výrazy;reg. výr.;regulární výraz;Používat rozšířené barvy;Barevné schéma editoru; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Zobrazovat vložené nápovědy;Automatické vložení koncových konstruktorů;Změnit nastavení přehledného výpisu;Změnit režim sbalení;Automatické vkládání členů Interface a MustOverride;Zobrazit nebo skrýt oddělovače řádků procedur;Zapnout nebo vypnout návrhy oprav;Zapnout nebo vypnout zvýrazňování odkazů a klíčových slov;Regex;Obarvit regulární výrazy;Zvýrazňovat související komponenty pod kurzorem;Nahlásit neplatné regulární výrazy;reg. výr.;regulární výraz;Používat rozšířené barvy;Barevné schéma editoru; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf index ea2b9181f9614..f545b1377989f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Inline-Hinweise anzeigen;Endkonstrukte automatisch einfügen;Einstellungen für automatische Strukturierung und Einrückung ändern;Gliederungsmodus ändern;Schnittstellen- und MustOverride-Member automatisch einfügen;Zeilentrennzeichen zwischen Prozeduren anzeigen oder ausblenden;Vorschläge für Fehlerkorrektur ein- oder ausschalten;Hervorhebung von Verweisen und Schlüsselwörtern ein- oder ausschalten;Regex;Reguläre Ausdrücke farbig markieren;Verwandte Komponenten unter Cursor hervorheben;Ungültige reguläre Ausdrücke melden;regex;regulärer Ausdruck;nErweiterte Farben verwenden;Editor-Farbschema; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Inline-Hinweise anzeigen;Endkonstrukte automatisch einfügen;Einstellungen für automatische Strukturierung und Einrückung ändern;Gliederungsmodus ändern;Schnittstellen- und MustOverride-Member automatisch einfügen;Zeilentrennzeichen zwischen Prozeduren anzeigen oder ausblenden;Vorschläge für Fehlerkorrektur ein- oder ausschalten;Hervorhebung von Verweisen und Schlüsselwörtern ein- oder ausschalten;Regex;Reguläre Ausdrücke farbig markieren;Verwandte Komponenten unter Cursor hervorheben;Ungültige reguläre Ausdrücke melden;regex;regulärer Ausdruck;nErweiterte Farben verwenden;Editor-Farbschema; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf index dafd2973a2002..65ad78eb34fa0 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Mostrar sugerencias insertadas;Inserción automática de construcciones End;Cambiar configuración de la lista descriptiva;Cambiar modo de esquematización;Inserción automática de miembros Interface y MustOverride;Mostrar u ocultar separadores de línea de procedimientos;Activar o desactivar sugerencias de corrección de errores;Activar o desactivar resaltado de referencias y palabras clave;Regex;Colorear expresiones regulares;Resaltar componentes relacionados bajo el cursor;Informar sobre expresiones regulares no válidas;regex;expresión regular;Usar colores mejorados;Combinación de colores del editor; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Mostrar sugerencias insertadas;Inserción automática de construcciones End;Cambiar configuración de la lista descriptiva;Cambiar modo de esquematización;Inserción automática de miembros Interface y MustOverride;Mostrar u ocultar separadores de línea de procedimientos;Activar o desactivar sugerencias de corrección de errores;Activar o desactivar resaltado de referencias y palabras clave;Regex;Colorear expresiones regulares;Resaltar componentes relacionados bajo el cursor;Informar sobre expresiones regulares no válidas;regex;expresión regular;Usar colores mejorados;Combinación de colores del editor; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf index 8af1fb613c47c..55d1ccfdb9da3 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Afficher les indicateurs inline;Insertion automatique des constructions de fin;Changer les paramètres de listing en mode Pretty;Changer le mode Plan;Insertion automatique des membres Interface et MustOverride;Afficher ou masquer les séparateurs de ligne de procédure;Activer ou désactiver les suggestions de correction d'erreurs;Activer ou désactiver la mise en surbrillance des références et des mots clés;Regex;Mettre en couleurs les expressions régulières;Mettre en surbrillance les composants connexes sous le curseur;Signaler les expressions régulières non valides;regex;expression régulière;Utiliser des couleurs améliorées;Modèle de couleurs de l'éditeur; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Afficher les indicateurs inline;Insertion automatique des constructions de fin;Changer les paramètres de listing en mode Pretty;Changer le mode Plan;Insertion automatique des membres Interface et MustOverride;Afficher ou masquer les séparateurs de ligne de procédure;Activer ou désactiver les suggestions de correction d'erreurs;Activer ou désactiver la mise en surbrillance des références et des mots clés;Regex;Mettre en couleurs les expressions régulières;Mettre en surbrillance les composants connexes sous le curseur;Signaler les expressions régulières non valides;regex;expression régulière;Utiliser des couleurs améliorées;Modèle de couleurs de l'éditeur; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf index f6cf37d00242d..a5f8592425d0d 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf @@ -1,4 +1,4 @@ - + @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Visualizza suggerimenti inline;Inserimento automatico di costrutti End;Modifica impostazioni di riformattazione;Modifica modalità struttura;Inserimento automatico di membri Interface e MustOverride;Mostra o nascondi separatori di riga routine;Attiva o disattiva i suggerimenti per la correzione degli errori;Attiva o disattiva l'evidenziazione di riferimenti e parole chiave;Regex;Colora espressioni regolari;Evidenzia i componenti correlati sotto il cursore;Segnala espressioni regolari non valide;regex;espressione regolare;Usa colori migliorati;Combinazione colori editor; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Visualizza suggerimenti inline;Inserimento automatico di costrutti End;Modifica impostazioni di riformattazione;Modifica modalità struttura;Inserimento automatico di membri Interface e MustOverride;Mostra o nascondi separatori di riga routine;Attiva o disattiva i suggerimenti per la correzione degli errori;Attiva o disattiva l'evidenziazione di riferimenti e parole chiave;Regex;Colora espressioni regolari;Evidenzia i componenti correlati sotto il cursore;Segnala espressioni regolari non valide;regex;espressione regolare;Usa colori migliorati;Combinazione colori editor; Advanced options page keywords @@ -89,7 +89,7 @@ Change completion list settings;Pre-select most recently used member - Modifica impostazioni di completamento elenco;Preseleziona membri usati più di recente + Modifica impostazioni dell'elenco di completamento;Preseleziona membri usati di recente IntelliSense options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf index 6c5a51b6074d4..ef4ff8eaf0481 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - インラインのヒントを表示する;コンストラクトの終端の自動挿入;再フォーマット設定の変更;アウトライン モードの変更;Interface と MustOverride メンバーの自動挿入;プロシージャ行の区切り記号の表示/非表示;エラー修正候補のオン/オフの切り替え;参照とキーワードの強調表示のオン/オフの切り替え;Regex;正規表現の色付け;カーソルの下の関連コンポーネントの強調表示;無効な正規表現の報告;Regex;正規表現;拡張された色を使用する;エディターの配色; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + インラインのヒントを表示する;コンストラクトの終端の自動挿入;再フォーマット設定の変更;アウトライン モードの変更;Interface と MustOverride メンバーの自動挿入;プロシージャ行の区切り記号の表示/非表示;エラー修正候補のオン/オフの切り替え;参照とキーワードの強調表示のオン/オフの切り替え;Regex;正規表現の色付け;カーソルの下の関連コンポーネントの強調表示;無効な正規表現の報告;Regex;正規表現;拡張された色を使用する;エディターの配色; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf index 1676ff6c48eed..995463caa041e 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - 인라인 힌트 표시; 맺음 구문 자동 삽입; 자동 서식 지정 설정 변경; 개요 모드 변경; 인터페이스 및 MustOverride 멤버 자동 삽입; 프로시저 줄 구분 기호 표시/숨기기; 오류 수정 제안 설정/해제; 참조 및 키워드 강조 표시 설정/해제; Regex; 정규식 색 지정; 커서 아래의 관련 구성 요소 강조 표시; 잘못된 정규식 보고; regex; 정규식; 향상된 색 사용; 편집기 색 구성표; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + 인라인 힌트 표시; 맺음 구문 자동 삽입; 자동 서식 지정 설정 변경; 개요 모드 변경; 인터페이스 및 MustOverride 멤버 자동 삽입; 프로시저 줄 구분 기호 표시/숨기기; 오류 수정 제안 설정/해제; 참조 및 키워드 강조 표시 설정/해제; Regex; 정규식 색 지정; 커서 아래의 관련 구성 요소 강조 표시; 잘못된 정규식 보고; regex; 정규식; 향상된 색 사용; 편집기 색 구성표; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf index 1e500657f2141..4d3ce46f9c81d 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Wyświetlaj wskazówki w tekście;Automatyczne wstawianie konstrukcji końcowych;Zmień ustawienia formatowania kodu;Zmień tryb konspektu;Automatyczne wstawianie składowych Interface i MustOverride;Pokaż lub ukryj separatory wierszy procedury;Włącz lub wyłącz sugestie dotyczące poprawy błędów;Włącz lub wyłącz wyróżnianie odwołań i słów kluczowych;Wyrażenie regularne;Koloruj wyrażenia regularne;Wyróżnij pokrewne składniki wskazane przez kursor;Zgłaszaj nieprawidłowe wyrażenia regularne;wyrażenie regularne;wyrażenie regularne;Użyj ulepszonych kolorów;Schemat kolorów edytora; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Wyświetlaj wskazówki w tekście;Automatyczne wstawianie konstrukcji końcowych;Zmień ustawienia formatowania kodu;Zmień tryb konspektu;Automatyczne wstawianie składowych Interface i MustOverride;Pokaż lub ukryj separatory wierszy procedury;Włącz lub wyłącz sugestie dotyczące poprawy błędów;Włącz lub wyłącz wyróżnianie odwołań i słów kluczowych;Wyrażenie regularne;Koloruj wyrażenia regularne;Wyróżnij pokrewne składniki wskazane przez kursor;Zgłaszaj nieprawidłowe wyrażenia regularne;wyrażenie regularne;wyrażenie regularne;Użyj ulepszonych kolorów;Schemat kolorów edytora; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf index 0f58644c134dc..4b0ae56e9e10a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Exibir as dicas embutidas;Inserção automática de constructos finais;Alterar as configurações de reformatação automática;Alterar o modo de estrutura de tópicos;Inserção automática dos membros Interface e MustOverride;Mostrar ou ocultar os separadores de linha de procedimento;Ativar ou desativar as sugestões para correção de erros;Ativar ou desativar o realce de referências e palavras-chave;Regex;Colorir as expressões regulares;Realçar os componentes relacionados sob o cursor;Relatar as expressões regulares inválidas;regex;expressão regular;Usar cores avançadas;Esquema de Cores do Editor; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Exibir as dicas embutidas;Inserção automática de constructos finais;Alterar as configurações de reformatação automática;Alterar o modo de estrutura de tópicos;Inserção automática dos membros Interface e MustOverride;Mostrar ou ocultar os separadores de linha de procedimento;Ativar ou desativar as sugestões para correção de erros;Ativar ou desativar o realce de referências e palavras-chave;Regex;Colorir as expressões regulares;Realçar os componentes relacionados sob o cursor;Relatar as expressões regulares inválidas;regex;expressão regular;Usar cores avançadas;Esquema de Cores do Editor; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf index 54fe830209e41..e2c1bf83adf60 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Отображать встроенные подсказки;Автоматическая вставка конечных конструкций;Изменение параметров автоматического форматирования;Изменение режима структуры;Автоматическая вставка интерфейса и элементов MustOverride;Отображение или скрытие разделителей строк для процедуры;Включение или отключение предложений об исправлении ошибок;Включение или отключение выделения ссылок и ключевых слов;Регулярные выражения;Выделение регулярных выражений цветом;Выделение связанных компонентов под курсором;Выделение недопустимых регулярных выражений;регулярное выражение;регулярное выражение;Использование расширенных цветов;Цветовая схема редактора; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Отображать встроенные подсказки;Автоматическая вставка конечных конструкций;Изменение параметров автоматического форматирования;Изменение режима структуры;Автоматическая вставка интерфейса и элементов MustOverride;Отображение или скрытие разделителей строк для процедуры;Включение или отключение предложений об исправлении ошибок;Включение или отключение выделения ссылок и ключевых слов;Регулярные выражения;Выделение регулярных выражений цветом;Выделение связанных компонентов под курсором;Выделение недопустимых регулярных выражений;регулярное выражение;регулярное выражение;Использование расширенных цветов;Цветовая схема редактора; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf index 9b61761bf5a82..602652766f332 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - Satır içi ipuçlarını göster;Bitiş yapılarının otomatik olarak eklenmesi;Düzgün listeleme ayarlarını değiştir;Ana hat modunu değiştir;Interface ve MustOverride üyelerinin otomatik olarak eklenmesi;Yordam satır ayıraçlarını göster veya gizle;Hata düzeltme önerilerini aç veya kapat;Başvuruları ve anahtar sözcükleri vurgulamayı aç veya kapat;Normal ifade;Normal ifadeleri renklendir;İmlecin altındaki ilgili bileşenleri vurgula;Geçersiz normal ifadeleri bildir;normal ifade;normal ifade;Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + Satır içi ipuçlarını göster;Bitiş yapılarının otomatik olarak eklenmesi;Düzgün listeleme ayarlarını değiştir;Ana hat modunu değiştir;Interface ve MustOverride üyelerinin otomatik olarak eklenmesi;Yordam satır ayıraçlarını göster veya gizle;Hata düzeltme önerilerini aç veya kapat;Başvuruları ve anahtar sözcükleri vurgulamayı aç veya kapat;Normal ifade;Normal ifadeleri renklendir;İmlecin altındaki ilgili bileşenleri vurgula;Geçersiz normal ifadeleri bildir;normal ifade;normal ifade;Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf index 2894e44902b73..da83604bde627 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - 显示内联提示;自动插入 End 构造;更改整齐排列设置;更改大纲模式;自动插入 Interface 和 MustOverride 成员;显示或隐藏过程行分隔符;打开或关闭错误纠正建议;打开或关闭引用和关键字的突出显示;正则表达式;对正则表达式着色;突出显示光标下的相关部分;报告无效正则表达式;regex;正则表达式;使用增强色;编辑器配色方案; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + 显示内联提示;自动插入 End 构造;更改整齐排列设置;更改大纲模式;自动插入 Interface 和 MustOverride 成员;显示或隐藏过程行分隔符;打开或关闭错误纠正建议;打开或关闭引用和关键字的突出显示;正则表达式;对正则表达式着色;突出显示光标下的相关部分;报告无效正则表达式;regex;正则表达式;使用增强色;编辑器配色方案; Advanced options page keywords diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf index 7219040ed6b51..dc7fa458309bf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf @@ -18,8 +18,8 @@ - Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; - 顯示內嵌提示;自動插入 End 建構;變更美化列表設定;變更大綱模式;自動插入 Interface 和 MustOverride 成員;顯示或隱藏程序行分隔符號;開啟或關閉錯誤修正建議;開啟或關閉參考及關鍵字的醒目提示;Regex;為規則運算式著色;醒目提示游標下的相關元件;回報無效的規則運算式;regex;規則運算式;使用進階色彩;編輯器色彩配置; + Underline reassigned variables;Display inline hints;Automatic insertion of end constructs;Change pretty listing settings;Change outlining mode;Automatic insertion of Interface and MustOverride members;Show or hide procedure line separators;Turn error correction suggestions on or off;Turn highlighting of references and keywords on or off;Regex;Colorize regular expressions;Highlight related components under cursor;Report invalid regular expressions;regex;regular expression;Use enhanced colors;Editor Color Scheme; + 顯示內嵌提示;自動插入 End 建構;變更美化列表設定;變更大綱模式;自動插入 Interface 和 MustOverride 成員;顯示或隱藏程序行分隔符號;開啟或關閉錯誤修正建議;開啟或關閉參考及關鍵字的醒目提示;Regex;為規則運算式著色;醒目提示游標下的相關元件;回報無效的規則運算式;regex;規則運算式;使用進階色彩;編輯器色彩配置; Advanced options page keywords diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCommitCharacters.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCommitCharacters.cs new file mode 100644 index 0000000000000..e75ae9073c2ae --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCommitCharacters.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.Collections.Immutable; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion +{ + public readonly struct XamlCommitCharacters + { + /// + /// Commit characters. + /// + public ImmutableArray Characters { get; } + + /// + /// Commit characters that will not be inserted when commit + /// + public ImmutableArray NonInsertCharacters { get; } + + private XamlCommitCharacters(ImmutableArray characters, ImmutableArray nonInsertCharacters) + { + Characters = characters; + NonInsertCharacters = nonInsertCharacters; + } + + public static XamlCommitCharacters Create(ImmutableArray characters, ImmutableArray nonInsertCharacters) + => new(characters, nonInsertCharacters); + + public static XamlCommitCharacters Create(ImmutableArray characters, params char[] nonInsertCharacters) + => new(characters, nonInsertCharacters?.ToImmutableArray() ?? ImmutableArray.Empty); + + public static XamlCommitCharacters Create(char[] characters, params char[] nonInsertCharacters) + => Create(characters.ToImmutableArray(), nonInsertCharacters); + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs index eef4527d759cc..ad73ad856d6f6 100644 --- a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs @@ -13,6 +13,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion internal class XamlCompletionItem { public string[] CommitCharacters { get; set; } + public XamlCommitCharacters? XamlCommitCharacters { get; set; } public string DisplayText { get; set; } public string InsertText { get; set; } public string Detail { get; set; } @@ -25,5 +26,6 @@ internal class XamlCompletionItem public ImageElement Icon { get; set; } public ISymbol Symbol { get; set; } public XamlEventDescription? EventDescription { get; set; } + public bool RetriggerCompletion { get; set; } } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlCapabilities.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlCapabilities.cs index b484da7f669fb..8a3aac47f76fa 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlCapabilities.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlCapabilities.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 System.Linq; using Microsoft.CodeAnalysis.Editor.Xaml; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler; +using RoslynCompletion = Microsoft.CodeAnalysis.Completion; namespace Microsoft.VisualStudio.LanguageServices.Xaml { @@ -15,7 +17,12 @@ internal static class XamlCapabilities /// public static VSServerCapabilities Current => new() { - CompletionProvider = new CompletionOptions { ResolveProvider = true, TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", ",", "(" } }, + CompletionProvider = new CompletionOptions + { + ResolveProvider = true, + TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", ",", "(" }, + AllCommitCharacters = RoslynCompletion.CompletionRules.Default.DefaultCommitCharacters.Select(c => c.ToString()).ToArray() + }, HoverProvider = true, FoldingRangeProvider = new FoldingRangeOptions { }, DocumentFormattingProvider = true, diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs index 66fb8ef7325a3..963f50ff8340e 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClient.cs @@ -49,7 +49,7 @@ public XamlInProcLanguageClient( /// public override string Name => "XAML Language Server Client (Experimental)"; - protected internal override VSServerCapabilities GetCapabilities() + public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) { var experimentationService = Workspace.Services.GetRequiredService(); var isLspExperimentEnabled = experimentationService.IsExperimentEnabled(StringConstants.EnableLspIntelliSense); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClientDisableUX.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClientDisableUX.cs index 89fb84fcbf811..b7c811068d6c1 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClientDisableUX.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlInProcLanguageClientDisableUX.cs @@ -51,7 +51,7 @@ public XamlInProcLanguageClientDisableUX( /// public override string Name => "XAML Language Server Client for LiveShare and Codespaces"; - protected internal override VSServerCapabilities GetCapabilities() + public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) { var experimentationService = Workspace.Services.GetRequiredService(); var isLspExperimentEnabled = experimentationService.IsExperimentEnabled(StringConstants.EnableLspIntelliSense); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs index e0335dbc7c163..99027afb1a00b 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Diagnostics; using System.Linq; @@ -29,6 +31,12 @@ internal class CompletionHandler : AbstractStatelessRequestHandler Methods.TextDocumentCompletionName; private const string CreateEventHandlerCommandTitle = "Create Event Handler"; + private static readonly Command s_retriggerCompletionCommand = new Command() + { + CommandIdentifier = StringConstants.RetriggerCompletionCommand, + Title = "Re-trigger completions" + }; + public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; @@ -42,6 +50,12 @@ public CompletionHandler() public override async Task HandleRequestAsync(CompletionParams request, RequestContext context, CancellationToken cancellationToken) { + if (request.Context is VSCompletionContext completionContext && completionContext.InvokeKind == VSCompletionInvokeKind.Deletion) + { + // Don't trigger completions on backspace. + return null; + } + var document = context.Document; if (document == null) { @@ -57,19 +71,20 @@ public CompletionHandler() return null; } + var commitCharactersCache = new Dictionary>(); return new VSCompletionList { - Items = completionResult.Completions.Select(c => CreateCompletionItem(c, document.Id, text, request.Position, request.TextDocument)).ToArray(), + Items = completionResult.Completions.Select(c => CreateCompletionItem(c, document.Id, text, request.Position, request.TextDocument, commitCharactersCache)).ToArray(), SuggestionMode = false, }; } - private static CompletionItem CreateCompletionItem(XamlCompletionItem xamlCompletion, DocumentId documentId, SourceText text, Position position, TextDocumentIdentifier textDocument) + private static CompletionItem CreateCompletionItem(XamlCompletionItem xamlCompletion, DocumentId documentId, SourceText text, Position position, TextDocumentIdentifier textDocument, Dictionary> commitCharactersCach) { var item = new VSCompletionItem { Label = xamlCompletion.DisplayText, - CommitCharacters = xamlCompletion.CommitCharacters, + VsCommitCharacters = GetCommitCharacters(xamlCompletion, commitCharactersCach), Detail = xamlCompletion.Detail, InsertText = xamlCompletion.InsertText, Preselect = xamlCompletion.Preselect.GetValueOrDefault(), @@ -99,10 +114,35 @@ private static CompletionItem CreateCompletionItem(XamlCompletionItem xamlComple Title = CreateEventHandlerCommandTitle }; } + else if (xamlCompletion.RetriggerCompletion) + { + // Retriger completion after commit + item.Command = s_retriggerCompletionCommand; + } return item; } + private static SumType GetCommitCharacters(XamlCompletionItem completionItem, Dictionary> commitCharactersCache) + { + if (!completionItem.XamlCommitCharacters.HasValue) + { + return completionItem.CommitCharacters; + } + + if (commitCharactersCache.TryGetValue(completionItem.Kind, out var cachedCharacters)) + { + // If we have already cached the commit characters, return the cached ones + return cachedCharacters.ToArray(); + } + + var xamlCommitCharacters = completionItem.XamlCommitCharacters.Value; + + var commitCharacters = xamlCommitCharacters.Characters.Select(c => new CommitCharacter { Character = c.ToString(), Insert = !xamlCommitCharacters.NonInsertCharacters.Contains(c) }).ToImmutableArray(); + commitCharactersCache.Add(completionItem.Kind, commitCharacters); + return commitCharacters.ToArray(); + } + private static CompletionItemKind GetItemKind(XamlCompletionKind kind) { switch (kind) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs index ee754c805c217..e8480ad7627f8 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs @@ -46,6 +46,11 @@ public CompletionResolveHandler() { Contract.ThrowIfNull(context.Solution); + if (completionItem is not VSCompletionItem vsCompletionItem) + { + return completionItem; + } + CompletionResolveData data; if (completionItem.Data is JToken token) { @@ -63,7 +68,7 @@ public CompletionResolveHandler() return completionItem; } - int offset = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(data.Position), cancellationToken).ConfigureAwait(false); + var offset = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(data.Position), cancellationToken).ConfigureAwait(false); var completionService = document.Project.LanguageServices.GetRequiredService(); var symbol = await completionService.GetSymbolAsync(new XamlCompletionContext(document, offset), completionItem.Label, cancellationToken: cancellationToken).ConfigureAwait(false); if (symbol == null) @@ -73,29 +78,8 @@ public CompletionResolveHandler() var description = await symbol.GetDescriptionAsync(document, cancellationToken).ConfigureAwait(false); - var vsCompletionItem = CloneVSCompletionItem(completionItem); vsCompletionItem.Description = new ClassifiedTextElement(description.Select(tp => new ClassifiedTextRun(tp.Tag.ToClassificationTypeName(), tp.Text))); return vsCompletionItem; } - - private static LSP.VSCompletionItem CloneVSCompletionItem(LSP.CompletionItem completionItem) - { - return new LSP.VSCompletionItem - { - 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/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs index f17c4d93ea997..ca1f02968f887 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -19,7 +19,7 @@ internal abstract class AbstractFormatDocumentHandlerBase false; public override bool RequiresLSPSolution => true; - protected async Task GetTextEditsAsync(LSP.TextDocumentIdentifier documentIdentifier, LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) + protected async Task GetTextEditsAsync(LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) { using var _ = ArrayBuilder.GetInstance(out var edits); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs index 8ccb5ab8986c2..41008fd3fbba0 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs @@ -28,6 +28,6 @@ public FormatDocumentHandler() public override LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.DocumentFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync(LSP.DocumentFormattingParams request, RequestContext context, CancellationToken cancellationToken) - => GetTextEditsAsync(request.TextDocument, request.Options, context, cancellationToken); + => GetTextEditsAsync(request.Options, context, cancellationToken); } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs index 2539d07c93e70..8bb993af21e32 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -50,7 +50,7 @@ public override async Task HandleRequestAsync(DocumentOnTypeFormatti { var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); var options = new XamlFormattingOptions { InsertSpaces = request.Options.InsertSpaces, TabSize = request.Options.TabSize, OtherOptions = request.Options.OtherOptions }; - IList? textChanges = await formattingService.GetFormattingChangesAsync(document, options, request.Character[0], position, cancellationToken).ConfigureAwait(false); + var textChanges = await formattingService.GetFormattingChangesAsync(document, options, request.Character[0], position, cancellationToken).ConfigureAwait(false); if (textChanges != null) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs index 5317b5cb61017..504c4b5665a36 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs @@ -28,6 +28,6 @@ public FormatDocumentRangeHandler() public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentRangeFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync(DocumentRangeFormattingParams request, RequestContext context, CancellationToken cancellationToken) - => GetTextEditsAsync(request.TextDocument, request.Options, context, cancellationToken, range: request.Range); + => GetTextEditsAsync(request.Options, context, cancellationToken, range: request.Range); } } diff --git a/src/VisualStudio/Xaml/Impl/StringConstants.cs b/src/VisualStudio/Xaml/Impl/StringConstants.cs index c39d0222bd8ee..0531a4961f6fe 100644 --- a/src/VisualStudio/Xaml/Impl/StringConstants.cs +++ b/src/VisualStudio/Xaml/Impl/StringConstants.cs @@ -15,5 +15,7 @@ internal static class StringConstants public const string EnableLspIntelliSense = "Xaml.EnableLspIntelliSense"; public const string CreateEventHandlerCommand = "Xaml.CreateEventHandler"; + + public const string RetriggerCompletionCommand = "editor.action.triggerSuggest"; } } diff --git a/src/Workspaces/CSharp/Portable/Classification/CSharpClassificationService.cs b/src/Workspaces/CSharp/Portable/Classification/CSharpClassificationService.cs index 03263dfd2bb28..a0bb3b2c1e896 100644 --- a/src/Workspaces/CSharp/Portable/Classification/CSharpClassificationService.cs +++ b/src/Workspaces/CSharp/Portable/Classification/CSharpClassificationService.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Composition; using System.Threading; using Microsoft.CodeAnalysis.Classification; @@ -22,12 +21,8 @@ public CSharpEditorClassificationService() { } - public override void AddLexicalClassifications(SourceText text, TextSpan textSpan, List result, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var temp); - ClassificationHelpers.AddLexicalClassifications(text, textSpan, temp, cancellationToken); - AddRange(temp, result); - } + public override void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) + => ClassificationHelpers.AddLexicalClassifications(text, textSpan, result, cancellationToken); public override ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan) => ClassificationHelpers.AdjustStaleClassification(text, classifiedSpan); diff --git a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs index 79bff73c599db..f96a666c8a024 100644 --- a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs +++ b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs @@ -301,6 +301,7 @@ private static bool IsVerbatimStringToken(SyntaxToken token) SyntaxKind.ClassDeclaration => ClassificationTypeNames.ClassName, SyntaxKind.RecordDeclaration => ClassificationTypeNames.RecordClassName, SyntaxKind.StructDeclaration => ClassificationTypeNames.StructName, + SyntaxKind.RecordStructDeclaration => ClassificationTypeNames.RecordStructName, _ => null }; @@ -354,6 +355,7 @@ private static bool IsExtensionMethod(MethodDeclarationSyntax methodDeclaration) SyntaxKind.StructDeclaration => ClassificationTypeNames.StructName, SyntaxKind.InterfaceDeclaration => ClassificationTypeNames.InterfaceName, SyntaxKind.RecordDeclaration => ClassificationTypeNames.RecordClassName, + SyntaxKind.RecordStructDeclaration => ClassificationTypeNames.RecordStructName, _ => null, }; diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs index 1ee5fb85b19f4..246b0b8597c71 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs @@ -47,8 +47,8 @@ public override ImmutableArray GetDefaultSyntaxClassifiers() public override void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) => ClassificationHelpers.AddLexicalClassifications(text, textSpan, result, cancellationToken); - public override void AddSyntacticClassifications(SyntaxTree syntaxTree, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) - => Worker.CollectClassifiedSpans(syntaxTree.GetRoot(cancellationToken), textSpan, result, cancellationToken); + public override void AddSyntacticClassifications(SyntaxNode root, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) + => Worker.CollectClassifiedSpans(root, textSpan, result, cancellationToken); public override ClassifiedSpan FixClassification(SourceText rawText, ClassifiedSpan classifiedSpan) => ClassificationHelpers.AdjustStaleClassification(rawText, classifiedSpan); diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpCodeGenerationHelpers.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpCodeGenerationHelpers.cs index 4bc3ec3d13544..f92f3e76186b4 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpCodeGenerationHelpers.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpCodeGenerationHelpers.cs @@ -72,11 +72,17 @@ internal static void AddAccessibilityModifiers( public static TypeDeclarationSyntax AddMembersTo( TypeDeclarationSyntax destination, SyntaxList members) { + var syntaxTree = destination.SyntaxTree; destination = ReplaceUnterminatedConstructs(destination); - return ConditionallyAddFormattingAnnotationTo( + var node = ConditionallyAddFormattingAnnotationTo( destination.EnsureOpenAndCloseBraceTokens().WithMembers(members), members); + + // Make sure the generated syntax node has same parse option. + // e.g. If add syntax member to a C# 5 destination, we should return a C# 5 syntax node. + var tree = node.SyntaxTree.WithRootAndOptions(node, syntaxTree.Options); + return (TypeDeclarationSyntax)tree.GetRoot(); } private static TypeDeclarationSyntax ReplaceUnterminatedConstructs(TypeDeclarationSyntax destination) diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs index 8d2ba875c3ba7..22366b11e111f 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs @@ -30,7 +30,9 @@ internal class CSharpDeclarationComparer : IComparer { SyntaxKind.InterfaceDeclaration, 11 }, { SyntaxKind.StructDeclaration, 12 }, { SyntaxKind.ClassDeclaration, 13 }, - { SyntaxKind.DelegateDeclaration, 14 } + { SyntaxKind.RecordDeclaration, 14 }, + { SyntaxKind.RecordStructDeclaration, 15 }, + { SyntaxKind.DelegateDeclaration, 16 } }; private static readonly Dictionary s_operatorPrecedenceMap = new(SyntaxFacts.EqualityComparer) @@ -118,7 +120,9 @@ public int Compare(SyntaxNode x, SyntaxNode y) case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: return Compare((BaseTypeDeclarationSyntax)x, (BaseTypeDeclarationSyntax)y); case SyntaxKind.ConversionOperatorDeclaration: @@ -358,7 +362,7 @@ private static int GetAccessibilityPrecedence(SyntaxTokenList modifiers, SyntaxN // All interface members are public return (int)Accessibility.Public; } - else if (node.Kind() == SyntaxKind.StructDeclaration || node.Kind() == SyntaxKind.ClassDeclaration) + else if (node.Kind() is SyntaxKind.StructDeclaration or SyntaxKind.ClassDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration) { // Members and nested types default to private return (int)Accessibility.Private; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 36f009884ee5c..b2f0ab1bfc4b1 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -150,6 +150,7 @@ private static SyntaxNode AsNamespaceMember(SyntaxNode declaration) case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return declaration; default: return null; @@ -1473,6 +1474,7 @@ private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) return s_interfaceModifiers; case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return s_structModifiers; case SyntaxKind.MethodDeclaration: @@ -1964,7 +1966,6 @@ private SyntaxNode AsNodeLike(SyntaxNode existingNode, SyntaxNode newNode) switch (this.GetDeclarationKind(existingNode)) { case DeclarationKind.Class: - case DeclarationKind.RecordClass: case DeclarationKind.Interface: case DeclarationKind.Struct: case DeclarationKind.Enum: diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs index 08baff5438364..e0cdd610ccf7b 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs @@ -104,6 +104,9 @@ private static RecordDeclarationSyntax GenerateRecordMembers( ImmutableArray members, CancellationToken cancellationToken) { + if (!options.GenerateMembers) + members = ImmutableArray.Empty; + // For a record, add record parameters if we have a primary constructor. var primaryConstructor = members.OfType().FirstOrDefault(m => CodeGenerationConstructorInfo.GetIsPrimaryConstructor(m)); if (primaryConstructor != null) @@ -114,8 +117,8 @@ private static RecordDeclarationSyntax GenerateRecordMembers( // remove the primary constructor from the list of members to generate. members = members.Remove(primaryConstructor); - // remove any properties that were created by the primary constructor - members = members.WhereAsArray(m => m is not IPropertySymbol property || !primaryConstructor.Parameters.Any(p => p.Name == property.Name)); + // remove any fields/properties that were created by the primary constructor + members = members.WhereAsArray(m => m is not IPropertySymbol and not IFieldSymbol || !primaryConstructor.Parameters.Any(p => p.Name == m.Name)); } // remove any implicit overrides to generate. @@ -123,20 +126,13 @@ private static RecordDeclarationSyntax GenerateRecordMembers( // If there are no members, just make a simple record with no body if (members.Length == 0) - { - recordDeclaration = recordDeclaration.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - } - else - { - // Otherwise, give the record a body. - recordDeclaration = recordDeclaration.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken)) - .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); - } - - if (options.GenerateMembers) - recordDeclaration = service.AddMembers(recordDeclaration, members, options, cancellationToken); + return recordDeclaration.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - return recordDeclaration; + // Otherwise, give the record a body and add the members to it. + recordDeclaration = recordDeclaration.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken)) + .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)) + .WithSemicolonToken(default); + return service.AddMembers(recordDeclaration, members, options, cancellationToken); } public static MemberDeclarationSyntax UpdateNamedTypeDeclaration( @@ -170,8 +166,10 @@ private static MemberDeclarationSyntax RemoveAllMembers(MemberDeclarationSyntax return ((EnumDeclarationSyntax)declaration).WithMembers(default); case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: return ((TypeDeclarationSyntax)declaration).WithMembers(default); default: @@ -196,7 +194,14 @@ private static MemberDeclarationSyntax GetDeclarationSyntaxWithoutMembersWorker( TypeDeclarationSyntax typeDeclaration; if (namedType.IsRecord) { - typeDeclaration = SyntaxFactory.RecordDeclaration(SyntaxFactory.Token(SyntaxKind.RecordKeyword), namedType.Name.ToIdentifierToken()); + var isRecordClass = namedType.TypeKind is TypeKind.Class; + var declarationKind = isRecordClass ? SyntaxKind.RecordDeclaration : SyntaxKind.RecordStructDeclaration; + var classOrStructKeyword = SyntaxFactory.Token(isRecordClass ? default : SyntaxKind.StructKeyword); + + typeDeclaration = SyntaxFactory.RecordDeclaration(kind: declarationKind, attributeLists: default, modifiers: default, + SyntaxFactory.Token(SyntaxKind.RecordKeyword), classOrStructKeyword, namedType.Name.ToIdentifierToken(), + typeParameterList: null, parameterList: null, baseList: null, constraintClauses: default, openBraceToken: default, members: default, closeBraceToken: default, + SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } else { diff --git a/src/Workspaces/CSharp/Portable/ExternalAccess/Pythia/Api/PythiaSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/ExternalAccess/Pythia/Api/PythiaSyntaxExtensions.cs index b494a7557f463..57a9b2f0ffe53 100644 --- a/src/Workspaces/CSharp/Portable/ExternalAccess/Pythia/Api/PythiaSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/ExternalAccess/Pythia/Api/PythiaSyntaxExtensions.cs @@ -22,7 +22,7 @@ public static SyntaxToken FindTokenOnLeftOfPosition(this SyntaxTree syntaxTree, public static bool IsFoundUnder(this SyntaxNode node, Func childGetter) where TParent : SyntaxNode => Shared.Extensions.SyntaxNodeExtensions.IsFoundUnder(node, childGetter); - public static SimpleNameSyntax GetRightmostName(this ExpressionSyntax node) + public static SimpleNameSyntax? GetRightmostName(this ExpressionSyntax node) => CSharp.Extensions.ExpressionSyntaxExtensions.GetRightmostName(node); public static bool IsInStaticContext(this SyntaxNode node) diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs index d3fa6a2825ce3..73116e2bd4528 100644 --- a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs @@ -166,6 +166,7 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod { case SyntaxKind.ClassDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: var typeDecl = (TypeDeclarationSyntax)node; @@ -182,6 +183,7 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Record, SyntaxKind.InterfaceDeclaration => DeclaredSymbolInfoKind.Interface, SyntaxKind.StructDeclaration => DeclaredSymbolInfoKind.Struct, + SyntaxKind.RecordStructDeclaration => DeclaredSymbolInfoKind.RecordStruct, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), }, GetAccessibility(typeDecl, typeDecl.Modifiers), @@ -461,7 +463,9 @@ private static Accessibility GetAccessibility(SyntaxNode node, SyntaxTokenList m switch (node.Parent.Kind()) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: // Anything without modifiers is private if it's in a class/struct declaration. return Accessibility.Private; case SyntaxKind.InterfaceDeclaration: diff --git a/src/Workspaces/CSharp/Portable/ReassignedVariable/CSharpReassignedVariableService.cs b/src/Workspaces/CSharp/Portable/ReassignedVariable/CSharpReassignedVariableService.cs new file mode 100644 index 0000000000000..ba1ee094aa9b8 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/ReassignedVariable/CSharpReassignedVariableService.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.ReassignedVariable; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.ReassignedVariable +{ + [ExportLanguageService(typeof(IReassignedVariableService), LanguageNames.CSharp), Shared] + internal class CSharpReassignedVariableService : AbstractReassignedVariableService< + ParameterSyntax, + VariableDeclaratorSyntax, + SingleVariableDesignationSyntax, + IdentifierNameSyntax> + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpReassignedVariableService() + { + } + + protected override SyntaxToken GetIdentifierOfVariable(VariableDeclaratorSyntax variable) + => variable.Identifier; + + protected override SyntaxToken GetIdentifierOfSingleVariableDesignation(SingleVariableDesignationSyntax variable) + => variable.Identifier; + + protected override bool HasInitializer(SyntaxNode variable) + => (variable as VariableDeclaratorSyntax)?.Initializer != null; + + protected override SyntaxNode GetMemberBlock(SyntaxNode methodOrPropertyDeclaration) + => methodOrPropertyDeclaration; + + protected override SyntaxNode GetParentScope(SyntaxNode localDeclaration) + { + var current = localDeclaration; + while (current != null) + { + if (current is BlockSyntax or SwitchSectionSyntax or ArrowExpressionClauseSyntax or AnonymousMethodExpressionSyntax or MemberDeclarationSyntax) + break; + + current = current.Parent; + } + + Contract.ThrowIfNull(current, "Couldn't find a suitable parent of this local declaration"); + return current is GlobalStatementSyntax + ? current.GetRequiredParent() + : current; + } + } +} diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs index 618972f1a79c3..c123818fa2c9e 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs @@ -109,9 +109,9 @@ private RecommendedSymbols GetSymbolsOffOfContainer() var node = _context.TargetToken.GetRequiredParent(); return node switch { - MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.SimpleMemberAccessExpression } memberAccess + MemberAccessExpressionSyntax(SyntaxKind.SimpleMemberAccessExpression) memberAccess => GetSymbolsOffOfExpression(memberAccess.Expression), - MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.PointerMemberAccessExpression } memberAccess + MemberAccessExpressionSyntax(SyntaxKind.PointerMemberAccessExpression) memberAccess => GetSymbolsOffOfDereferencedExpression(memberAccess.Expression), // This code should be executing only if the cursor is between two dots in a dotdot token. diff --git a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpParenthesizedExpressionReducer.cs b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpParenthesizedExpressionReducer.cs index f8508b7be5536..8e632075d40b4 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpParenthesizedExpressionReducer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpParenthesizedExpressionReducer.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.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -33,7 +31,7 @@ private static SyntaxNode SimplifyParentheses( OptionSet optionSet, CancellationToken cancellationToken) { - if (node.CanRemoveParentheses(semanticModel)) + if (node.CanRemoveParentheses(semanticModel, cancellationToken)) { // TODO(DustinCa): We should not be skipping elastic trivia below. // However, the formatter seems to mess up trailing trivia in some diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs index ca02f36c95ac4..a6457f3d5ab50 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs @@ -402,7 +402,7 @@ private static bool AccessMethodWithDynamicArgumentInsideStructConstructor(Membe { var constructor = memberAccess.Ancestors().OfType().SingleOrDefault(); - if (constructor == null || constructor.Parent.Kind() != SyntaxKind.StructDeclaration) + if (constructor == null || !constructor.Parent.IsKind(SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration)) { return false; } diff --git a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs index 6e78c2153f060..8635e1da7e696 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs @@ -971,7 +971,7 @@ public void TestConstructorDeclaration() Generator.ConstructorDeclaration("c", parameters: new[] { Generator.ParameterDeclaration("p", Generator.IdentifierName("t")) }, baseConstructorArguments: new[] { Generator.IdentifierName("p") }), - "c(t p): base(p)\r\n{\r\n}"); + "c(t p) : base(p)\r\n{\r\n}"); } [Fact] @@ -3023,11 +3023,13 @@ public void TestMultiFieldDeclarations() }"); } - [Fact, WorkItem(48789, "https://github.com/dotnet/roslyn/issues/48789")] - public void TestInsertMembersOnRecord_SemiColon() + [Theory, WorkItem(48789, "https://github.com/dotnet/roslyn/issues/48789")] + [InlineData("record")] + [InlineData("record class")] + public void TestInsertMembersOnRecord_SemiColon(string typeKind) { var comp = Compile( -@"public record C; +$@"public {typeKind} C; "); var symbolC = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("C").First(); @@ -3035,7 +3037,28 @@ public void TestInsertMembersOnRecord_SemiColon() VerifySyntax( Generator.InsertMembers(declC, 0, Generator.FieldDeclaration("A", Generator.IdentifierName("T"))), -@"public record C +$@"public {typeKind} C +{{ + T A; +}}"); + } + + [Fact, WorkItem(48789, "https://github.com/dotnet/roslyn/issues/48789")] + public void TestInsertMembersOnRecordStruct_SemiColon() + { + var src = +@"public record struct C; +"; + var comp = CSharpCompilation.Create("test") + .AddReferences(TestMetadata.Net451.mscorlib) + .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(src, options: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview))); + + var symbolC = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("C").First(); + var declC = Generator.GetDeclaration(symbolC.DeclaringSyntaxReferences.Select(x => x.GetSyntax()).First()); + + VerifySyntax( + Generator.InsertMembers(declC, 0, Generator.FieldDeclaration("A", Generator.IdentifierName("T"))), +@"public record struct C { T A; }"); diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index 7e20a750cb882..51c5d663b4cae 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -9860,5 +9860,118 @@ void Goo(R r) } }", changedOptionSet: changingOptions); } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + [WorkItem(52413, "https://github.com/dotnet/roslyn/issues/52413")] + public async Task NewLinesForBraces_PropertyPatternClauses_Default() + { + await AssertFormatAsync( + @" +class A +{ + public string Name { get; } + + public bool IsFoo(A a) + { + return a is + { + Name: ""foo"", + }; + } +}", + @" +class A +{ + public string Name { get; } + + public bool IsFoo(A a) + { + return a is { + Name: ""foo"", + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + [WorkItem(52413, "https://github.com/dotnet/roslyn/issues/52413")] + public async Task NewLinesForBraces_PropertyPatternClauses_NonDefault() + { + var changingOptions = new OptionsCollection(LanguageNames.CSharp) + { + { NewLinesForBracesInObjectCollectionArrayInitializers, false }, + }; + await AssertFormatAsync( + @" +class A +{ + public string Name { get; } + + public bool IsFoo(A a) + { + return a is { + Name: ""foo"", + }; + } +}", + @" +class A +{ + public string Name { get; } + + public bool IsFoo(A a) + { + return a is + { + Name: ""foo"", + }; + } +}", changedOptionSet: changingOptions); + } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.Formatting)] + [WorkItem(52413, "https://github.com/dotnet/roslyn/issues/52413")] + public async Task NewLinesForBraces_PropertyPatternClauses_SingleLine(bool option) + { + var changingOptions = new OptionsCollection(LanguageNames.CSharp) + { + { NewLinesForBracesInObjectCollectionArrayInitializers, option }, + }; + var code = @" +class A +{ + public string Name { get; } + + public bool IsFoo(A a) + { + return a is { Name: ""foo"" }; + } +}"; + await AssertFormatAsync(code, code, changedOptionSet: changingOptions); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task RecordClass() + { + await AssertFormatAsync( + @" +record class R(int X); +", + @" +record class R(int X); +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task RecordStruct() + { + await AssertFormatAsync( + @" +record struct R(int X); +", + @" +record struct R(int X); +"); + } } } diff --git a/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj b/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj index fbe9cb851a771..b3c60aaaf9d5f 100644 --- a/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj +++ b/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj @@ -4,11 +4,14 @@ Library Microsoft.CodeAnalysis - net472 + net472;netcoreapp3.1 true + + + true - + diff --git a/src/Workspaces/Core/Desktop/PublicAPI.Shipped.txt b/src/Workspaces/Core/Desktop/PublicAPI.Shipped.txt index 34f8ccc135fc3..88343d77fd037 100644 --- a/src/Workspaces/Core/Desktop/PublicAPI.Shipped.txt +++ b/src/Workspaces/Core/Desktop/PublicAPI.Shipped.txt @@ -1,4 +1,4 @@ -Microsoft.CodeAnalysis.CommandLineProject +Microsoft.CodeAnalysis.CommandLineProject (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) Microsoft.CodeAnalysis.FileTextLoader (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) Microsoft.CodeAnalysis.FileTextLoader.DefaultEncoding.get -> System.Text.Encoding (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) Microsoft.CodeAnalysis.FileTextLoader.FileTextLoader(string path, System.Text.Encoding defaultEncoding) -> void (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) @@ -8,8 +8,8 @@ Microsoft.CodeAnalysis.Host.Mef.MefV1HostServices Microsoft.CodeAnalysis.Host.Mef.MefV1HostServices.GetExports() -> System.Collections.Generic.IEnumerable> Microsoft.CodeAnalysis.Host.Mef.MefV1HostServices.GetExports() -> System.Collections.Generic.IEnumerable> override Microsoft.CodeAnalysis.FileTextLoader.LoadTextAndVersionAsync(Microsoft.CodeAnalysis.Workspace workspace, Microsoft.CodeAnalysis.DocumentId documentId, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) -static Microsoft.CodeAnalysis.CommandLineProject.CreateProjectInfo(string projectName, string language, string commandLine, string baseDirectory, Microsoft.CodeAnalysis.Workspace workspace = null) -> Microsoft.CodeAnalysis.ProjectInfo -static Microsoft.CodeAnalysis.CommandLineProject.CreateProjectInfo(string projectName, string language, System.Collections.Generic.IEnumerable commandLineArgs, string projectDirectory, Microsoft.CodeAnalysis.Workspace workspace = null) -> Microsoft.CodeAnalysis.ProjectInfo +static Microsoft.CodeAnalysis.CommandLineProject.CreateProjectInfo(string projectName, string language, string commandLine, string baseDirectory, Microsoft.CodeAnalysis.Workspace workspace = null) -> Microsoft.CodeAnalysis.ProjectInfo (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) +static Microsoft.CodeAnalysis.CommandLineProject.CreateProjectInfo(string projectName, string language, System.Collections.Generic.IEnumerable commandLineArgs, string projectDirectory, Microsoft.CodeAnalysis.Workspace workspace = null) -> Microsoft.CodeAnalysis.ProjectInfo (forwarded, contained in Microsoft.CodeAnalysis.Workspaces) static Microsoft.CodeAnalysis.Host.Mef.DesktopMefHostServices.DefaultAssemblies.get -> System.Collections.Immutable.ImmutableArray static Microsoft.CodeAnalysis.Host.Mef.DesktopMefHostServices.DefaultServices.get -> Microsoft.CodeAnalysis.Host.Mef.MefHostServices static Microsoft.CodeAnalysis.Host.Mef.MefV1HostServices.Create(System.Collections.Generic.IEnumerable assemblies) -> Microsoft.CodeAnalysis.Host.Mef.MefV1HostServices diff --git a/src/Workspaces/Core/Desktop/TypeForwarders.cs b/src/Workspaces/Core/Desktop/TypeForwarders.cs index 963cf555a129f..bd00d5063caa4 100644 --- a/src/Workspaces/Core/Desktop/TypeForwarders.cs +++ b/src/Workspaces/Core/Desktop/TypeForwarders.cs @@ -8,3 +8,6 @@ // Microsoft.CodeAnalysis.FileTextLoader has been moved to Microsoft.CodeAnalysis.Workspaces.dll. [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.FileTextLoader))] + +// Microsoft.CodeAnalysis.CommandLineProject has been moved to Microsoft.CodeAnalysis.Workspaces.dll. +[assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.CommandLineProject))] diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index 170235a8584c0..10e91ddd6ad76 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -268,8 +268,7 @@ public async Task OpenProjectAsync( #region Apply Changes public override bool CanApplyChange(ApplyChangesKind feature) { - return feature switch - { + return feature is ApplyChangesKind.ChangeDocument or ApplyChangesKind.AddDocument or ApplyChangesKind.RemoveDocument or @@ -278,9 +277,8 @@ ApplyChangesKind.RemoveMetadataReference or ApplyChangesKind.AddProjectReference or ApplyChangesKind.RemoveProjectReference or ApplyChangesKind.AddAnalyzerReference or - ApplyChangesKind.RemoveAnalyzerReference => true, - _ => false, - }; + ApplyChangesKind.RemoveAnalyzerReference or + ApplyChangesKind.ChangeAdditionalDocument; } private static bool HasProjectFileChanges(ProjectChanges changes) @@ -382,7 +380,24 @@ protected override void ApplyDocumentTextChanged(DocumentId documentId, SourceTe } } - private static Encoding? DetermineEncoding(SourceText text, Document document) + protected override void ApplyAdditionalDocumentTextChanged(DocumentId documentId, SourceText text) + { + var document = this.CurrentSolution.GetAdditionalDocument(documentId); + if (document != null) + { + var encoding = DetermineEncoding(text, document); + if (document.FilePath is null) + { + var message = string.Format(WorkspaceMSBuildResources.Path_for_additional_document_0_was_null, document.Name); + _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, message, document.Id)); + return; + } + this.SaveDocumentText(documentId, document.FilePath, text, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + this.OnAdditionalDocumentTextChanged(documentId, text, PreservationMode.PreserveValue); + } + } + + private static Encoding? DetermineEncoding(SourceText text, TextDocument document) { if (text.Encoding != null) { diff --git a/src/Workspaces/Core/MSBuild/WorkspaceMSBuildResources.resx b/src/Workspaces/Core/MSBuild/WorkspaceMSBuildResources.resx index f76282466b4fe..d19bb2f63b844 100644 --- a/src/Workspaces/Core/MSBuild/WorkspaceMSBuildResources.resx +++ b/src/Workspaces/Core/MSBuild/WorkspaceMSBuildResources.resx @@ -171,4 +171,7 @@ Unresolved metadata reference removed from project: {0} + + Path for additional document '{0}' was null} + \ No newline at end of file diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf index 47fd86022348a..38d68f7ab3c36 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Nepovedlo se načíst filtr řešení: {0} @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + Neplatné zadané {0}: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Parametr nemůže být null, prázdný nebo obsahovat prázdné znaky. + + + + Path for additional document '{0}' was null} + Cesta k dalšímu dokumentu {0} byla null} Path for document '{0}' was null - Path for document '{0}' was null + Cesta pro dokument {0} byla null. Project already added. - Project already added. + Projekt se už přidal. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + Projekt nemá cestu. Project path for '{0}' was null - Project path for '{0}' was null + Cesta projektu pro {0} byla null. Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Nedá se přidat odkaz na metadata {0}. Unable to find '{0}' - Unable to find '{0}' + Nedá se najít {0}. Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + Nedá se najít {0} pro {1}. Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Nedá se odebrat odkaz na metadata {0}. diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf index e22ee168a1cac..2b232c96f4c9d 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Fehler beim Laden des Projektmappenfilters: "{0}" @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + {0} wurde ungültig angegeben: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Der Parameter darf nicht NULL oder leer sein oder Leerzeichen enthalten. + + + + Path for additional document '{0}' was null} + Der Pfad für das Zusatzdokument '{0}' war nicht vorhanden}. Path for document '{0}' was null - Path for document '{0}' was null + Der Pfad für das Dokument "{0}" war NULL Project already added. - Project already added. + Projekt bereits hinzugefügt. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + Das Projekt hat keinen Pfad. Project path for '{0}' was null - Project path for '{0}' was null + Der Projektpfad für "{0}" war NULL Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Der Metadaten-Verweis "{0}" wurde nicht hinzugefügt Unable to find '{0}' - Unable to find '{0}' + "{0}" nicht gefunden Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + "{0}" für "{1}" wurde nicht gefunden Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Der Metadaten-Verweis "{0}" wurde nicht entfernt diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf index 66ec75c226558..84becf80a9d39 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + No se pudo cargar el filtro de la solución: "{0}" @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + {0} no válido especificado: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + El parámetro no puede ser nulo, estar vacío ni contener espacios en blanco. + + + + Path for additional document '{0}' was null} + Ruta de acceso del documento adicional "{0}" era null} Path for document '{0}' was null - Path for document '{0}' was null + La ruta de acceso del documento '{0}' era null Project already added. - Project already added. + El proyecto ya se agregó. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + El proyecto no tiene una ruta de acceso. Project path for '{0}' was null - Project path for '{0}' was null + La ruta de acceso del proyecto para '{0}' era null Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + No se puede agregar la referencia de metadatos '{0}' Unable to find '{0}' - Unable to find '{0}' + No se encuentra "{0}" Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + No se encuentra '{0}' para '{1}' Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + No se puede quitar la referencia de metadatos ' {0} ' diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf index c564c7848346c..f509943462f16 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Échec du chargement du filtre de solution : '{0}' @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + {0} non valide spécifié : {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Le paramètre ne peut pas être null, vide ou contenir d’espaces. + + + + Path for additional document '{0}' was null} + Chemin d’accès pour le document supplémentaire « {0} » était nul} Path for document '{0}' was null - Path for document '{0}' was null + Chemin d’accès du document '{0}' était null Project already added. - Project already added. + Le projet existe déjà. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + Le projet n’a pas de chemin d’accès. Project path for '{0}' was null - Project path for '{0}' was null + Le chemin d’accès du projet pour '{0}' était null Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Impossible d’ajouter une référence de métadonnées '{0}' Unable to find '{0}' - Unable to find '{0}' + '{0}' est introuvable Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + Impossible de trouver un '{0}' pour '{1}' Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Impossible de supprimer la référence de métadonnées '{0}' diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf index 8a8a2ec9f42f9..521ede0731d67 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Non è stato possibile caricare il filtro soluzione: '{0}' @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + {0} non valido specificato: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Il parametro non può essere null o vuoto, o contenere spazi vuoti. + + + + Path for additional document '{0}' was null} + Il percorso del documento aggiuntivo '{0}' è Null} Path for document '{0}' was null - Path for document '{0}' was null + Il percorso per il documento '{0}' è null Project already added. - Project already added. + Il progetto è già stato aggiunto. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + Il progetto non dispone di un percorso. Project path for '{0}' was null - Project path for '{0}' was null + Il percorso del progetto per '{0}' è null Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Non è possibile aggiungere il riferimento ai metadati '{0}' Unable to find '{0}' - Unable to find '{0}' + Non è possibile trovare '{0}' Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + Impossibile trovare un '{0}' per '{1}' Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Non è possibile rimuovere il riferimento ai metadati '{0}' diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf index ec55d7f199907..21645b332d7ee 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + ソリューション フィルター '{0}' を読み込めませんでした @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + 無効な {0} が指定されました: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + パラメーターを null、空、または空白にすることはできません。 + + + + Path for additional document '{0}' was null} + 追加文書 '{0}' のパスが null です} Path for document '{0}' was null - Path for document '{0}' was null + ドキュメント ' {0} ' のパスが null でした Project already added. - Project already added. + プロジェクトが既に追加されました。 @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + プロジェクトにパスが存在しません。 Project path for '{0}' was null - Project path for '{0}' was null + ' {0} ' のプロジェクト パスが null でした Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + メタデータ参照 "{0}" を追加できません。 Unable to find '{0}' - Unable to find '{0}' + '{0}' が見つかりません Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + '{1}' の ' {0} ' が見つかりません Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + メタデータ参照 ' {0} ' を削除できません diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf index 4d0b7b3b1fd22..3fd6d786b543c 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + 솔루션 필터 '{0}'을(를) 로드하지 못했습니다. @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + 잘못된 {0}이(가) 지정됨: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + 매개 변수는 null이거나 비어 있거나 공백을 포함할 수 없습니다. + + + + Path for additional document '{0}' was null} + 추가 문서 ‘{0}‘의 경로는 null} 입니다 Path for document '{0}' was null - Path for document '{0}' was null + 문서 '{0}'에 대한 경로가 null이었음 Project already added. - Project already added. + Project가 이미 추가되었습니다. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + 프로젝트에 경로가 없습니다. Project path for '{0}' was null - Project path for '{0}' was null + '{0}'에 대한 프로젝트 경로가 null이었음 Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + 메타데이터 참조 '{0}'을(를) 추가할 수 없음 Unable to find '{0}' - Unable to find '{0}' + {0}을(를) 찾을 수 없습니다. Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + '{1}'에 대한 '{0}'을(를) 찾을 수 없음 Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + 메타데이터 참조 '{0}'을(를) 제거할 수 없음 diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf index 961640ad6484e..f4c4d729fe109 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Nie można załadować filtru rozwiązań: „{0}” @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + Określono nieprawidłowy plik {0}” {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Parametr nie może mieć wartości null, być pusty ani zawierać białych znaków. + + + + Path for additional document '{0}' was null} + Ścieżka dla dodatkowego dokumentu "{0}" miała wartość null} Path for document '{0}' was null - Path for document '{0}' was null + Ścieżka dokumentu "{0}" miała wartość null Project already added. - Project already added. + Projekt już został dodany. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + Projekt nie ma ścieżki. Project path for '{0}' was null - Project path for '{0}' was null + Ścieżka projektu dla pliku "{0}" miała wartość null Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Nie można dodać odwołania do metadanych "{0}" Unable to find '{0}' - Unable to find '{0}' + Nie można znaleźć pliku "{0}" Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + Nie można znaleźć pliku "{0}"dla "{1}" Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Nie można usunąć odwołania do metadanych "{0}" diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf index c685bd385a3d8..862ba52cc16b5 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Falha ao carregar o filtro da solução: '{0}' @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + Inválido {0} especificado: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + O parâmetro não pode ser nulo, vazio ou conter espaços em branco. + + + + Path for additional document '{0}' was null} + O caminho para o documento adicional '{0}' era nulo} Path for document '{0}' was null - Path for document '{0}' was null + O caminho para o documento '{0}' era nulo Project already added. - Project already added. + Projeto já adicionado. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + O projeto não tem um caminho. Project path for '{0}' was null - Project path for '{0}' was null + O caminho do projeto para '{0}' era nulo Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Não foi possível adicionar a referência de metadados '{0}' Unable to find '{0}' - Unable to find '{0}' + Não é possível localizar {0} Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + Não foi possível encontrar um '{0}' para '{1}' Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Não foi possível remover a referência de metadados '{0}' diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf index af12a9cef5455..a9633e8c1d98e 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Не удалось загрузить фильтр решений: "{0}" @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + Указан недопустимый элемент {0}: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Параметр не может иметь значение NULL, быть пустым или содержать пробелы. + + + + Path for additional document '{0}' was null} + Путь к дополнительному документу "{0}" имел значение NULL} Path for document '{0}' was null - Path for document '{0}' was null + Путь для документа "{0}" имел значение NULL Project already added. - Project already added. + Проект уже добавлен. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + У проекта нет пути. Project path for '{0}' was null - Project path for '{0}' was null + Путь проекта для "{0}" имел значение NULL Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + Не удается добавить ссылку на метаданные "{0}" Unable to find '{0}' - Unable to find '{0}' + Невозможно найти "{0}" Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + Не удается найти "{0}" для "{1}" Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + Не удается удалить ссылку на метаданные "{0}" diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf index f8dcb7dedcf1f..16298c264670c 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + Çözüm filtresi yüklenemedi: '{0}' @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + Geçersiz {0} belirtildi: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + Parametre, null ve boş değeri alamaz veya boşluk içeremez. + + + + Path for additional document '{0}' was null} + '{0}' ek belgesinin yolu null değerini almıştı Path for document '{0}' was null - Path for document '{0}' was null + '{0}' belgesinin yolu null değerindeydi Project already added. - Project already added. + Proje zaten eklenmiş. @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + Proje yol içermiyor. Project path for '{0}' was null - Project path for '{0}' was null + '{0}' için proje yolu null değerindeydi Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + '{0}' meta veri başvurusu eklenemiyor Unable to find '{0}' - Unable to find '{0}' + '{0}' bulunamadı Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + '{1}' için '{0}' bulunamıyor Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + '{0}' meta veri başvurusu kaldırılamıyor diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf index 0777ffc288614..9d16bc39f13cd 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + 未能加载解决方案筛选器:“{0}” @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + 指定的 {0} 无效: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + 参数不能为 null、空或包含空格。 + + + + Path for additional document '{0}' was null} + 其他文件“{0}”的路径为 NULL} Path for document '{0}' was null - Path for document '{0}' was null + 文档 "{0}" 的路径为 null Project already added. - Project already added. + 项目已添加。 @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + 项目不包含路径。 Project path for '{0}' was null - Project path for '{0}' was null + "{0}" 的项目路径为 null Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + 无法添加元数据文件 '{0}' Unable to find '{0}' - Unable to find '{0}' + 找不到“{0}” Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + 找不到 '{1}' 的 {0} Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + 无法删除元数据引用 "{0}" diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf index 935a1cf6df929..91a682af402b4 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf @@ -1,10 +1,10 @@ - + Failed to load solution filter: '{0}' - Failed to load solution filter: '{0}' + 無法載入解決方案篩選: '{0}' @@ -14,7 +14,7 @@ Invalid {0} specified: {1} - Invalid {0} specified: {1} + 指定了無效的 {0}: {1} @@ -29,17 +29,22 @@ Parameter cannot be null, empty, or contain whitespace. - Parameter cannot be null, empty, or contain whitespace. + 參數不可為 null、空白或包含空白字元。 + + + + Path for additional document '{0}' was null} + 其他文件 '{0}' 的路徑為 null} Path for document '{0}' was null - Path for document '{0}' was null + 文件 '{0}' 的路徑為 null Project already added. - Project already added. + 專案已新增。 @@ -59,32 +64,32 @@ Project does not have a path. - Project does not have a path. + 專案沒有路徑。 Project path for '{0}' was null - Project path for '{0}' was null + '{0}' 的專案路徑為 null Unable to add metadata reference '{0}' - Unable to add metadata reference '{0}' + 無法新增中繼資料參照 '{0}' Unable to find '{0}' - Unable to find '{0}' + 找不到 '{0}' Unable to find a '{0}' for '{1}' - Unable to find a '{0}' for '{1}' + 找不到 '{1}' 的 {0} Unable to remove metadata reference '{0}' - Unable to remove metadata reference '{0}' + 無法移除中繼資料參照 '{0}' diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index 345cadc41cbad..d25611e3fcb6e 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -10,16 +11,16 @@ using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.ReassignedVariable; namespace Microsoft.CodeAnalysis.Classification { internal abstract class AbstractClassificationService : IClassificationService { - public abstract void AddLexicalClassifications(SourceText text, TextSpan textSpan, List result, CancellationToken cancellationToken); + public abstract void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); public abstract ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan); - public async Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken) + public async Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) { var classificationService = document.GetLanguageService(); if (classificationService == null) @@ -52,22 +53,20 @@ public async Task AddSemanticClassificationsAsync(Document document, TextSpan te // if the remote call fails do nothing (error has already been reported) if (classifiedSpans.HasValue) - { classifiedSpans.Value.Rehydrate(result); - } } else { - using var _ = ArrayBuilder.GetInstance(out var temp); await AddSemanticClassificationsInCurrentProcessAsync( - document, textSpan, temp, cancellationToken).ConfigureAwait(false); - AddRange(temp, result); + document, textSpan, result, cancellationToken).ConfigureAwait(false); } } - public static async Task AddSemanticClassificationsInCurrentProcessAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) + public static async Task AddSemanticClassificationsInCurrentProcessAsync( + Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) { var classificationService = document.GetRequiredLanguageService(); + var reassignedVariableService = document.GetRequiredLanguageService(); var extensionManager = document.Project.Solution.Workspace.Services.GetRequiredService(); var classifiers = classificationService.GetDefaultSyntaxClassifiers(); @@ -76,37 +75,34 @@ public static async Task AddSemanticClassificationsInCurrentProcessAsync(Documen var getTokenClassifiers = extensionManager.CreateTokenExtensionGetter(classifiers, c => c.SyntaxTokenKinds); await classificationService.AddSemanticClassificationsAsync(document, textSpan, getNodeClassifiers, getTokenClassifiers, result, cancellationToken).ConfigureAwait(false); + + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + var classifyReassignedVariables = options.GetOption(ClassificationOptions.ClassifyReassignedVariables); + if (classifyReassignedVariables) + { + var reassignedVariableSpans = await reassignedVariableService.GetLocationsAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + foreach (var span in reassignedVariableSpans) + result.Add(new ClassifiedSpan(span, ClassificationTypeNames.ReassignedVariable)); + } } - public async Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken) + public async Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) { - var classificationService = document.GetLanguageService(); - if (classificationService == null) - { - // When renaming a file's extension through VS when it's opened in editor, - // the content type might change and the content type changed event can be - // raised before the renaming propagate through VS workspace. As a result, - // the document we got (based on the buffer) could still be the one in the workspace - // before rename happened. This would cause us problem if the document is supported - // by workspace but not a roslyn language (e.g. xaml, F#, etc.), since none of the roslyn - // language services would be available. - // - // If this is the case, we will simply bail out. It's OK to ignore the request - // because when the buffer eventually get associated with the correct document in roslyn - // workspace, we will be invoked again. - // - // For example, if you open a xaml from from a WPF project in designer view, - // and then rename file extension from .xaml to .cs, then the document we received - // here would still belong to the special "-xaml" project. + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + AddSyntacticClassifications(document.Project.Solution.Workspace, root, textSpan, result, cancellationToken); + } + + public void AddSyntacticClassifications( + Workspace workspace, SyntaxNode? root, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken) + { + if (root == null) return; - } - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(syntaxTree); + var classificationService = workspace.Services.GetLanguageServices(root.Language).GetService(); + if (classificationService == null) + return; - using var _ = ArrayBuilder.GetInstance(out var temp); - classificationService.AddSyntacticClassifications(syntaxTree, textSpan, temp, cancellationToken); - AddRange(temp, result); + classificationService.AddSyntacticClassifications(root, textSpan, result, cancellationToken); } /// @@ -120,5 +116,14 @@ protected static void AddRange(ArrayBuilder temp, List ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) + => default; + + public TextChangeRange? ComputeSyntacticChangeRange(Workspace workspace, SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken) + { + var classificationService = workspace.Services.GetLanguageServices(oldRoot.Language).GetService(); + return classificationService?.ComputeSyntacticChangeRange(oldRoot, newRoot, timeout, cancellationToken); + } } } diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs b/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs index acfdc86125492..7d7e6bdbe3061 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs @@ -11,7 +11,7 @@ internal static class ClassificationExtensions { TypeKind.Class => type.IsRecord ? ClassificationTypeNames.RecordClassName : ClassificationTypeNames.ClassName, TypeKind.Module => ClassificationTypeNames.ModuleName, - TypeKind.Struct => ClassificationTypeNames.StructName, + TypeKind.Struct => type.IsRecord ? ClassificationTypeNames.RecordStructName : ClassificationTypeNames.StructName, TypeKind.Interface => ClassificationTypeNames.InterfaceName, TypeKind.Enum => ClassificationTypeNames.EnumName, TypeKind.Delegate => ClassificationTypeNames.DelegateName, diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs index cd41916f2b2a8..bd1166ebc8390 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs @@ -30,6 +30,7 @@ public static class ClassificationTypeNames public const string WhiteSpace = "whitespace"; public const string Text = "text"; + internal const string ReassignedVariable = "reassigned variable"; public const string StaticSymbol = "static symbol"; public const string PreprocessorText = "preprocessor text"; @@ -44,6 +45,7 @@ public static class ClassificationTypeNames public const string InterfaceName = "interface name"; public const string ModuleName = "module name"; public const string StructName = "struct name"; + public const string RecordStructName = "record struct name"; public const string TypeParameterName = "type parameter name"; public const string FieldName = "field name"; diff --git a/src/Workspaces/Core/Portable/Classification/Classifier.cs b/src/Workspaces/Core/Portable/Classification/Classifier.cs index dc1158f89c787..af87bfad02cbe 100644 --- a/src/Workspaces/Core/Portable/Classification/Classifier.cs +++ b/src/Workspaces/Core/Portable/Classification/Classifier.cs @@ -51,7 +51,8 @@ public static IEnumerable GetClassifiedSpans( using var _1 = ArrayBuilder.GetInstance(out var syntacticClassifications); using var _2 = ArrayBuilder.GetInstance(out var semanticClassifications); - service.AddSyntacticClassifications(semanticModel.SyntaxTree, textSpan, syntacticClassifications, cancellationToken); + var root = semanticModel.SyntaxTree.GetRoot(cancellationToken); + service.AddSyntacticClassifications(root, textSpan, syntacticClassifications, cancellationToken); service.AddSemanticClassifications(semanticModel, textSpan, workspace, getNodeClassifiers, getTokenClassifiers, semanticClassifications, cancellationToken); var allClassifications = new List(semanticClassifications.Where(s => s.TextSpan.OverlapsWith(textSpan))); diff --git a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs index 9ecd4e7a77752..dabd306252d7c 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs @@ -6,7 +6,6 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -37,45 +36,36 @@ public static async Task> GetClassifiedSpansAsync // name), we'll do a later merging step to get the final correct list of // classifications. For tagging, normally the editor handles this. But as // we're producing the list of Inlines ourselves, we have to handles this here. - var syntaxSpans = ListPool.Allocate(); - var semanticSpans = ListPool.Allocate(); - try - { - await classificationService.AddSyntacticClassificationsAsync(document, span, syntaxSpans, cancellationToken).ConfigureAwait(false); - await classificationService.AddSemanticClassificationsAsync(document, span, semanticSpans, cancellationToken).ConfigureAwait(false); - - // MergeClassifiedSpans will ultimately filter multiple classifications for the same - // span down to one. We know that additive classifications are there just to - // provide additional information about the true classification. We will remove - // additive ClassifiedSpans until we have support for additive classifications - // in classified spans. https://github.com/dotnet/roslyn/issues/32770 - RemoveAdditiveSpans(syntaxSpans); - RemoveAdditiveSpans(semanticSpans); - - var classifiedSpans = MergeClassifiedSpans(syntaxSpans, semanticSpans, span); - return classifiedSpans; - } - finally - { - ListPool.Free(syntaxSpans); - ListPool.Free(semanticSpans); - } + using var _1 = ArrayBuilder.GetInstance(out var syntaxSpans); + using var _2 = ArrayBuilder.GetInstance(out var semanticSpans); + + await classificationService.AddSyntacticClassificationsAsync(document, span, syntaxSpans, cancellationToken).ConfigureAwait(false); + await classificationService.AddSemanticClassificationsAsync(document, span, semanticSpans, cancellationToken).ConfigureAwait(false); + + // MergeClassifiedSpans will ultimately filter multiple classifications for the same + // span down to one. We know that additive classifications are there just to + // provide additional information about the true classification. We will remove + // additive ClassifiedSpans until we have support for additive classifications + // in classified spans. https://github.com/dotnet/roslyn/issues/32770 + RemoveAdditiveSpans(syntaxSpans); + RemoveAdditiveSpans(semanticSpans); + + var classifiedSpans = MergeClassifiedSpans(syntaxSpans, semanticSpans, span); + return classifiedSpans; } - private static void RemoveAdditiveSpans(List spans) + private static void RemoveAdditiveSpans(ArrayBuilder spans) { for (var i = spans.Count - 1; i >= 0; i--) { var span = spans[i]; if (ClassificationTypeNames.AdditiveTypeNames.Contains(span.ClassificationType)) - { spans.RemoveAt(i); - } } } private static ImmutableArray MergeClassifiedSpans( - List syntaxSpans, List semanticSpans, TextSpan widenedSpan) + ArrayBuilder syntaxSpans, ArrayBuilder semanticSpans, TextSpan widenedSpan) { // The spans produced by the language services may not be ordered // (indeed, this happens with semantic classification as different @@ -113,10 +103,10 @@ private static ImmutableArray MergeClassifiedSpans( return MergeParts(filledInSyntaxSpans, filledInSemanticSpans); } - private static void Order(List syntaxSpans) + private static void Order(ArrayBuilder syntaxSpans) => syntaxSpans.Sort((s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start); - private static void AdjustSpans(List spans, TextSpan widenedSpan) + private static void AdjustSpans(ArrayBuilder spans, TextSpan widenedSpan) { for (var i = 0; i < spans.Count; i++) { @@ -140,14 +130,13 @@ private static void AdjustSpans(List spans, TextSpan widenedSpan } } - var newSpan = new ClassifiedSpan(span.ClassificationType, - intersection ?? new TextSpan()); + var newSpan = new ClassifiedSpan(span.ClassificationType, intersection.GetValueOrDefault()); spans[i] = newSpan; } } public static void FillInClassifiedSpanGaps( - int startPosition, IEnumerable classifiedSpans, ArrayBuilder result) + int startPosition, ArrayBuilder classifiedSpans, ArrayBuilder result) { foreach (var span in classifiedSpans) { diff --git a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs index 0875e45e1012b..5fccedda88549 100644 --- a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs @@ -2,10 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Classification @@ -23,7 +24,13 @@ internal interface IClassificationService : ILanguageService /// (i.e. identifiers being classified as keywords). These incorrect results will be patched /// up when the lexical results are superseded by the calls to AddSyntacticClassifications. /// - void AddLexicalClassifications(SourceText text, TextSpan textSpan, List result, CancellationToken cancellationToken); + void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); + + /// + /// This method is optional and only should be implemented by languages that support + /// syntax. If the language does not support syntax, callers should use + /// instead. + void AddSyntacticClassifications(Workspace workspace, SyntaxNode root, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); /// /// Produce the classifications for the span of text specified. The syntax of the document @@ -31,7 +38,7 @@ internal interface IClassificationService : ILanguageService /// be used to determine if a piece of text that looks like a keyword should actually be /// considered an identifier in its current context. /// - Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken); + Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); /// /// Produce the classifications for the span of text specified. Semantics of the language @@ -39,7 +46,7 @@ internal interface IClassificationService : ILanguageService /// For example, semantic information can be used to determine if an identifier should be /// classified as a type, structure, or something else entirely. /// - Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, List result, CancellationToken cancellationToken); + Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); /// /// Adjust a classification from a previous version of text accordingly based on the current @@ -51,5 +58,33 @@ internal interface IClassificationService : ILanguageService /// syntactic and semantic classifications for this version later. /// ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan); + + /// + /// Determines the range of the documents that should be considered syntactically changed after an edit. In + /// language systems that can reuse major parts of a document after an edit, and which would not need to + /// recompute classifications for those reused parts, this can speed up processing on a host by not requiring + /// the host to reclassify all the source in view, but only the source that could have changed. + /// + /// If determining this is not possible, or potentially expensive, can be returned to + /// indicate that the entire document should be considered changed and should be syntactically reclassified. + /// + /// + /// Implementations should attempt to abide by the provided timeout as much as they can, returning the best + /// information available at that point. As this can be called in performance critical scenarios, it is better + /// to return quickly with potentially larger change span (including that of the full document) rather than + /// spend too much time computing a very precise result. + /// + /// + ValueTask ComputeSyntacticChangeRangeAsync( + Document oldDocument, Document newDocument, + TimeSpan timeout, CancellationToken cancellationToken); + + /// + /// This method is optional and only should be implemented by languages that support + /// syntax. If the language does not support syntax, callers should use + /// instead. + TextChangeRange? ComputeSyntacticChangeRange( + Workspace workspace, SyntaxNode oldRoot, SyntaxNode newRoot, + TimeSpan timeout, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs index 0b9286dfecd06..18aa98d0314be 100644 --- a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs @@ -68,7 +68,7 @@ private static SerializableClassifiedSpans Dehydrate(ImmutableArray classifiedSpans) + internal void Rehydrate(ArrayBuilder classifiedSpans) { Contract.ThrowIfNull(ClassificationTypes); Contract.ThrowIfNull(ClassificationTriples); diff --git a/src/Workspaces/Core/Portable/Classification/ReassignedVariableOptions.cs b/src/Workspaces/Core/Portable/Classification/ReassignedVariableOptions.cs new file mode 100644 index 0000000000000..5c3b09399c13e --- /dev/null +++ b/src/Workspaces/Core/Portable/Classification/ReassignedVariableOptions.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.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; + +namespace Microsoft.CodeAnalysis.Classification +{ + internal class ClassificationOptions + { + public static PerLanguageOption2 ClassifyReassignedVariables = + new PerLanguageOption2(nameof(ClassificationOptions), nameof(ClassifyReassignedVariables), defaultValue: false, + storageLocations: new RoamingProfileStorageLocation($"TextEditor.%LANGUAGE%.Specific.{nameof(ClassificationOptions)}.{nameof(ClassifyReassignedVariables)}")); + } + + [ExportOptionProvider, Shared] + internal class ClassificationOptionsProvider : IOptionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ClassificationOptionsProvider() + { + } + + public ImmutableArray Options { get; } = ImmutableArray.Create( + ClassificationOptions.ClassifyReassignedVariables); + } +} diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs index b3a7c98fe03aa..2d228dd7fa96f 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs @@ -22,7 +22,7 @@ protected AbstractSyntaxClassificationService() } public abstract void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); - public abstract void AddSyntacticClassifications(SyntaxTree syntaxTree, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); + public abstract void AddSyntacticClassifications(SyntaxNode root, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); public abstract ImmutableArray GetDefaultSyntaxClassifiers(); public abstract ClassifiedSpan FixClassification(SourceText text, ClassifiedSpan classifiedSpan); @@ -53,5 +53,8 @@ public void AddSemanticClassifications( { Worker.Classify(workspace, semanticModel, textSpan, result, getNodeClassifiers, getTokenClassifiers, cancellationToken); } + + public TextChangeRange? ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken) + => SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(oldRoot, newRoot, timeout, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs index a92ac4cd4253e..db831c1591a2e 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs @@ -17,16 +17,20 @@ internal interface ISyntaxClassificationService : ILanguageService { ImmutableArray GetDefaultSyntaxClassifiers(); + /// void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); - void AddSyntacticClassifications(SyntaxTree syntaxTree, + /// + void AddSyntacticClassifications( + SyntaxNode root, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); + /// Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, Func> getNodeClassifiers, @@ -34,6 +38,7 @@ Task AddSemanticClassificationsAsync(Document document, ArrayBuilder result, CancellationToken cancellationToken); + /// void AddSemanticClassifications( SemanticModel semanticModel, TextSpan textSpan, @@ -43,6 +48,11 @@ void AddSemanticClassifications( ArrayBuilder result, CancellationToken cancellationToken); + /// ClassifiedSpan FixClassification(SourceText text, ClassifiedSpan classifiedSpan); + + /// + TextChangeRange? ComputeSyntacticChangeRange( + SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs new file mode 100644 index 0000000000000..a9e3f5ad2a256 --- /dev/null +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -0,0 +1,246 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Classification +{ + /// + /// Computes a syntactic text change range that determines the range of a document that was changed by an edit. The + /// portions outside this change range are guaranteed to be syntactically identical (see ). This algorithm is intended to be fast. It is + /// technically linear in the number of nodes and tokens that may need to examined. However, in practice, it should + /// operate in sub-linear time as it will bail the moment tokens don't match, and it's able to skip over matching + /// nodes fully without examining the contents of those nodes. This is intended for consumers that want a + /// reasonably accurate change range computer, but do not want to spend an inordinate amount of time getting the + /// most accurate and minimal result possible. + /// + /// + /// This computation is not guaranteed to be minimal. It may return a range that includes parts that are unchanged. + /// This means it is also legal for the change range to just specify the entire file was changed. The quality of + /// results will depend on how well the parsers did with incremental parsing, and how much time is given to do the + /// comparison. In practice, for large files (i.e. 15kloc) with standard types of edits, this generally returns + /// results in around 50-100 usecs on a i7 3GHz desktop. + /// + /// This algorithm will respect the timeout provided to the best of abilities. If any information has been computed + /// when the timeout elapses, it will be returned. + /// + /// + internal static class SyntacticChangeRangeComputer + { + private static readonly ObjectPool> s_pool = new(() => new()); + + public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken) + { + if (oldRoot == newRoot) + return default; + + var stopwatch = SharedStopwatch.StartNew(); + + // We will be comparing the trees for two documents like so: + // + // -------------------------------------------- + // old: | | + // -------------------------------------------- + // + // --------------------------------------------------- + // new: | | + // --------------------------------------------------- + // + // (Note that `new` could be smaller or the same length as `old`, it makes no difference). + // + // The algorithm will sweep in from both sides, as long as the nodes and tokens it's touching on each side + // are 'identical' (i.e. are the exact same green node, and were thus reused over an incremental parse.). + // This will leave us with: + // + // -------------------------------------------- + // old: | CLW | | CRW | + // -------------------------------------------- + // | | \ \ + // --------------------------------------------------- + // new: | CLW | | CRW | + // --------------------------------------------------- + // + // Where CLW and CRW refer to the common-left-width and common-right-width respectively. The part in between + // this s the change range: + // + // -------------------------------------------- + // old: | |**********************| | + // -------------------------------------------- + // |**************************\ + // --------------------------------------------------- + // new: | |*****************************| | + // --------------------------------------------------- + // + // The Span changed will go from `[CLW, Old_Width - CRW)`, and the NewLength will be `New_Width - CLW - CRW` + + var commonLeftWidth = ComputeCommonLeftWidth(); + if (commonLeftWidth == null) + { + // The trees were effectively identical (even if the children were different). Return that there was no + // text change. + return default; + } + + // Only compute the right side if we have time for it. Otherwise, assume there is nothing in common there. + var commonRightWidth = 0; + if (stopwatch.Elapsed < timeout) + commonRightWidth = ComputeCommonRightWidth(); + + var oldRootWidth = oldRoot.FullWidth(); + var newRootWidth = newRoot.FullWidth(); + + Contract.ThrowIfTrue(commonLeftWidth > oldRootWidth); + Contract.ThrowIfTrue(commonLeftWidth > newRootWidth); + Contract.ThrowIfTrue(commonRightWidth > oldRootWidth); + Contract.ThrowIfTrue(commonRightWidth > newRootWidth); + + // it's possible for the common left/right to overlap. This can happen as some tokens + // in the parser have a true shared underlying state, so they may get consumed both on + // a leftward and rightward walk. Cap the right width so that it never overlaps hte left + // width in either the old or new tree. + commonRightWidth = Math.Min(commonRightWidth, oldRootWidth - commonLeftWidth.Value); + commonRightWidth = Math.Min(commonRightWidth, newRootWidth - commonLeftWidth.Value); + + return new TextChangeRange( + TextSpan.FromBounds(start: commonLeftWidth.Value, end: oldRootWidth - commonRightWidth), + newRootWidth - commonLeftWidth.Value - commonRightWidth); + + int? ComputeCommonLeftWidth() + { + using var leftOldStack = s_pool.GetPooledObject(); + using var leftNewStack = s_pool.GetPooledObject(); + + var oldStack = leftOldStack.Object; + var newStack = leftNewStack.Object; + + oldStack.Push(oldRoot); + newStack.Push(newRoot); + + while (oldStack.Count > 0 && newStack.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + var currentOld = oldStack.Pop(); + var currentNew = newStack.Pop(); + Contract.ThrowIfFalse(currentOld.FullSpan.Start == currentNew.FullSpan.Start); + + // If the two nodes/tokens were the same just skip past them. They're part of the common left width. + if (currentOld.IsIncrementallyIdenticalTo(currentNew)) + continue; + + // if we reached a token for either of these, then we can't break things down any further, and we hit + // the furthest point they are common. + if (currentOld.IsToken || currentNew.IsToken) + return currentOld.FullSpan.Start; + + // Similarly, if we've run out of time, just return what we've computed so far. It's not as accurate as + // we could be. But the caller wants the results asap. + if (stopwatch.Elapsed > timeout) + return currentOld.FullSpan.Start; + + // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the + // class, the class node would be new, but there might be many member nodes that were the same that we'd + // want to see and skip. Crumble the node and deal with its left side. + // + // Reverse so that we process the leftmost child first and walk left to right. + foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens().Reverse()) + oldStack.Push(nodeOrToken); + + foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens().Reverse()) + newStack.Push(nodeOrToken); + } + + // If we consumed all of 'new', then the length of the new doc is what we have in common. + if (oldStack.Count > 0) + return newRoot.FullSpan.Length; + + // If we consumed all of 'old', then the length of the old doc is what we have in common. + if (newStack.Count > 0) + return oldRoot.FullSpan.Length; + + // We consumed both stacks entirely. That means the trees were identical (though the root was different). Return null to signify no change to the doc. + return null; + } + + int ComputeCommonRightWidth() + { + using var rightOldStack = s_pool.GetPooledObject(); + using var rightNewStack = s_pool.GetPooledObject(); + + var oldStack = rightOldStack.Object; + var newStack = rightNewStack.Object; + + oldStack.Push(oldRoot); + newStack.Push(newRoot); + + while (oldStack.Count > 0 && newStack.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + var currentOld = oldStack.Pop(); + var currentNew = newStack.Pop(); + + // The width on the right we've moved past on both old/new should be the same. + Contract.ThrowIfFalse((oldRoot.FullSpan.End - currentOld.FullSpan.End) == + (newRoot.FullSpan.End - currentNew.FullSpan.End)); + + // If the two nodes/tokens were the same just skip past them. They're part of the common right width. + // Critically though, we can only skip past if this wasn't already something we consumed when determining + // the common left width. If this was common the left side, we can't consider it common to the right, + // otherwise we could end up with overlapping regions of commonality. + // + // This can occur in incremental settings when the similar tokens are written successsively. + // Because the parser can reuse underlying token data, it may end up with many incrementally + // identical tokens in a row. + if (currentOld.IsIncrementallyIdenticalTo(currentNew) && + currentOld.FullSpan.Start >= commonLeftWidth && + currentNew.FullSpan.Start >= commonLeftWidth) + { + continue; + } + + // if we reached a token for either of these, then we can't break things down any further, and we hit + // the furthest point they are common. + if (currentOld.IsToken || currentNew.IsToken) + return oldRoot.FullSpan.End - currentOld.FullSpan.End; + + // Similarly, if we've run out of time, just return what we've computed so far. It's not as accurate as + // we could be. But the caller wants the results asap. + if (stopwatch.Elapsed > timeout) + return oldRoot.FullSpan.End - currentOld.FullSpan.End; + + // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the + // class, the class node would be new, but there might be many member nodes following the edited node + // that were the same that we'd want to see and skip. Crumble the node and deal with its right side. + // + // Do not reverse the children. We want to process the rightmost child first and walk right to left. + foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens()) + oldStack.Push(nodeOrToken); + + foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens()) + newStack.Push(nodeOrToken); + } + + // If we consumed all of 'new', then the length of the new doc is what we have in common. + if (oldStack.Count > 0) + return newRoot.FullSpan.Length; + + // If we consumed all of 'old', then the length of the old doc is what we have in common. + if (newStack.Count > 0) + return oldRoot.FullSpan.Length; + + // We consumed both stacks entirely. That means the trees were identical (though the root was + // different). We should never get here. If we were the same, then walking from the left should have + // consumed everything and already bailed out. + throw ExceptionUtilities.Unreachable; + } + } + } +} diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 06256f7418270..4c47010ceceba 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -68,7 +68,7 @@ internal virtual ImmutableArray NestedCodeActions /// /// Gets custom tags for the CodeAction. /// - public ImmutableArray CustomTags { get; protected set; } = ImmutableArray.Empty; + internal ImmutableArray CustomTags { get; set; } = ImmutableArray.Empty; /// /// Used by the CodeFixService and CodeRefactoringService to add the Provider Name as a CustomTag. @@ -373,13 +373,10 @@ internal abstract class SimpleCodeAction : CodeAction { public SimpleCodeAction( string title, - string? equivalenceKey, - IEnumerable? customTags = null) + string? equivalenceKey) { Title = title; EquivalenceKey = equivalenceKey; - - CustomTags = customTags.ToImmutableArrayOrEmpty(); } public sealed override string Title { get; } @@ -392,9 +389,8 @@ public CodeActionWithNestedActions( string title, ImmutableArray nestedActions, bool isInlinable, - CodeActionPriority priority = CodeActionPriority.Medium, - IEnumerable? customTags = null) - : base(title, ComputeEquivalenceKey(nestedActions), customTags) + CodeActionPriority priority = CodeActionPriority.Medium) + : base(title, ComputeEquivalenceKey(nestedActions)) { Debug.Assert(nestedActions.Length > 0); NestedCodeActions = nestedActions; @@ -434,9 +430,8 @@ internal class DocumentChangeAction : SimpleCodeAction public DocumentChangeAction( string title, Func> createChangedDocument, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedDocument = createChangedDocument; } @@ -452,9 +447,8 @@ internal class SolutionChangeAction : SimpleCodeAction public SolutionChangeAction( string title, Func> createChangedSolution, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedSolution = createChangedSolution; } @@ -467,9 +461,8 @@ internal class NoChangeAction : SimpleCodeAction { public NoChangeAction( string title, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { } diff --git a/src/Workspaces/Core/Portable/CodeGeneration/AbstractCodeGenerationService.cs b/src/Workspaces/Core/Portable/CodeGeneration/AbstractCodeGenerationService.cs index 3e47648256741..0ddb16da72684 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/AbstractCodeGenerationService.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/AbstractCodeGenerationService.cs @@ -227,7 +227,7 @@ protected TDeclarationNode AddMembers( var filteredMembers = membersList.Where(m => !m.IsImplicitlyDeclared || m.IsTupleField()); return options.AutoInsertionLocation - ? AddMembersToAppropiateLocationInDestination(destination, filteredMembers, availableIndices, options, cancellationToken) + ? AddMembersToAppropriateLocationInDestination(destination, filteredMembers, availableIndices, options, cancellationToken) : AddMembersToEndOfDestination(destination, filteredMembers, options, cancellationToken); } @@ -262,7 +262,7 @@ private TDeclarationSyntax AddMembersToEndOfDestination( return this.AddMembers(destination, newMembers); } - private TDeclarationSyntax AddMembersToAppropiateLocationInDestination( + private TDeclarationSyntax AddMembersToAppropriateLocationInDestination( TDeclarationSyntax destination, IEnumerable members, IList? availableIndices, diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs index 65c372717317b..8012a0fe291d7 100644 --- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs +++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.CodeStyle { /// - public class CodeStyleOption : ICodeStyleOption, IEquatable> + public sealed class CodeStyleOption : ICodeStyleOption, IEquatable> { static CodeStyleOption() { @@ -32,7 +32,9 @@ public CodeStyleOption(T value, NotificationOption notification) public T Value { get => _codeStyleOptionImpl.Value; - set => _codeStyleOptionImpl.Value = value; + + [Obsolete("Modifying a CodeStyleOption is not supported.", error: true)] + set => throw new InvalidOperationException(); } bool IObjectWritable.ShouldReuseInSerialization => _codeStyleOptionImpl.ShouldReuseInSerialization; @@ -47,7 +49,9 @@ ICodeStyleOption ICodeStyleOption.AsCodeStyleOption() public NotificationOption Notification { get => (NotificationOption)_codeStyleOptionImpl.Notification; - set => _codeStyleOptionImpl.Notification = (NotificationOption2)(value ?? throw new ArgumentNullException(nameof(value))); + + [Obsolete("Modifying a CodeStyleOption is not supported.", error: true)] + set => throw new InvalidOperationException(); } internal CodeStyleOption2 UnderlyingOption => _codeStyleOptionImpl; diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption2_operators.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption2_operators.cs index 1cceed6580c9d..2e7879a923557 100644 --- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption2_operators.cs +++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption2_operators.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.CodeStyle { - internal partial class CodeStyleOption2 + internal sealed partial class CodeStyleOption2 { [return: NotNullIfNotNull("option")] public static explicit operator CodeStyleOption?(CodeStyleOption2? option) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs index 9b8bfbe32b678..771841341bef1 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs @@ -151,7 +151,16 @@ private void AddDiagnostics( switch (diagnostic.Location.Kind) { case LocationKind.ExternalFile: - // TODO: currently additional file location is not supported. + var diagnosticDocumentId = Project.GetDocumentForExternalLocation(diagnostic.Location); + if (diagnosticDocumentId != null) + { + AddDocumentDiagnostic(ref _lazyNonLocals, Project.GetRequiredTextDocument(diagnosticDocumentId), diagnostic); + } + else + { + AddOtherDiagnostic(DiagnosticData.Create(diagnostic, Project)); + } + break; case LocationKind.None: diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs index 2ca220aa80d4a..f5848ff4ca02f 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs @@ -250,6 +250,11 @@ public async Task ToDiagnosticAsync(Project project, CancellationTok var location = await DataLocation.ConvertLocationAsync(project, cancellationToken).ConfigureAwait(false); var additionalLocations = await AdditionalLocations.ConvertLocationsAsync(project, cancellationToken).ConfigureAwait(false); + return ToDiagnostic(location, additionalLocations); + } + + public Diagnostic ToDiagnostic(Location location, ImmutableArray additionalLocations) + { return Diagnostic.Create( Id, Category, Message, Severity, DefaultSeverity, IsEnabledByDefault, WarningLevel, IsSuppressed, Title, Description, HelpLink, diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs index 9440e3069355b..d743bf6688716 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.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.IO; using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -67,6 +68,9 @@ public DiagnosticDataLocation( int mappedEndLine = 0, int mappedEndColumn = 0) { + // If the original source location path is not available then mapped must be as well. + Contract.ThrowIfFalse(originalFilePath != null || mappedFilePath == null); + DocumentId = documentId; SourceSpan = sourceSpan; MappedFilePath = mappedFilePath; @@ -81,6 +85,8 @@ public DiagnosticDataLocation( OriginalEndColumn = originalEndColumn; } + public bool IsMapped => MappedFilePath != null; + internal DiagnosticDataLocation WithCalculatedSpan(TextSpan newSourceSpan) { Contract.ThrowIfTrue(SourceSpan.HasValue); @@ -92,5 +98,39 @@ internal DiagnosticDataLocation WithCalculatedSpan(TextSpan newSourceSpan) MappedFilePath, MappedStartLine, MappedStartColumn, MappedEndLine, MappedEndColumn); } + + internal FileLinePositionSpan GetFileLinePositionSpan() + { + var filePath = GetFilePath(); + if (filePath == null) + { + return default; + } + + return IsMapped ? + new(filePath, new(MappedStartLine, MappedStartColumn), new(MappedEndLine, MappedEndColumn)) : + new(filePath, new(OriginalStartLine, OriginalStartColumn), new(OriginalEndLine, OriginalEndColumn)); + } + + internal string? GetFilePath() + => GetFilePath(OriginalFilePath, MappedFilePath); + + internal static string? GetFilePath(string? original, string? mapped) + { + if (RoslynString.IsNullOrEmpty(mapped)) + { + return original; + } + + var combined = PathUtilities.CombinePaths(PathUtilities.GetDirectoryName(original), mapped); + try + { + return Path.GetFullPath(combined); + } + catch + { + return combined; + } + } } } diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs deleted file mode 100644 index 69c9dae08b888..0000000000000 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs +++ /dev/null @@ -1,368 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Workspaces.Diagnostics -{ - /// - /// DiagnosticData serializer - /// - internal readonly struct DiagnosticDataSerializer - { - // version of serialized format - private const int FormatVersion = 1; - - // version of analyzer that produced this data - public readonly VersionStamp AnalyzerVersion; - - // version of project this data belong to - public readonly VersionStamp Version; - - public DiagnosticDataSerializer(VersionStamp analyzerVersion, VersionStamp version) - { - AnalyzerVersion = analyzerVersion; - Version = version; - } - - public async Task SerializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, ImmutableArray items, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); - - using var stream = SerializableBytes.CreateWritableStream(); - - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) - { - WriteDiagnosticData(writer, items, cancellationToken); - } - - var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); - - stream.Position = 0; - - var writeTask = (textDocument != null) ? - textDocument is Document document ? - storage.WriteStreamAsync(document, key, stream, cancellationToken) : - storage.WriteStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), stream, cancellationToken) : - storage.WriteStreamAsync(project, key, stream, cancellationToken); - - return await writeTask.ConfigureAwait(false); - } - - private static string GetSerializationKeyForNonSourceDocument(TextDocument document, string key) - => document.Id + ";" + key; - - public async ValueTask> DeserializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); - - var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); - - var readTask = (textDocument != null) ? - textDocument is Document document ? - storage.ReadStreamAsync(document, key, cancellationToken) : - storage.ReadStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), cancellationToken) : - storage.ReadStreamAsync(project, key, cancellationToken); - - using var stream = await readTask.ConfigureAwait(false); - using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken); - - if (reader == null || - !TryReadDiagnosticData(reader, project, textDocument, cancellationToken, out var data)) - { - return default; - } - - return data; - } - - public void WriteDiagnosticData(ObjectWriter writer, ImmutableArray items, CancellationToken cancellationToken) - { - writer.WriteInt32(FormatVersion); - - AnalyzerVersion.WriteTo(writer); - Version.WriteTo(writer); - - writer.WriteInt32(items.Length); - - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); - - writer.WriteString(item.Id); - writer.WriteString(item.Category); - - writer.WriteString(item.Message); - writer.WriteString(item.ENUMessageForBingSearch); - writer.WriteString(item.Title); - writer.WriteString(item.Description); - writer.WriteString(item.HelpLink); - writer.WriteInt32((int)item.Severity); - writer.WriteInt32((int)item.DefaultSeverity); - writer.WriteBoolean(item.IsEnabledByDefault); - writer.WriteBoolean(item.IsSuppressed); - writer.WriteInt32(item.WarningLevel); - - // unused - writer.WriteInt32(0); - writer.WriteInt32(0); - - WriteLocation(writer, item.DataLocation); - WriteAdditionalLocations(writer, item.AdditionalLocations, cancellationToken); - - writer.WriteInt32(item.CustomTags.Length); - foreach (var tag in item.CustomTags) - writer.WriteString(tag); - - writer.WriteInt32(item.Properties.Count); - foreach (var property in item.Properties) - { - writer.WriteString(property.Key); - writer.WriteString(property.Value); - } - } - } - - private static void WriteAdditionalLocations(ObjectWriter writer, IReadOnlyCollection additionalLocations, CancellationToken cancellationToken) - { - writer.WriteInt32(additionalLocations.Count); - - foreach (var location in additionalLocations) - { - cancellationToken.ThrowIfCancellationRequested(); - WriteLocation(writer, location); - } - } - - private static void WriteLocation(ObjectWriter writer, DiagnosticDataLocation? item) - { - if (item == null) - { - writer.WriteBoolean(false); - return; - } - - writer.WriteBoolean(true); - - if (item.SourceSpan.HasValue) - { - writer.WriteBoolean(true); - writer.WriteInt32(item.SourceSpan.Value.Start); - writer.WriteInt32(item.SourceSpan.Value.Length); - } - else - { - writer.WriteBoolean(false); - } - - writer.WriteString(item.OriginalFilePath); - writer.WriteInt32(item.OriginalStartLine); - writer.WriteInt32(item.OriginalStartColumn); - writer.WriteInt32(item.OriginalEndLine); - writer.WriteInt32(item.OriginalEndColumn); - - writer.WriteString(item.MappedFilePath); - writer.WriteInt32(item.MappedStartLine); - writer.WriteInt32(item.MappedStartColumn); - writer.WriteInt32(item.MappedEndLine); - writer.WriteInt32(item.MappedEndColumn); - } - - public bool TryReadDiagnosticData( - ObjectReader reader, - Project project, - TextDocument? document, - CancellationToken cancellationToken, - out ImmutableArray data) - { - data = default; - - try - { - var format = reader.ReadInt32(); - if (format != FormatVersion) - { - return false; - } - - // saved data is for same analyzer of different version of dll - var analyzerVersion = VersionStamp.ReadFrom(reader); - if (analyzerVersion != AnalyzerVersion) - { - return false; - } - - var version = VersionStamp.ReadFrom(reader); - if (version != VersionStamp.Default && version != Version) - { - return false; - } - - data = ReadDiagnosticDataArray(reader, project, document, cancellationToken); - return true; - } - catch (EndOfStreamException) when (cancellationToken.IsCancellationRequested) - { - // The reader was closed due to a cancellation request while in the process of reading a value. Make - // sure to propagate the exception as cancellation and not an error. - cancellationToken.ThrowIfCancellationRequested(); - throw ExceptionUtilities.Unreachable; - } - catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex)) - { - return false; - } - } - - private static ImmutableArray ReadDiagnosticDataArray(ObjectReader reader, Project project, TextDocument? document, CancellationToken cancellationToken) - { - var count = reader.ReadInt32(); - if (count == 0) - { - return ImmutableArray.Empty; - } - - var builder = ArrayBuilder.GetInstance(count); - - for (var i = 0; i < count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - var id = reader.ReadString(); - var category = reader.ReadString(); - - var message = reader.ReadString(); - var messageFormat = reader.ReadString(); - var title = reader.ReadString(); - var description = reader.ReadString(); - var helpLink = reader.ReadString(); - var severity = (DiagnosticSeverity)reader.ReadInt32(); - var defaultSeverity = (DiagnosticSeverity)reader.ReadInt32(); - var isEnabledByDefault = reader.ReadBoolean(); - var isSuppressed = reader.ReadBoolean(); - var warningLevel = reader.ReadInt32(); - - // these fields are unused - the actual span is read in ReadLocation - _ = reader.ReadInt32(); - _ = reader.ReadInt32(); - - var location = ReadLocation(project, reader, document); - var additionalLocations = ReadAdditionalLocations(project, reader); - - var customTagsCount = reader.ReadInt32(); - var customTags = GetCustomTags(reader, customTagsCount); - - var propertiesCount = reader.ReadInt32(); - var properties = GetProperties(reader, propertiesCount); - - builder.Add(new DiagnosticData( - id: id, - category: category, - message: message, - enuMessageForBingSearch: messageFormat, - severity: severity, - defaultSeverity: defaultSeverity, - isEnabledByDefault: isEnabledByDefault, - warningLevel: warningLevel, - customTags: customTags, - properties: properties, - projectId: project.Id, - location: location, - additionalLocations: additionalLocations, - language: project.Language, - title: title, - description: description, - helpLink: helpLink, - isSuppressed: isSuppressed)); - } - - return builder.ToImmutableAndFree(); - } - - private static DiagnosticDataLocation? ReadLocation(Project project, ObjectReader reader, TextDocument? document) - { - var exists = reader.ReadBoolean(); - if (!exists) - { - return null; - } - - TextSpan? sourceSpan = null; - if (reader.ReadBoolean()) - { - sourceSpan = new TextSpan(reader.ReadInt32(), reader.ReadInt32()); - } - - var originalFile = reader.ReadString(); - var originalStartLine = reader.ReadInt32(); - var originalStartColumn = reader.ReadInt32(); - var originalEndLine = reader.ReadInt32(); - var originalEndColumn = reader.ReadInt32(); - - var mappedFile = reader.ReadString(); - var mappedStartLine = reader.ReadInt32(); - var mappedStartColumn = reader.ReadInt32(); - var mappedEndLine = reader.ReadInt32(); - var mappedEndColumn = reader.ReadInt32(); - - var documentId = document != null - ? document.Id - : project.Solution.GetDocumentIdsWithFilePath(originalFile).FirstOrDefault(documentId => documentId.ProjectId == project.Id); - - return new DiagnosticDataLocation(documentId, sourceSpan, - originalFile, originalStartLine, originalStartColumn, originalEndLine, originalEndColumn, - mappedFile, mappedStartLine, mappedStartColumn, mappedEndLine, mappedEndColumn); - } - - private static ImmutableArray ReadAdditionalLocations(Project project, ObjectReader reader) - { - var count = reader.ReadInt32(); - using var _ = ArrayBuilder.GetInstance(count, out var result); - for (var i = 0; i < count; i++) - result.AddIfNotNull(ReadLocation(project, reader, document: null)); - - return result.ToImmutable(); - } - - private static ImmutableDictionary GetProperties(ObjectReader reader, int count) - { - if (count > 0) - { - var properties = ImmutableDictionary.CreateBuilder(); - for (var i = 0; i < count; i++) - { - properties.Add(reader.ReadString(), reader.ReadString()); - } - - return properties.ToImmutable(); - } - - return ImmutableDictionary.Empty; - } - - private static ImmutableArray GetCustomTags(ObjectReader reader, int count) - { - using var _ = ArrayBuilder.GetInstance(count, out var tags); - for (var i = 0; i < count; i++) - tags.Add(reader.ReadString()); - - return tags.ToImmutable(); - } - } -} diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 6a024529de2db..46fca9bf07d12 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -48,25 +48,10 @@ public static async Task> ToDiagnosticsAsync(this IEn return result.ToImmutableAndFree(); } - public static async Task> ConvertLocationsAsync( - this IReadOnlyCollection locations, Project project, CancellationToken cancellationToken) - { - if (locations.Count == 0) - { - return SpecializedCollections.EmptyList(); - } - - var result = new List(); - foreach (var data in locations) - { - var location = await data.ConvertLocationAsync(project, cancellationToken).ConfigureAwait(false); - result.Add(location); - } - - return result; - } + public static ValueTask> ConvertLocationsAsync(this IReadOnlyCollection locations, Project project, CancellationToken cancellationToken) + => locations.SelectAsArrayAsync((location, project, cancellationToken) => location.ConvertLocationAsync(project, cancellationToken), project, cancellationToken); - public static async Task ConvertLocationAsync( + public static async ValueTask ConvertLocationAsync( this DiagnosticDataLocation? dataLocation, Project project, CancellationToken cancellationToken) { if (dataLocation?.DocumentId == null) diff --git a/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs b/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs index 3a1cf51596046..78b72e5c5264c 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/InternalDiagnosticsOptions.cs @@ -30,5 +30,8 @@ internal static class InternalDiagnosticsOptions public static readonly Option2 RazorDiagnosticMode = new(nameof(InternalDiagnosticsOptions), nameof(RazorDiagnosticMode), defaultValue: DiagnosticMode.Pull, storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "RazorDiagnosticMode")); + + public static readonly Option2 EnableFileLoggingForDiagnostics = new(nameof(InternalDiagnosticsOptions), nameof(EnableFileLoggingForDiagnostics), defaultValue: false, + storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "EnableFileLoggingForDiagnostics")); } } diff --git a/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs b/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs index 1f7b3dc181bfc..4ebca11c60f9e 100644 --- a/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs +++ b/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable +using System; +using System.ComponentModel; #if CODE_STYLE namespace Microsoft.CodeAnalysis.Internal.Editing @@ -10,11 +11,22 @@ namespace Microsoft.CodeAnalysis.Internal.Editing namespace Microsoft.CodeAnalysis.Editing #endif { + /// + /// This should contain only language-agnostic declarations. Things like record struct should fall under struct, etc. + /// public enum DeclarationKind { None, CompilationUnit, + + /// + /// Represents a class declaration, including record class declarations in C#. + /// Class, + + /// + /// Represents a struct declaration, including record struct declarations in C#. + /// Struct, Interface, Enum, @@ -37,10 +49,18 @@ public enum DeclarationKind Attribute, LambdaExpression, GetAccessor, + + /// + /// Represents set accessor declaration of a property, including init accessors in C#. + /// SetAccessor, + AddAccessor, RemoveAccessor, RaiseAccessor, + + [Obsolete($"This value is not used. Use {nameof(Class)} instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] RecordClass, } } diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs b/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs index b31d871818732..daf8f4e07e9ae 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs @@ -316,7 +316,10 @@ public ReplaceChange( public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator) { var current = root.GetCurrentNode(this.Node); - Contract.ThrowIfNull(current, $"GetCurrentNode returned null with the following node: {this.Node}"); + if (current is null) + { + Contract.Fail($"GetCurrentNode returned null with the following node: {this.Node}"); + } var newNode = _modifier(current, generator); newNode = _editor.ApplyTrackingToNewNode(newNode); @@ -342,7 +345,10 @@ public ReplaceWithCollectionChange( public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator) { var current = root.GetCurrentNode(this.Node); - Contract.ThrowIfNull(current, $"GetCurrentNode returned null with the following node: {this.Node}"); + if (current is null) + { + Contract.Fail($"GetCurrentNode returned null with the following node: {this.Node}"); + } var newNodes = _modifier(current, generator).ToList(); for (var i = 0; i < newNodes.Count; i++) @@ -375,7 +381,10 @@ public ReplaceChange( public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator) { var current = root.GetCurrentNode(this.Node); - Contract.ThrowIfNull(current, $"GetCurrentNode returned null with the following node: {this.Node}"); + if (current is null) + { + Contract.Fail($"GetCurrentNode returned null with the following node: {this.Node}"); + } var newNode = _modifier(current, generator, _argument); newNode = _editor.ApplyTrackingToNewNode(newNode); diff --git a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs index bd8a5eff7d1d8..881f40c78a723 100644 --- a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs +++ b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs @@ -35,8 +35,11 @@ internal static class WellKnownExperimentNames public const string OOPServerGC = "Roslyn.OOPServerGC"; public const string ImportsOnPasteDefaultEnabled = "Roslyn.ImportsOnPasteDefaultEnabled"; public const string LspTextSyncEnabled = "Roslyn.LspTextSyncEnabled"; + public const string SourceGeneratorsEnableOpeningInWorkspace = "Roslyn.SourceGeneratorsEnableOpeningInWorkspace"; public const string RemoveUnusedReferences = "Roslyn.RemoveUnusedReferences"; public const string LSPCompletion = "Roslyn.LSP.Completion"; + public const string CloudCache = "Roslyn.CloudCache"; public const string UnnamedSymbolCompletionDisabled = "Roslyn.UnnamedSymbolCompletionDisabled"; + public const string RazorLspEditorFeatureFlag = "Razor.LSP.Editor"; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs index 52acb4f38e8c3..ba9994fa62aed 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs @@ -31,6 +31,7 @@ internal enum DeclaredSymbolInfoKind : byte Module, Property, Record, + RecordStruct, Struct, } @@ -79,13 +80,13 @@ internal enum DeclaredSymbolInfoKind : byte [DataMember(Order = 5)] public ImmutableArray InheritanceNames { get; } - // Store the kind, accessibility, parameter-count, and type-parameter-count - // in a single int. Each gets 4 bits which is ample and gives us more space - // for flags in the future. + // Store the kind (5 bits), accessibility (4 bits), parameter-count (4 bits), and type-parameter-count (4 bits) + // in a single int. [DataMember(Order = 6)] private readonly uint _flags; private const uint Lower4BitMask = 0b1111; + private const uint Lower5BitMask = 0b11111; public DeclaredSymbolInfoKind Kind => GetKind(_flags); public Accessibility Accessibility => GetAccessibility(_flags); @@ -126,21 +127,22 @@ public static DeclaredSymbolInfo Create( ImmutableArray inheritanceNames, bool isNestedType = false, int parameterCount = 0, int typeParameterCount = 0) { - const uint MaxFlagValue = 0b1111; + const uint MaxFlagValue5 = 0b10000; + const uint MaxFlagValue4 = 0b1111; - Contract.ThrowIfTrue((uint)accessibility > MaxFlagValue); - Contract.ThrowIfTrue((uint)kind > MaxFlagValue); + Contract.ThrowIfTrue((uint)accessibility > MaxFlagValue4); + Contract.ThrowIfTrue((uint)kind > MaxFlagValue5); - parameterCount = Math.Min(parameterCount, (byte)MaxFlagValue); - typeParameterCount = Math.Min(typeParameterCount, (byte)MaxFlagValue); + parameterCount = Math.Min(parameterCount, (byte)MaxFlagValue4); + typeParameterCount = Math.Min(typeParameterCount, (byte)MaxFlagValue4); var flags = (uint)kind | - ((uint)accessibility << 4) | - ((uint)parameterCount << 8) | - ((uint)typeParameterCount << 12) | - ((isNestedType ? 1u : 0u) << 16) | - ((isPartial ? 1u : 0u) << 17); + ((uint)accessibility << 5) | + ((uint)parameterCount << 9) | + ((uint)typeParameterCount << 13) | + ((isNestedType ? 1u : 0u) << 17) | + ((isPartial ? 1u : 0u) << 18); #pragma warning disable CS0618 // Type or member is obsolete return new DeclaredSymbolInfo( @@ -159,22 +161,22 @@ public static DeclaredSymbolInfo Create( => name == null ? null : stringTable.Add(name); private static DeclaredSymbolInfoKind GetKind(uint flags) - => (DeclaredSymbolInfoKind)(flags & Lower4BitMask); + => (DeclaredSymbolInfoKind)(flags & Lower5BitMask); private static Accessibility GetAccessibility(uint flags) - => (Accessibility)((flags >> 4) & Lower4BitMask); + => (Accessibility)((flags >> 5) & Lower4BitMask); private static byte GetParameterCount(uint flags) - => (byte)((flags >> 8) & Lower4BitMask); + => (byte)((flags >> 9) & Lower4BitMask); private static byte GetTypeParameterCount(uint flags) - => (byte)((flags >> 12) & Lower4BitMask); + => (byte)((flags >> 13) & Lower4BitMask); private static bool GetIsNestedType(uint flags) - => ((flags >> 16) & 1) == 1; + => ((flags >> 17) & 1) == 1; private static bool GetIsPartial(uint flags) - => ((flags >> 17) & 1) == 1; + => ((flags >> 18) & 1) == 1; internal void WriteTo(ObjectWriter writer) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs index 86eae129a52ca..e306f901565b7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs @@ -29,7 +29,6 @@ private enum SearchKind private readonly Solution _solution; private readonly IStreamingFindLiteralReferencesProgress _progress; private readonly IStreamingProgressTracker _progressTracker; - private readonly CancellationToken _cancellationToken; private readonly object _value; private readonly string _stringValue; @@ -38,14 +37,12 @@ private enum SearchKind public FindLiteralsSearchEngine( Solution solution, - IStreamingFindLiteralReferencesProgress progress, object value, - CancellationToken cancellationToken) + IStreamingFindLiteralReferencesProgress progress, object value) { _solution = solution; _progress = progress; _progressTracker = progress.ProgressTracker; _value = value; - _cancellationToken = cancellationToken; switch (value) { @@ -75,89 +72,90 @@ public FindLiteralsSearchEngine( } } - public async Task FindReferencesAsync() + public async Task FindReferencesAsync(CancellationToken cancellationToken) { - await using var _ = await _progressTracker.AddSingleItemAsync().ConfigureAwait(false); + await using var _ = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); if (_searchKind != SearchKind.None) { - await FindReferencesWorkerAsync().ConfigureAwait(false); + await FindReferencesWorkerAsync(cancellationToken).ConfigureAwait(false); } } - private async Task FindReferencesWorkerAsync() + private async Task FindReferencesWorkerAsync(CancellationToken cancellationToken) { var count = _solution.Projects.SelectMany(p => p.DocumentIds).Count(); - await _progressTracker.AddItemsAsync(count).ConfigureAwait(false); + await _progressTracker.AddItemsAsync(count, cancellationToken).ConfigureAwait(false); foreach (var project in _solution.Projects) { - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); var documentTasks = new List(); - foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(_cancellationToken).ConfigureAwait(false)) + foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) { - documentTasks.Add(ProcessDocumentAsync(document)); + documentTasks.Add(ProcessDocumentAsync(document, cancellationToken)); } await Task.WhenAll(documentTasks).ConfigureAwait(false); } } - private async Task ProcessDocumentAsync(Document document) + private async Task ProcessDocumentAsync(Document document, CancellationToken cancellationToken) { try { - await ProcessDocumentWorkerAsync(document).ConfigureAwait(false); + await ProcessDocumentWorkerAsync(document, cancellationToken).ConfigureAwait(false); } finally { - await _progressTracker.ItemCompletedAsync().ConfigureAwait(false); + await _progressTracker.ItemCompletedAsync(cancellationToken).ConfigureAwait(false); } } - private async Task ProcessDocumentWorkerAsync(Document document) + private async Task ProcessDocumentWorkerAsync(Document document, CancellationToken cancellationToken) { var index = await SyntaxTreeIndex.GetIndexAsync( - document, _cancellationToken).ConfigureAwait(false); + document, cancellationToken).ConfigureAwait(false); if (_searchKind == SearchKind.StringLiterals) { if (index.ProbablyContainsStringValue(_stringValue)) { - await SearchDocumentAsync(document).ConfigureAwait(false); + await SearchDocumentAsync(document, cancellationToken).ConfigureAwait(false); } } else if (index.ProbablyContainsInt64Value(_longValue)) { - await SearchDocumentAsync(document).ConfigureAwait(false); + await SearchDocumentAsync(document, cancellationToken).ConfigureAwait(false); } } - private async Task SearchDocumentAsync(Document document) + private async Task SearchDocumentAsync(Document document, CancellationToken cancellationToken) { - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); var syntaxFacts = document.GetLanguageService(); - var root = await document.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var matches = ArrayBuilder.GetInstance(); - ProcessNode(syntaxFacts, root, matches); + ProcessNode(syntaxFacts, root, matches, cancellationToken); foreach (var token in matches) { - await _progress.OnReferenceFoundAsync(document, token.Span).ConfigureAwait(false); + await _progress.OnReferenceFoundAsync(document, token.Span, cancellationToken).ConfigureAwait(false); } } private void ProcessNode( - ISyntaxFactsService syntaxFacts, SyntaxNode node, ArrayBuilder matches) + ISyntaxFactsService syntaxFacts, SyntaxNode node, + ArrayBuilder matches, CancellationToken cancellationToken) { - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); foreach (var child in node.ChildNodesAndTokens()) { if (child.IsNode) { - ProcessNode(syntaxFacts, child.AsNode(), matches); + ProcessNode(syntaxFacts, child.AsNode(), matches, cancellationToken); } else { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs index ad870da410e8d..bb4d13878f077 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs @@ -14,8 +14,8 @@ namespace Microsoft.CodeAnalysis.FindSymbols.FindReferences { internal static partial class BaseTypeFinder { - public static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) - => FindBaseTypes(type).AddRange(type.AllInterfaces).CastArray(); + public static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) + => FindBaseTypes(type).AddRange(type.AllInterfaces); public static async ValueTask> FindOverriddenAndImplementedMembersAsync( ISymbol symbol, Solution solution, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs index 8141e0a29c731..8ad4e3aa063b2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs @@ -48,7 +48,7 @@ public static Task GetIndexAsync( private static async Task CreateIndexAsync(Project project, CancellationToken cancellationToken) { - var classesAndRecordsThatMayDeriveFromSystemObject = new MultiDictionary(); + var classesThatMayDeriveFromSystemObject = new MultiDictionary(); var valueTypes = new MultiDictionary(); var enums = new MultiDictionary(); var delegates = new MultiDictionary(); @@ -65,12 +65,13 @@ private static async Task CreateIndexAsync(Project project, Cancel { case DeclaredSymbolInfoKind.Class: case DeclaredSymbolInfoKind.Record: - classesAndRecordsThatMayDeriveFromSystemObject.Add(document, info); + classesThatMayDeriveFromSystemObject.Add(document, info); break; case DeclaredSymbolInfoKind.Enum: enums.Add(document, info); break; case DeclaredSymbolInfoKind.Struct: + case DeclaredSymbolInfoKind.RecordStruct: valueTypes.Add(document, info); break; case DeclaredSymbolInfoKind.Delegate: @@ -85,7 +86,7 @@ private static async Task CreateIndexAsync(Project project, Cancel } } - return new ProjectIndex(classesAndRecordsThatMayDeriveFromSystemObject, valueTypes, enums, delegates, namedTypes); + return new ProjectIndex(classesThatMayDeriveFromSystemObject, valueTypes, enums, delegates, namedTypes); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 17a89c1b595d0..69f4306df840d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -17,7 +16,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using ProjectToDocumentMap = Dictionary>>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { @@ -26,7 +25,6 @@ internal partial class FindReferencesSearchEngine private readonly ImmutableArray _finders; private readonly IStreamingProgressTracker _progressTracker; private readonly IStreamingFindReferencesProgress _progress; - private readonly CancellationToken _cancellationToken; private readonly FindReferencesSearchOptions _options; /// @@ -41,14 +39,12 @@ public FindReferencesSearchEngine( IImmutableSet? documents, ImmutableArray finders, IStreamingFindReferencesProgress progress, - FindReferencesSearchOptions options, - CancellationToken cancellationToken) + FindReferencesSearchOptions options) { _documents = documents; _solution = solution; _finders = finders; _progress = progress; - _cancellationToken = cancellationToken; _options = options; _progressTracker = progress.ProgressTracker; @@ -60,31 +56,32 @@ public FindReferencesSearchEngine( _scheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler; } - public async Task FindReferencesAsync(ISymbol symbol) + public async Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationToken) { - await _progress.OnStartedAsync().ConfigureAwait(false); + await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - await using var _ = await _progressTracker.AddSingleItemAsync().ConfigureAwait(false); + await using var _ = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); // For the starting symbol, always cascade up and down the inheritance hierarchy. - var symbols = await DetermineAllSymbolsAsync(symbol, FindReferencesCascadeDirection.UpAndDown).ConfigureAwait(false); + var symbols = await DetermineAllSymbolsAsync( + symbol, FindReferencesCascadeDirection.UpAndDown, cancellationToken).ConfigureAwait(false); - var projectMap = await CreateProjectMapAsync(symbols).ConfigureAwait(false); - var projectToDocumentMap = await CreateProjectToDocumentMapAsync(projectMap).ConfigureAwait(false); + var projectMap = await CreateProjectMapAsync(symbols, cancellationToken).ConfigureAwait(false); + var projectToDocumentMap = await CreateProjectToDocumentMapAsync(projectMap, cancellationToken).ConfigureAwait(false); ValidateProjectToDocumentMap(projectToDocumentMap); - await ProcessAsync(projectToDocumentMap).ConfigureAwait(false); + await ProcessAsync(projectToDocumentMap, cancellationToken).ConfigureAwait(false); } finally { - await _progress.OnCompletedAsync().ConfigureAwait(false); + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } } - private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap) + private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_ProcessAsync, _cancellationToken)) + using (Logger.LogBlock(FunctionId.FindReference_ProcessAsync, cancellationToken)) { // quick exit if (projectToDocumentMap.Count == 0) @@ -96,12 +93,12 @@ private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap) // We'll mark the item as completed in "ProcessDocumentAsync". var totalFindCount = projectToDocumentMap.Sum( kvp1 => kvp1.Value.Sum(kvp2 => kvp2.Value.Count)); - await _progressTracker.AddItemsAsync(totalFindCount).ConfigureAwait(false); + await _progressTracker.AddItemsAsync(totalFindCount, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var (project, documentMap) in projectToDocumentMap) - tasks.Add(Task.Factory.StartNew(() => ProcessProjectAsync(project, documentMap), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + tasks.Add(Task.Factory.StartNew(() => ProcessProjectAsync(project, documentMap, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -111,7 +108,7 @@ private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap) private static void ValidateProjectToDocumentMap( ProjectToDocumentMap projectToDocumentMap) { - var set = new HashSet<(ISymbol symbol, IReferenceFinder finder)>(); + var set = new HashSet<(SymbolGroup group, ISymbol symbol, IReferenceFinder finder)>(); foreach (var documentMap in projectToDocumentMap.Values) { @@ -119,15 +116,13 @@ private static void ValidateProjectToDocumentMap( { set.Clear(); - foreach (var finder in documentToFinderList.Value) - { - Debug.Assert(set.Add(finder)); - } + foreach (var tuple in documentToFinderList.Value) + Debug.Assert(set.Add(tuple)); } } } - private ValueTask HandleLocationAsync(ISymbol symbol, ReferenceLocation location) - => _progress.OnReferenceFoundAsync(symbol, location); + private ValueTask HandleLocationAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + => _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs index 2c3ce2468a163..9ea4179095976 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; @@ -15,28 +16,27 @@ internal partial class FindReferencesSearchEngine { private async Task ProcessDocumentQueueAsync( Document document, - HashSet<(ISymbol symbol, IReferenceFinder finder)> documentQueue) + HashSet<(SymbolGroup group, ISymbol symbol, IReferenceFinder finder)> documentQueue, + CancellationToken cancellationToken) { - await _progress.OnFindInDocumentStartedAsync(document).ConfigureAwait(false); + await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); SemanticModel? model = null; try { - model = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false); + model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); // start cache for this semantic model FindReferenceCache.Start(model); - foreach (var (symbol, finder) in documentQueue) - { - await ProcessDocumentAsync(document, model, symbol, finder).ConfigureAwait(false); - } + foreach (var (group, symbol, finder) in documentQueue) + await ProcessDocumentAsync(document, model, group, symbol, finder, cancellationToken).ConfigureAwait(false); } finally { FindReferenceCache.Stop(model); - await _progress.OnFindInDocumentCompletedAsync(document).ConfigureAwait(false); + await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); } } @@ -48,23 +48,25 @@ private async Task ProcessDocumentQueueAsync( private async Task ProcessDocumentAsync( Document document, SemanticModel semanticModel, + SymbolGroup group, ISymbol symbol, - IReferenceFinder finder) + IReferenceFinder finder, + CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_ProcessDocumentAsync, s_logDocument, document, symbol, _cancellationToken)) + using (Logger.LogBlock(FunctionId.FindReference_ProcessDocumentAsync, s_logDocument, document, symbol, cancellationToken)) { try { var references = await finder.FindReferencesInDocumentAsync( - symbol, document, semanticModel, _options, _cancellationToken).ConfigureAwait(false); + symbol, document, semanticModel, _options, cancellationToken).ConfigureAwait(false); foreach (var (_, location) in references) { - await HandleLocationAsync(symbol, location).ConfigureAwait(false); + await HandleLocationAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); } } finally { - await _progressTracker.ItemCompletedAsync().ConfigureAwait(false); + await _progressTracker.ItemCompletedAsync(cancellationToken).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 1525056574707..47010180cdfcb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -16,38 +17,38 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = Dictionary>; - using ProjectMap = Dictionary>; - using ProjectToDocumentMap = Dictionary>>; + using DocumentMap = Dictionary>; + using ProjectMap = Dictionary>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { private static readonly Func s_createDocumentMap = _ => new DocumentMap(); - private async Task CreateProjectToDocumentMapAsync(ProjectMap projectMap) + private async Task CreateProjectToDocumentMapAsync(ProjectMap projectMap, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_CreateDocumentMapAsync, _cancellationToken)) + using (Logger.LogBlock(FunctionId.FindReference_CreateDocumentMapAsync, cancellationToken)) { - using var _ = ArrayBuilder, ISymbol, IReferenceFinder)>>.GetInstance(out var tasks); + using var _ = ArrayBuilder, SymbolGroup, ISymbol, IReferenceFinder)>>.GetInstance(out var tasks); foreach (var (project, projectQueue) in projectMap) { - foreach (var (symbol, finder) in projectQueue) + foreach (var (group, symbol, finder) in projectQueue) { tasks.Add(Task.Factory.StartNew(() => - DetermineDocumentsToSearchAsync(project, symbol, finder), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + DetermineDocumentsToSearchAsync(project, group, symbol, finder, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } } var results = await Task.WhenAll(tasks).ConfigureAwait(false); var finalMap = new ProjectToDocumentMap(); - foreach (var (documents, symbol, finder) in results) + foreach (var (documents, group, symbol, finder) in results) { foreach (var document in documents) { finalMap.GetOrAdd(document.Project, s_createDocumentMap) - .MultiAdd(document, (symbol, finder)); + .MultiAdd(document, (group, symbol, finder)); } } @@ -62,35 +63,36 @@ private async Task CreateProjectToDocumentMapAsync(Project } } - private async Task<(ImmutableArray, ISymbol, IReferenceFinder)> DetermineDocumentsToSearchAsync( - Project project, ISymbol symbol, IReferenceFinder finder) + private async Task<(ImmutableArray, SymbolGroup, ISymbol, IReferenceFinder)> DetermineDocumentsToSearchAsync( + Project project, SymbolGroup group, ISymbol symbol, IReferenceFinder finder, CancellationToken cancellationToken) { var documents = await finder.DetermineDocumentsToSearchAsync( - symbol, project, _documents, _options, _cancellationToken).ConfigureAwait(false); + symbol, project, _documents, _options, cancellationToken).ConfigureAwait(false); var finalDocs = documents.WhereNotNull().Distinct().Where( d => _documents == null || _documents.Contains(d)).ToImmutableArray(); - return (finalDocs, symbol, finder); + return (finalDocs, group, symbol, finder); } - private async Task CreateProjectMapAsync(ConcurrentSet symbols) + private async Task CreateProjectMapAsync(ConcurrentSet symbolGroups, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_CreateProjectMapAsync, _cancellationToken)) + using (Logger.LogBlock(FunctionId.FindReference_CreateProjectMapAsync, cancellationToken)) { var projectMap = new ProjectMap(); var scope = _documents?.Select(d => d.Project).ToImmutableHashSet(); - foreach (var symbol in symbols) + foreach (var symbolGroup in symbolGroups) { - foreach (var finder in _finders) + foreach (var symbol in symbolGroup.Symbols) { - _cancellationToken.ThrowIfCancellationRequested(); - - var projects = await finder.DetermineProjectsToSearchAsync(symbol, _solution, scope, _cancellationToken).ConfigureAwait(false); - foreach (var project in projects.Distinct().WhereNotNull()) + foreach (var finder in _finders) { - if (scope == null || scope.Contains(project)) + cancellationToken.ThrowIfCancellationRequested(); + + var projects = await finder.DetermineProjectsToSearchAsync(symbol, _solution, scope, cancellationToken).ConfigureAwait(false); + foreach (var project in projects.Distinct().WhereNotNull()) { - projectMap.MultiAdd(project, (symbol, finder)); + if (scope == null || scope.Contains(project)) + projectMap.MultiAdd(project, (symbolGroup, symbol, finder)); } } } @@ -101,40 +103,41 @@ private async Task CreateProjectMapAsync(ConcurrentSet symb } } - private async Task> DetermineAllSymbolsAsync( - ISymbol symbol, FindReferencesCascadeDirection cascadeDirection) + private async Task> DetermineAllSymbolsAsync( + ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_DetermineAllSymbolsAsync, _cancellationToken)) + using (Logger.LogBlock(FunctionId.FindReference_DetermineAllSymbolsAsync, cancellationToken)) { - var result = new ConcurrentSet(MetadataUnifyingEquivalenceComparer.Instance); - await DetermineAllSymbolsCoreAsync(symbol, cascadeDirection, result).ConfigureAwait(false); + var result = new ConcurrentSet(); + await DetermineAllSymbolsCoreAsync(symbol, cascadeDirection, result, cancellationToken).ConfigureAwait(false); return result; } } private async Task DetermineAllSymbolsCoreAsync( - ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, ConcurrentSet result) + ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, + ConcurrentSet result, CancellationToken cancellationToken) { - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); var searchSymbol = MapToAppropriateSymbol(symbol); // 2) Try to map this back to source symbol if this was a metadata symbol. - var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(searchSymbol, _solution, _cancellationToken).ConfigureAwait(false); + var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(searchSymbol, _solution, cancellationToken).ConfigureAwait(false); if (sourceSymbol != null) - { searchSymbol = sourceSymbol; - } Contract.ThrowIfNull(searchSymbol); - if (result.Add(searchSymbol)) + + var group = await DetermineSymbolGroupAsync(searchSymbol, cancellationToken).ConfigureAwait(false); + if (result.Add(group)) { - await _progress.OnDefinitionFoundAsync(searchSymbol).ConfigureAwait(false); + await _progress.OnDefinitionFoundAsync(group, cancellationToken).ConfigureAwait(false); // get project to search var projects = GetProjectScope(); - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); using var _ = ArrayBuilder.GetInstance(out var finderTasks); foreach (var f in _finders) @@ -144,39 +147,48 @@ private async Task DetermineAllSymbolsCoreAsync( using var _ = ArrayBuilder.GetInstance(out var symbolTasks); var symbols = await f.DetermineCascadedSymbolsAsync( - searchSymbol, _solution, projects, _options, cascadeDirection, _cancellationToken).ConfigureAwait(false); - AddSymbolTasks(result, symbols, symbolTasks); + searchSymbol, _solution, projects, _options, cascadeDirection, cancellationToken).ConfigureAwait(false); + AddSymbolTasks(result, symbols, symbolTasks, cancellationToken); // Defer to the language to see if it wants to cascade here in some special way. var symbolProject = _solution.GetProject(searchSymbol.ContainingAssembly); if (symbolProject?.LanguageServices.GetService() is { } service) { symbols = await service.DetermineCascadedSymbolsAsync( - searchSymbol, symbolProject, cascadeDirection, _cancellationToken).ConfigureAwait(false); - AddSymbolTasks(result, symbols, symbolTasks); + searchSymbol, symbolProject, cascadeDirection, cancellationToken).ConfigureAwait(false); + AddSymbolTasks(result, symbols, symbolTasks, cancellationToken); } - _cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); await Task.WhenAll(symbolTasks).ConfigureAwait(false); - }, _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + }, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } await Task.WhenAll(finderTasks).ConfigureAwait(false); } } + private async Task DetermineSymbolGroupAsync(ISymbol searchSymbol, CancellationToken cancellationToken) + { + if (!_options.Cascade) + return new SymbolGroup(ImmutableArray.Create(searchSymbol)); + + return new SymbolGroup( + await SymbolFinder.FindLinkedSymbolsAsync(searchSymbol, _solution, cancellationToken).ConfigureAwait(false)); + } + private void AddSymbolTasks( - ConcurrentSet result, + ConcurrentSet result, ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)> symbols, - ArrayBuilder symbolTasks) + ArrayBuilder symbolTasks, + CancellationToken cancellationToken) { if (!symbols.IsDefault) { foreach (var (symbol, cascadeDirection) in symbols) { Contract.ThrowIfNull(symbol); - _cancellationToken.ThrowIfCancellationRequested(); // If we're cascading unidirectionally, then keep going in the direction this symbol was found in. // Otherwise, if we're not unidirectional, then continue to cascade in both directions with this @@ -185,7 +197,7 @@ private void AddSymbolTasks( ? cascadeDirection : FindReferencesCascadeDirection.UpAndDown; symbolTasks.Add(Task.Factory.StartNew( - () => DetermineAllSymbolsCoreAsync(symbol, finalDirection, result), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + () => DetermineAllSymbolsCoreAsync(symbol, finalDirection, result, cancellationToken), 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 e35744ee0a02d..c1db21f5f435a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; @@ -11,26 +12,27 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = Dictionary>; + using DocumentMap = Dictionary>; internal partial class FindReferencesSearchEngine { private async Task ProcessProjectAsync( Project project, - DocumentMap documentMap) + DocumentMap documentMap, + CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.FindReference_ProcessProjectAsync, project.Name, _cancellationToken)) + using (Logger.LogBlock(FunctionId.FindReference_ProcessProjectAsync, project.Name, cancellationToken)) { if (project.SupportsCompilation) { // make sure we hold onto compilation while we search documents belong to this project - var compilation = await project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var documentTasks = new List(); foreach (var (document, documentQueue) in documentMap) { if (document.Project == project) - documentTasks.Add(Task.Factory.StartNew(() => ProcessDocumentQueueAsync(document, documentQueue), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + documentTasks.Add(Task.Factory.StartNew(() => ProcessDocumentQueueAsync(document, documentQueue, cancellationToken), cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } await Task.WhenAll(documentTasks).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs deleted file mode 100644 index 90ac75ed84fa8..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols.Finders -{ - internal class LinkedFileReferenceFinder : IReferenceFinder - { - public async Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet? projects, - FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken) - { - if (!options.Cascade) - return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; - - var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - return linkedSymbols.SelectAsArray(s => (s, cascadeDirection)); - } - - public Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, Project project, IImmutableSet? documents, - FindReferencesSearchOptions options, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyImmutableArray(); - } - - public Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) - => SpecializedTasks.EmptyImmutableArray(); - - public ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, Document document, SemanticModel semanticModel, - FindReferencesSearchOptions options, CancellationToken cancellationToken) - { - return new ValueTask>(ImmutableArray.Empty); - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs index c36ff233c8e61..7afe3bc6cb545 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs @@ -16,7 +16,6 @@ internal static class ReferenceFinders public static readonly IReferenceFinder Event = new EventSymbolReferenceFinder(); public static readonly IReferenceFinder Field = new FieldSymbolReferenceFinder(); public static readonly IReferenceFinder Label = new LabelSymbolReferenceFinder(); - public static readonly IReferenceFinder LinkedFiles = new LinkedFileReferenceFinder(); public static readonly IReferenceFinder Local = new LocalSymbolReferenceFinder(); public static readonly IReferenceFinder MethodTypeParameter = new MethodTypeParameterSymbolReferenceFinder(); public static readonly IReferenceFinder NamedType = new NamedTypeSymbolReferenceFinder(); @@ -47,7 +46,6 @@ static ReferenceFinders() ExplicitInterfaceMethod, Field, Label, - LinkedFiles, Local, MethodTypeParameter, NamedType, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 2ccc29a638c01..9dfc71ad26461 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -28,17 +29,17 @@ private NoOpStreamingFindReferencesProgress() public static Task ReportProgressAsync(int current, int maximum) => Task.CompletedTask; #pragma warning restore IDE0060 // Remove unused parameter - public ValueTask OnCompletedAsync() => default; - public ValueTask OnStartedAsync() => default; - public ValueTask OnDefinitionFoundAsync(ISymbol symbol) => default; - public ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location) => default; - public ValueTask OnFindInDocumentStartedAsync(Document document) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document) => default; + public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; + public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; + public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => default; + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) => default; + public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; + public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; private class NoOpProgressTracker : IStreamingProgressTracker { - public ValueTask AddItemsAsync(int count) => default; - public ValueTask ItemCompletedAsync() => default; + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => default; + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) => default; } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index bf314ea7990ba..0dc8f2679485c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -22,44 +23,46 @@ internal class StreamingFindReferencesProgressAdapter : IStreamingFindReferences public StreamingFindReferencesProgressAdapter(IFindReferencesProgress progress) { _progress = progress; - ProgressTracker = new StreamingProgressTracker((current, max) => + ProgressTracker = new StreamingProgressTracker((current, max, ct) => { _progress.ReportProgress(current, max); return default; }); } - public ValueTask OnCompletedAsync() + public ValueTask OnCompletedAsync(CancellationToken cancellationToken) { _progress.OnCompleted(); return default; } - public ValueTask OnDefinitionFoundAsync(ISymbol symbol) + public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) { - _progress.OnDefinitionFound(symbol); + _progress.OnFindInDocumentCompleted(document); return default; } - public ValueTask OnFindInDocumentCompletedAsync(Document document) + public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) { - _progress.OnFindInDocumentCompleted(document); + _progress.OnFindInDocumentStarted(document); return default; } - public ValueTask OnFindInDocumentStartedAsync(Document document) + public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { - _progress.OnFindInDocumentStarted(document); + foreach (var symbol in group.Symbols) + _progress.OnDefinitionFound(symbol); + return default; } - public ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location) + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) { _progress.OnReferenceFound(symbol, location); return default; } - public ValueTask OnStartedAsync() + public ValueTask OnStartedAsync(CancellationToken cancellationToken) { _progress.OnStarted(); return default; diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 529739f91cb43..0fbb148cf1944 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -17,18 +17,18 @@ internal interface IRemoteSymbolFinderService { internal interface ICallback { - ValueTask AddReferenceItemsAsync(RemoteServiceCallbackId callbackId, int count); - ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId); - ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId); - ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId); - ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId); - ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId); - ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition); - ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference); - - ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count); - ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId); - ValueTask OnLiteralReferenceFoundAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, TextSpan span); + ValueTask AddReferenceItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); + ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); + ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); + ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, CancellationToken cancellationToken); + ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken); + + ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); + ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask OnLiteralReferenceFoundAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, TextSpan span, CancellationToken cancellationToken); } ValueTask FindReferencesAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId symbolAndProjectIdArg, ImmutableArray documentArgs, diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 27d9e4a7c2d8f..ba5c90f6ea97e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -2,14 +2,61 @@ // 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.Immutable; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { + /// + /// Represents a group of s that should be treated as a single entity for + /// the purposes of presentation in a Find UI. For example, when a symbol is defined in a file + /// that is linked into multiple project contexts, there will be several unique symbols created + /// that we search for. Placing these in a group allows the final consumer to know that these + /// symbols can be merged together. + /// + internal class SymbolGroup : IEquatable + { + /// + /// All the symbols in the group. + /// + public ImmutableHashSet Symbols { get; } + + private int _hashCode; + + public SymbolGroup(ImmutableArray symbols) + { + Contract.ThrowIfTrue(symbols.IsDefaultOrEmpty, "Symbols should be non empty"); + + Symbols = ImmutableHashSet.CreateRange( + MetadataUnifyingEquivalenceComparer.Instance, symbols); + } + + public override bool Equals(object? obj) + => obj is SymbolGroup group && Equals(group); + + public bool Equals(SymbolGroup? group) + => this == group || (group != null && Symbols.SetEquals(group.Symbols)); + + public override int GetHashCode() + { + if (_hashCode == 0) + { + var hashCode = 0; + foreach (var symbol in Symbols) + hashCode += MetadataUnifyingEquivalenceComparer.Instance.GetHashCode(symbol); + _hashCode = hashCode; + } + + return _hashCode; + } + } + /// /// Reports the progress of the FindReferences operation. Note: these methods may be called on /// any thread. @@ -18,20 +65,20 @@ internal interface IStreamingFindReferencesProgress { IStreamingProgressTracker ProgressTracker { get; } - ValueTask OnStartedAsync(); - ValueTask OnCompletedAsync(); + ValueTask OnStartedAsync(CancellationToken cancellationToken); + ValueTask OnCompletedAsync(CancellationToken cancellationToken); - ValueTask OnFindInDocumentStartedAsync(Document document); - ValueTask OnFindInDocumentCompletedAsync(Document document); + ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken); + ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnDefinitionFoundAsync(ISymbol symbol); - ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location); + ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken); + ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken); } internal interface IStreamingFindLiteralReferencesProgress { IStreamingProgressTracker ProgressTracker { get; } - ValueTask OnReferenceFoundAsync(Document document, TextSpan span); + ValueTask OnReferenceFoundAsync(Document document, TextSpan span, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 671c5308fdb96..fcc4e5d2c12f7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -53,30 +54,31 @@ public ImmutableArray GetReferencedSymbols() } } - public ValueTask OnStartedAsync() => _underlyingProgress.OnStartedAsync(); - public ValueTask OnCompletedAsync() => _underlyingProgress.OnCompletedAsync(); + public ValueTask OnStartedAsync(CancellationToken cancellationToken) => _underlyingProgress.OnStartedAsync(cancellationToken); + public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => _underlyingProgress.OnCompletedAsync(cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(Document document) => _underlyingProgress.OnFindInDocumentCompletedAsync(document); - public ValueTask OnFindInDocumentStartedAsync(Document document) => _underlyingProgress.OnFindInDocumentStartedAsync(document); + public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => _underlyingProgress.OnFindInDocumentCompletedAsync(document, cancellationToken); + public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => _underlyingProgress.OnFindInDocumentStartedAsync(document, cancellationToken); - public ValueTask OnDefinitionFoundAsync(ISymbol definition) + public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { lock (_gate) { - _symbolToLocations[definition] = new List(); + foreach (var definition in group.Symbols) + _symbolToLocations[definition] = new List(); } - return _underlyingProgress.OnDefinitionFoundAsync(definition); + return _underlyingProgress.OnDefinitionFoundAsync(group, cancellationToken); } - public ValueTask OnReferenceFoundAsync(ISymbol definition, ReferenceLocation location) + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) { lock (_gate) { _symbolToLocations[definition].Add(location); } - return _underlyingProgress.OnReferenceFoundAsync(definition, location); + return _underlyingProgress.OnReferenceFoundAsync(group, definition, location, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index a1d4f5963b9c2..ef2f07cb0041d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Remote; @@ -30,40 +31,40 @@ private FindReferencesServerCallback GetFindReferencesCallback(RemoteServiceCall // references - public ValueTask AddReferenceItemsAsync(RemoteServiceCallbackId callbackId, int count) - => GetFindReferencesCallback(callbackId).AddItemsAsync(count); + public ValueTask AddReferenceItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).AddItemsAsync(count, cancellationToken); - public ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId) - => GetFindReferencesCallback(callbackId).ItemCompletedAsync(); + public ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).ItemCompletedAsync(cancellationToken); - public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId) - => GetFindReferencesCallback(callbackId).OnCompletedAsync(); + public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnCompletedAsync(cancellationToken); - public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition) - => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(definition); + public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(symbolGroup, cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId) - => GetFindReferencesCallback(callbackId).OnFindInDocumentCompletedAsync(documentId); + public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnFindInDocumentCompletedAsync(documentId, cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId) - => GetFindReferencesCallback(callbackId).OnFindInDocumentStartedAsync(documentId); + public ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnFindInDocumentStartedAsync(documentId, cancellationToken); - public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference) - => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(definition, reference); + public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(symbolGroup, definition, reference, cancellationToken); - public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId) - => GetFindReferencesCallback(callbackId).OnStartedAsync(); + public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnStartedAsync(cancellationToken); // literals - public ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count) - => GetFindLiteralsCallback(callbackId).AddItemsAsync(count); + public ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken) + => GetFindLiteralsCallback(callbackId).AddItemsAsync(count, cancellationToken); - public ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId) - => GetFindLiteralsCallback(callbackId).ItemCompletedAsync(); + public ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + => GetFindLiteralsCallback(callbackId).ItemCompletedAsync(cancellationToken); - public ValueTask OnLiteralReferenceFoundAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, TextSpan span) - => GetFindLiteralsCallback(callbackId).OnLiteralReferenceFoundAsync(documentId, span); + public ValueTask OnLiteralReferenceFoundAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, TextSpan span, CancellationToken cancellationToken) + => GetFindLiteralsCallback(callbackId).OnLiteralReferenceFoundAsync(documentId, span, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs index cfe0c50ef33d1..53115374d148a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.FindSymbols @@ -24,16 +24,16 @@ public FindLiteralsServerCallback( _progress = progress; } - public ValueTask AddItemsAsync(int count) - => _progress.ProgressTracker.AddItemsAsync(count); + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) + => _progress.ProgressTracker.AddItemsAsync(count, cancellationToken); - public ValueTask ItemCompletedAsync() - => _progress.ProgressTracker.ItemCompletedAsync(); + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + => _progress.ProgressTracker.ItemCompletedAsync(cancellationToken); - public async ValueTask OnLiteralReferenceFoundAsync(DocumentId documentId, TextSpan span) + public async ValueTask OnLiteralReferenceFoundAsync(DocumentId documentId, TextSpan span, CancellationToken cancellationToken) { - var document = _solution.GetDocument(documentId); - await _progress.OnReferenceFoundAsync(document, span).ConfigureAwait(false); + var document = _solution.GetRequiredDocument(documentId); + await _progress.OnReferenceFoundAsync(document, span, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index bc687183e8c50..77d5c5640eb39 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -5,9 +5,12 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { @@ -17,69 +20,80 @@ public static partial class SymbolFinder /// Callback object we pass to the OOP server to hear about the result /// of the FindReferencesEngine as it executes there. /// - internal sealed class FindReferencesServerCallback : IEqualityComparer + internal sealed class FindReferencesServerCallback { private readonly Solution _solution; private readonly IStreamingFindReferencesProgress _progress; - private readonly CancellationToken _cancellationToken; private readonly object _gate = new(); - private readonly Dictionary _definitionMap; + private readonly Dictionary _groupMap = new(); + private readonly Dictionary _definitionMap = new(); public FindReferencesServerCallback( Solution solution, - IStreamingFindReferencesProgress progress, - CancellationToken cancellationToken) + IStreamingFindReferencesProgress progress) { _solution = solution; _progress = progress; - _cancellationToken = cancellationToken; - _definitionMap = new Dictionary(this); } - public ValueTask AddItemsAsync(int count) - => _progress.ProgressTracker.AddItemsAsync(count); + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) + => _progress.ProgressTracker.AddItemsAsync(count, cancellationToken); - public ValueTask ItemCompletedAsync() - => _progress.ProgressTracker.ItemCompletedAsync(); + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + => _progress.ProgressTracker.ItemCompletedAsync(cancellationToken); - public ValueTask OnStartedAsync() - => _progress.OnStartedAsync(); + public ValueTask OnStartedAsync(CancellationToken cancellationToken) + => _progress.OnStartedAsync(cancellationToken); - public ValueTask OnCompletedAsync() - => _progress.OnCompletedAsync(); + public ValueTask OnCompletedAsync(CancellationToken cancellationToken) + => _progress.OnCompletedAsync(cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(DocumentId documentId) + public ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) { var document = _solution.GetDocument(documentId); - return _progress.OnFindInDocumentStartedAsync(document); + return _progress.OnFindInDocumentStartedAsync(document, cancellationToken); } - public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId) + public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) { var document = _solution.GetDocument(documentId); - return _progress.OnFindInDocumentCompletedAsync(document); + return _progress.OnFindInDocumentCompletedAsync(document, cancellationToken); } - public async ValueTask OnDefinitionFoundAsync(SerializableSymbolAndProjectId definition) + public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated, CancellationToken cancellationToken) { - var symbol = await definition.TryRehydrateAsync( - _solution, _cancellationToken).ConfigureAwait(false); + Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); - if (symbol == null) - return; + using var _ = PooledDictionary.GetInstance(out var map); + foreach (var symbolAndProjectId in dehydrated.Symbols) + { + var symbol = await symbolAndProjectId.TryRehydrateAsync(_solution, cancellationToken).ConfigureAwait(false); + if (symbol == null) + return; + + map[symbolAndProjectId] = symbol; + } + + var symbolGroup = new SymbolGroup(map.Values.ToImmutableArray()); lock (_gate) { - _definitionMap[definition] = symbol; + _groupMap[dehydrated] = symbolGroup; + foreach (var pair in map) + _definitionMap[pair.Key] = pair.Value; } - await _progress.OnDefinitionFoundAsync(symbol).ConfigureAwait(false); + await _progress.OnDefinitionFoundAsync(symbolGroup, cancellationToken).ConfigureAwait(false); } public async ValueTask OnReferenceFoundAsync( - SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference) + SerializableSymbolGroup serializableSymbolGroup, + SerializableSymbolAndProjectId serializableSymbol, + SerializableReferenceLocation reference, + CancellationToken cancellationToken) { + SymbolGroup symbolGroup; ISymbol symbol; lock (_gate) { @@ -90,21 +104,18 @@ public async ValueTask OnReferenceFoundAsync( // definition so we can track down that issue. // 2. NFE'ing and failing to show a result, is much better than NFE'ing and then crashing // immediately afterwards. - if (!_definitionMap.TryGetValue(definition, out symbol)) + if (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || + !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + { return; + } } var referenceLocation = await reference.RehydrateAsync( - _solution, _cancellationToken).ConfigureAwait(false); + _solution, cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(symbol, referenceLocation).ConfigureAwait(false); + await _progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation, cancellationToken).ConfigureAwait(false); } - - bool IEqualityComparer.Equals(SerializableSymbolAndProjectId x, SerializableSymbolAndProjectId y) - => y.SymbolKeyData.Equals(x.SymbolKeyData); - - int IEqualityComparer.GetHashCode(SerializableSymbolAndProjectId obj) - => obj.SymbolKeyData.GetHashCode(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index 7fdac43505daa..bbb870427206e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.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.Collections.Generic; using System.Collections.Immutable; @@ -46,6 +44,11 @@ public static async Task FindSymbolAtPositionAsync( Workspace workspace, CancellationToken cancellationToken = default) { + if (semanticModel is null) + throw new ArgumentNullException(nameof(semanticModel)); + if (workspace is null) + throw new ArgumentNullException(nameof(workspace)); + var semanticInfo = await GetSemanticInfoAtPositionAsync( semanticModel, position, workspace, cancellationToken: cancellationToken).ConfigureAwait(false); return semanticInfo.GetAnySymbol(includeType: false); @@ -75,7 +78,7 @@ private static Task GetTokenAtPositionAsync( CancellationToken cancellationToken) { var syntaxTree = semanticModel.SyntaxTree; - var syntaxFacts = workspace.Services.GetLanguageServices(semanticModel.Language).GetService(); + var syntaxFacts = workspace.Services.GetLanguageServices(semanticModel.Language).GetRequiredService(); return syntaxTree.GetTouchingTokenAsync(position, syntaxFacts.IsBindableToken, cancellationToken, findInsideTrivia: true); } @@ -85,7 +88,10 @@ public static async Task FindSymbolAtPositionAsync( int position, CancellationToken cancellationToken = default) { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (document is null) + throw new ArgumentNullException(nameof(document)); + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); return await FindSymbolAtPositionAsync(semanticModel, position, document.Project.Solution.Workspace, cancellationToken).ConfigureAwait(false); } @@ -93,8 +99,8 @@ public static async Task FindSymbolAtPositionAsync( /// Finds the definition symbol declared in source code for a corresponding reference symbol. /// Returns null if no such symbol can be found in the specified solution. /// - public static Task FindSourceDefinitionAsync( - ISymbol symbol, Solution solution, CancellationToken cancellationToken = default) + public static Task FindSourceDefinitionAsync( + ISymbol? symbol, Solution solution, CancellationToken cancellationToken = default) { if (symbol != null) { @@ -117,7 +123,7 @@ public static Task FindSourceDefinitionAsync( return SpecializedTasks.Null(); } - private static async Task FindSourceDefinitionWorkerAsync( + private static async Task FindSourceDefinitionWorkerAsync( ISymbol symbol, Solution solution, CancellationToken cancellationToken) @@ -160,7 +166,7 @@ private static async Task FindSourceDefinitionWorkerAsync( if (project != null && project.SupportsCompilation) { var symbolId = symbol.GetSymbolKey(cancellationToken); - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var result = symbolId.Resolve(compilation, ignoreAssemblyKey: true, cancellationToken: cancellationToken); if (result.Symbol != null && InSource(result.Symbol)) @@ -178,11 +184,6 @@ private static async Task FindSourceDefinitionWorkerAsync( private static bool InSource(ISymbol symbol) { - if (symbol == null) - { - return false; - } - return symbol.Locations.Any(loc => loc.IsInSource); } @@ -202,15 +203,11 @@ private static bool InSource(ISymbol symbol) public static IEnumerable FindSimilarSymbols(TSymbol symbol, Compilation compilation, CancellationToken cancellationToken = default) where TSymbol : ISymbol { - if (symbol == null) - { + if (symbol is null) throw new ArgumentNullException(nameof(symbol)); - } - if (compilation == null) - { + if (compilation is null) throw new ArgumentNullException(nameof(compilation)); - } var key = symbol.GetSymbolKey(cancellationToken); @@ -254,11 +251,11 @@ internal static async Task> FindLinkedSymbolsAsync( foreach (var linkedDocumentId in originalDocument.GetLinkedDocumentIds()) { - var linkedDocument = solution.GetDocument(linkedDocumentId); - var linkedSyntaxRoot = await linkedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var linkedDocument = solution.GetRequiredDocument(linkedDocumentId); + var linkedSyntaxRoot = await linkedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var linkedNode = linkedSyntaxRoot.FindNode(location.Span, getInnermostNodeForTie: true); - var semanticModel = await linkedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await linkedDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var linkedSymbol = semanticModel.GetDeclaredSymbol(linkedNode, cancellationToken); if (linkedSymbol != null && diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Callers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Callers.cs index 6dfc3c13d8266..3ed05a9930514 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Callers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Callers.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.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -32,8 +30,13 @@ public static Task> FindCallersAsync( /// Finds all the callers of a specified symbol. /// public static async Task> FindCallersAsync( - ISymbol symbol, Solution solution, IImmutableSet documents, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet? documents, CancellationToken cancellationToken = default) { + if (symbol is null) + throw new System.ArgumentNullException(nameof(symbol)); + if (solution is null) + throw new System.ArgumentNullException(nameof(solution)); + symbol = symbol.OriginalDefinition; var foundSymbol = await FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false); symbol = foundSymbol ?? symbol; @@ -72,21 +75,18 @@ async Task AddReferencingSymbols(ReferencedSymbol reference, bool isDirect) private static async Task> FindCallReferencesAsync( Solution solution, ISymbol symbol, - IImmutableSet documents, + IImmutableSet? documents, CancellationToken cancellationToken = default) { - if (symbol != null) + if (symbol.Kind == SymbolKind.Event || + symbol.Kind == SymbolKind.Method || + symbol.Kind == SymbolKind.Property) { - if (symbol.Kind == SymbolKind.Event || - symbol.Kind == SymbolKind.Method || - symbol.Kind == SymbolKind.Property) - { - var collector = new StreamingProgressCollector(); - await FindReferencesAsync( - symbol, solution, collector, documents, - FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); - return collector.GetReferencedSymbols(); - } + var collector = new StreamingProgressCollector(); + await FindReferencesAsync( + symbol, solution, collector, documents, + FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); + return collector.GetReferencedSymbols(); } return ImmutableArray.Empty; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs index 736db99013304..23e1b777576a2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs @@ -49,9 +49,8 @@ internal static Task FindLiteralReferencesInCurrentProcessAsync( IStreamingFindLiteralReferencesProgress progress, CancellationToken cancellationToken) { - var engine = new FindLiteralsSearchEngine( - solution, progress, value, cancellationToken); - return engine.FindReferencesAsync(); + var engine = new FindLiteralsSearchEngine(solution, progress, value); + return engine.FindReferencesAsync(cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs index 43aa74f5307cf..824fdab33a252 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.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.Collections.Immutable; using System.Linq; using System.Threading; @@ -25,7 +23,7 @@ internal static async Task FindReferencesAsync( ISymbol symbol, Solution solution, IStreamingFindReferencesProgress progress, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -39,7 +37,7 @@ internal static async Task FindReferencesAsync( // Create a callback that we can pass to the server process to hear about the // results as it finds them. When we hear about results we'll forward them to // the 'progress' parameter which will then update the UI. - var serverCallback = new FindReferencesServerCallback(solution, progress, cancellationToken); + var serverCallback = new FindReferencesServerCallback(solution, progress); var documentIds = documents?.SelectAsArray(d => d.Id) ?? default; await client.TryInvokeAsync( @@ -63,15 +61,15 @@ internal static Task FindReferencesInCurrentProcessAsync( ISymbol symbol, Solution solution, IStreamingFindReferencesProgress progress, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var finders = ReferenceFinders.DefaultReferenceFinders; progress ??= NoOpStreamingFindReferencesProgress.Instance; var engine = new FindReferencesSearchEngine( - solution, documents, finders, progress, options, cancellationToken); - return engine.FindReferencesAsync(symbol); + solution, documents, finders, progress, options); + return engine.FindReferencesAsync(symbol, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs index 72dc8dc1151da..2a6e4ef0ab58f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.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.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -29,6 +27,11 @@ public static Task> FindReferencesAsync( Solution solution, CancellationToken cancellationToken = default) { + if (symbol is null) + throw new System.ArgumentNullException(nameof(symbol)); + if (solution is null) + throw new System.ArgumentNullException(nameof(solution)); + return FindReferencesAsync(symbol, solution, FindReferencesSearchOptions.Default, cancellationToken); } @@ -55,9 +58,13 @@ await FindReferencesAsync( public static Task> FindReferencesAsync( ISymbol symbol, Solution solution, - IImmutableSet documents, + IImmutableSet? documents, CancellationToken cancellationToken = default) { + if (symbol is null) + throw new System.ArgumentNullException(nameof(symbol)); + if (solution is null) + throw new System.ArgumentNullException(nameof(solution)); return FindReferencesAsync(symbol, solution, progress: null, documents: documents, cancellationToken: cancellationToken); } @@ -73,10 +80,14 @@ public static Task> FindReferencesAsync( public static async Task> FindReferencesAsync( ISymbol symbol, Solution solution, - IFindReferencesProgress progress, - IImmutableSet documents, + IFindReferencesProgress? progress, + IImmutableSet? documents, CancellationToken cancellationToken = default) { + if (symbol is null) + throw new System.ArgumentNullException(nameof(symbol)); + if (solution is null) + throw new System.ArgumentNullException(nameof(solution)); return await FindReferencesAsync( symbol, solution, progress, documents, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); @@ -85,8 +96,8 @@ public static async Task> FindReferencesAsync( internal static async Task> FindReferencesAsync( ISymbol symbol, Solution solution, - IFindReferencesProgress progress, - IImmutableSet documents, + IFindReferencesProgress? progress, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindRenamableReferences.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindRenamableReferences.cs index 757510178b6b6..058cdb47c58c5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindRenamableReferences.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindRenamableReferences.cs @@ -26,10 +26,9 @@ internal static async Task> FindRenamableRefere documents: null, ReferenceFinders.DefaultRenameReferenceFinders, streamingProgress, - FindReferencesSearchOptions.Default, - cancellationToken); + FindReferencesSearchOptions.Default); - await engine.FindReferencesAsync(symbol).ConfigureAwait(false); + await engine.FindReferencesAsync(symbol, cancellationToken).ConfigureAwait(false); return streamingProgress.GetReferencedSymbols(); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 6a34e60525022..0ff4e6b551eb9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols internal partial class SymbolTreeInfo : IObjectWritable { private const string PrefixMetadataSymbolTreeInfo = ""; - private static readonly Checksum SerializationFormatChecksum = Checksum.Create("21"); + private static readonly Checksum SerializationFormatChecksum = Checksum.Create("22"); /// /// Loads the SpellChecker for a given assembly symbol (metadata or project). If the diff --git a/src/Workspaces/Core/Portable/Indentation/DefaultInferredIndentationService.cs b/src/Workspaces/Core/Portable/Indentation/DefaultInferredIndentationService.cs new file mode 100644 index 0000000000000..df5b50dce3a25 --- /dev/null +++ b/src/Workspaces/Core/Portable/Indentation/DefaultInferredIndentationService.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.Indentation +{ + [ExportWorkspaceService(typeof(IInferredIndentationService), ServiceLayer.Default), Shared] + internal sealed class DefaultInferredIndentationService + : IInferredIndentationService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DefaultInferredIndentationService() + { + } + + public Task GetDocumentOptionsWithInferredIndentationAsync(Document document, bool explicitFormat, CancellationToken cancellationToken) + { + // The workspaces layer doesn't have any smarts to infer spaces/tabs settings without an editorconfig, so just return + // the document's options. + return document.GetOptionsAsync(cancellationToken); + } + } +} diff --git a/src/Workspaces/Core/Portable/Indentation/IInferredIndentationService.cs b/src/Workspaces/Core/Portable/Indentation/IInferredIndentationService.cs new file mode 100644 index 0000000000000..1acc3d60ff49f --- /dev/null +++ b/src/Workspaces/Core/Portable/Indentation/IInferredIndentationService.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.Indentation +{ + /// + /// Gets the correct indentation to be used for the document. Depending on the host, there may be smarts to compensate for lack of an editorconfig if there + /// isn't one present. + /// + internal interface IInferredIndentationService : IWorkspaceService + { + Task GetDocumentOptionsWithInferredIndentationAsync(Document document, bool explicitFormat, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/DefaultDocumentTextDifferencingService.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/DefaultDocumentTextDifferencingService.cs index b499fa50fc95e..941a4c1ecffd1 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/DefaultDocumentTextDifferencingService.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/DefaultDocumentTextDifferencingService.cs @@ -2,8 +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. -#nullable disable - +using System; using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; @@ -18,7 +17,7 @@ namespace Microsoft.CodeAnalysis internal class DefaultDocumentTextDifferencingService : IDocumentTextDifferencingService { [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Incorrectly used in production code: https://github.com/dotnet/roslyn/issues/42839")] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DefaultDocumentTextDifferencingService() { } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs deleted file mode 100644 index c1b2bd5f005e8..0000000000000 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs +++ /dev/null @@ -1,119 +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 Microsoft.CodeAnalysis.Internal.Log; -using static Microsoft.CodeAnalysis.LinkedFileDiffMergingSession; - -namespace Microsoft.CodeAnalysis -{ - internal class LinkedFileDiffMergingLogger - { - private static readonly LogAggregator LogAggregator = new(); - - internal enum MergeInfo - { - SessionsWithLinkedFiles, - LinkedFileGroupsProcessed, - IdenticalDiffs, - IsolatedDiffs, - OverlappingDistinctDiffs, - OverlappingDistinctDiffsWithSameSpan, - OverlappingDistinctDiffsWithSameSpanAndSubstringRelation, - InsertedMergeConflictComments, - InsertedMergeConflictCommentsAtAdjustedLocation - } - - internal static void LogSession(LinkedFileDiffMergingSessionInfo sessionInfo) - { - if (sessionInfo.LinkedFileGroups.Count > 1) - { - LogNewSessionWithLinkedFiles(); - LogNumberOfLinkedFileGroupsProcessed(sessionInfo.LinkedFileGroups.Count); - - foreach (var groupInfo in sessionInfo.LinkedFileGroups) - { - LogNumberOfIdenticalDiffs(groupInfo.IdenticalDiffs); - LogNumberOfIsolatedDiffs(groupInfo.IsolatedDiffs); - LogNumberOfOverlappingDistinctDiffs(groupInfo.OverlappingDistinctDiffs); - LogNumberOfOverlappingDistinctDiffsWithSameSpan(groupInfo.OverlappingDistinctDiffsWithSameSpan); - LogNumberOfOverlappingDistinctDiffsWithSameSpanAndSubstringRelation(groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation); - LogNumberOfInsertedMergeConflictComments(groupInfo.InsertedMergeConflictComments); - LogNumberOfInsertedMergeConflictCommentsAtAdjustedLocation(groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation); - - if (groupInfo.InsertedMergeConflictComments > 0 || - groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation > 0) - { - Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup, SessionLogMessage.Create(groupInfo)); - } - } - } - } - - private static void LogNewSessionWithLinkedFiles() => - Log((int)MergeInfo.SessionsWithLinkedFiles, 1); - - private static void LogNumberOfLinkedFileGroupsProcessed(int linkedFileGroupsProcessed) => - Log((int)MergeInfo.LinkedFileGroupsProcessed, linkedFileGroupsProcessed); - - private static void LogNumberOfIdenticalDiffs(int identicalDiffs) => - Log((int)MergeInfo.IdenticalDiffs, identicalDiffs); - - private static void LogNumberOfIsolatedDiffs(int isolatedDiffs) => - Log((int)MergeInfo.IsolatedDiffs, isolatedDiffs); - - private static void LogNumberOfOverlappingDistinctDiffs(int overlappingDistinctDiffs) => - Log((int)MergeInfo.OverlappingDistinctDiffs, overlappingDistinctDiffs); - - private static void LogNumberOfOverlappingDistinctDiffsWithSameSpan(int overlappingDistinctDiffsWithSameSpan) => - Log((int)MergeInfo.OverlappingDistinctDiffsWithSameSpan, overlappingDistinctDiffsWithSameSpan); - - private static void LogNumberOfOverlappingDistinctDiffsWithSameSpanAndSubstringRelation(int overlappingDistinctDiffsWithSameSpanAndSubstringRelation) => - Log((int)MergeInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation, overlappingDistinctDiffsWithSameSpanAndSubstringRelation); - - private static void LogNumberOfInsertedMergeConflictComments(int insertedMergeConflictComments) => - Log((int)MergeInfo.InsertedMergeConflictComments, insertedMergeConflictComments); - - private static void LogNumberOfInsertedMergeConflictCommentsAtAdjustedLocation(int insertedMergeConflictCommentsAtAdjustedLocation) => - Log((int)MergeInfo.InsertedMergeConflictCommentsAtAdjustedLocation, insertedMergeConflictCommentsAtAdjustedLocation); - - private static void Log(int mergeInfo, int count) - => LogAggregator.IncreaseCountBy(mergeInfo, count); - - internal static void ReportTelemetry() - { - Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession, KeyValueLogMessage.Create(m => - { - foreach (var kv in LogAggregator) - { - var mergeInfo = ((MergeInfo)kv.Key).ToString("f"); - m[mergeInfo] = kv.Value.GetCount(); - } - })); - } - - private static class SessionLogMessage - { - private const string LinkedDocuments = nameof(LinkedDocuments); - private const string DocumentsWithChanges = nameof(DocumentsWithChanges); - - public static KeyValueLogMessage Create(LinkedFileGroupSessionInfo groupInfo) - { - return KeyValueLogMessage.Create(m => - { - m[LinkedDocuments] = groupInfo.LinkedDocuments; - m[DocumentsWithChanges] = groupInfo.DocumentsWithChanges; - m[MergeInfo.IdenticalDiffs.ToString("f")] = groupInfo.IdenticalDiffs; - m[MergeInfo.IsolatedDiffs.ToString("f")] = groupInfo.IsolatedDiffs; - m[MergeInfo.OverlappingDistinctDiffs.ToString("f")] = groupInfo.OverlappingDistinctDiffs; - m[MergeInfo.OverlappingDistinctDiffsWithSameSpan.ToString("f")] = groupInfo.OverlappingDistinctDiffsWithSameSpan; - m[MergeInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation.ToString("f")] = groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation; - m[MergeInfo.InsertedMergeConflictComments.ToString("f")] = groupInfo.InsertedMergeConflictComments; - m[MergeInfo.InsertedMergeConflictCommentsAtAdjustedLocation.ToString("f")] = groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation; - }); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index cee39de1155a8..18e31f305fcdc 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -19,18 +19,15 @@ namespace Microsoft.CodeAnalysis { internal sealed class LinkedFileDiffMergingSession { - private readonly bool _logSessionInfo; - private readonly Solution _oldSolution; private readonly Solution _newSolution; private readonly SolutionChanges _solutionChanges; - public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges, bool logSessionInfo) + public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges) { _oldSolution = oldSolution; _newSolution = newSolution; _solutionChanges = solutionChanges; - _logSessionInfo = logSessionInfo; } internal async Task MergeDiffsAsync(IMergeConflictHandler mergeConflictHandler, CancellationToken cancellationToken) @@ -74,8 +71,6 @@ internal async Task MergeDiffsAsync(IMergeConflict } } - LogLinkedFileDiffMergingSessionInfo(sessionInfo); - return new LinkedFileMergeSessionResult(updatedSolution, linkedFileMergeResults); } @@ -90,7 +85,7 @@ private async Task MergeLinkedDocumentGroupAsync( // Automatically merge non-conflicting diffs while collecting the conflicting diffs - var textDifferencingService = _oldSolution.Workspace.Services.GetService() ?? new DefaultDocumentTextDifferencingService(); + var textDifferencingService = _oldSolution.Workspace.Services.GetRequiredService(); var appliedChanges = await textDifferencingService.GetTextChangesAsync(_oldSolution.GetDocument(linkedDocumentGroup.First()), _newSolution.GetDocument(linkedDocumentGroup.First()), cancellationToken).ConfigureAwait(false); var unmergedChanges = new List(); @@ -319,16 +314,6 @@ private static IEnumerable NormalizeChanges(IEnumerable return normalizedChanges; } - private void LogLinkedFileDiffMergingSessionInfo(LinkedFileDiffMergingSessionInfo sessionInfo) - { - if (!_logSessionInfo) - { - return; - } - - LinkedFileDiffMergingLogger.LogSession(sessionInfo); - } - internal class LinkedFileDiffMergingSessionInfo { public readonly List LinkedFileGroups = new(); diff --git a/src/Workspaces/Core/Portable/Log/FileLogger.cs b/src/Workspaces/Core/Portable/Log/FileLogger.cs new file mode 100644 index 0000000000000..613abab8fee8c --- /dev/null +++ b/src/Workspaces/Core/Portable/Log/FileLogger.cs @@ -0,0 +1,115 @@ +// 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.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Internal.Log +{ + /// + /// A logger that publishes events to a log file. + /// + internal sealed class FileLogger : ILogger + { + private readonly object _gate; + private readonly string _logFilePath; + private readonly StringBuilder _buffer; + private bool _enabled; + + /// + /// Task queue to serialize all the IO to the log file. + /// + private readonly TaskQueue _taskQueue; + + public FileLogger(IGlobalOptionService optionService, string logFilePath) + { + _logFilePath = logFilePath; + _gate = new(); + _buffer = new(); + _taskQueue = new(AsynchronousOperationListenerProvider.NullListener, TaskScheduler.Default); + _enabled = optionService.GetOption(InternalDiagnosticsOptions.EnableFileLoggingForDiagnostics); + optionService.OptionChanged += OptionService_OptionChanged; + } + + public FileLogger(IGlobalOptionService optionService) + : this(optionService, Path.Combine(Path.GetTempPath(), "Roslyn", "Telemetry", GetLogFileName())) + { + } + + private static string GetLogFileName() + => DateTime.Now.ToString().Replace(' ', '_').Replace('/', '_').Replace(':', '_') + ".log"; + + private void OptionService_OptionChanged(object? sender, OptionChangedEventArgs e) + { + if (e.Option == InternalDiagnosticsOptions.EnableFileLoggingForDiagnostics) + { + Contract.ThrowIfNull(e.Value); + + _enabled = (bool)e.Value; + } + } + + public bool IsEnabled(FunctionId functionId) + { + if (!_enabled) + { + return false; + } + + // Limit logged function IDs to keep a reasonable log file size. + var str = functionId.ToString(); + return str.StartsWith("Diagnostic") || + str.StartsWith("CodeAnalysisService") || + str.StartsWith("Workspace") || + str.StartsWith("WorkCoordinator") || + str.StartsWith("IncrementalAnalyzerProcessor") || + str.StartsWith("ExternalErrorDiagnosticUpdateSource"); + } + + private void Log(FunctionId functionId, string message) + { + _taskQueue.ScheduleTask(nameof(FileLogger), () => + { + lock (_gate) + { + _buffer.AppendLine($"{DateTime.Now} ({functionId}) : {message}"); + + try + { + if (!File.Exists(_logFilePath)) + { + Directory.CreateDirectory(PathUtilities.GetDirectoryName(_logFilePath)); + } + + File.AppendAllText(_logFilePath, _buffer.ToString()); + _buffer.Clear(); + } + catch (IOException) + { + // Ignore IOException, we will log the buffer contents in next Log call. + } + } + }, CancellationToken.None); + } + + public void Log(FunctionId functionId, LogMessage logMessage) + => Log(functionId, logMessage.GetMessage()); + + public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int uniquePairId, CancellationToken cancellationToken) + => LogBlockEvent(functionId, logMessage, uniquePairId, "BlockStart"); + + public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int uniquePairId, int delta, CancellationToken cancellationToken) + => LogBlockEvent(functionId, logMessage, uniquePairId, cancellationToken.IsCancellationRequested ? "BlockCancelled" : "BlockEnd"); + + private void LogBlockEvent(FunctionId functionId, LogMessage logMessage, int uniquePairId, string blockEvent) + => Log(functionId, $"[{blockEvent} - {uniquePairId}] {logMessage.GetMessage()}"); + } +} diff --git a/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs b/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs index e7f803619055b..2cf943c2020b0 100644 --- a/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs +++ b/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs @@ -20,21 +20,25 @@ internal sealed class KeyValueLogMessage : LogMessage public static readonly KeyValueLogMessage NoProperty = new(); - public static KeyValueLogMessage Create(Action> propertySetter) + /// + /// Creates a with default , since + /// KV Log Messages are by default more informational and should be logged as such. + /// + public static KeyValueLogMessage Create(Action> propertySetter, LogLevel logLevel = LogLevel.Information) { var logMessage = s_pool.Allocate(); - logMessage.Construct(LogType.Trace, propertySetter); + logMessage.Construct(LogType.Trace, propertySetter, logLevel); return logMessage; } - public static KeyValueLogMessage Create(LogType kind) - => Create(kind, propertySetter: null); + public static KeyValueLogMessage Create(LogType kind, LogLevel logLevel = LogLevel.Information) + => Create(kind, propertySetter: null, logLevel); - public static KeyValueLogMessage Create(LogType kind, Action> propertySetter) + public static KeyValueLogMessage Create(LogType kind, Action> propertySetter, LogLevel logLevel = LogLevel.Information) { var logMessage = s_pool.Allocate(); - logMessage.Construct(kind, propertySetter); + logMessage.Construct(kind, propertySetter, logLevel); return logMessage; } @@ -49,10 +53,11 @@ private KeyValueLogMessage() _kind = LogType.Trace; } - private void Construct(LogType kind, Action> propertySetter) + private void Construct(LogType kind, Action> propertySetter, LogLevel logLevel) { _kind = kind; _propertySetter = propertySetter; + LogLevel = logLevel; } public LogType Kind => _kind; diff --git a/src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs b/src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs deleted file mode 100644 index 69d21fd4b407c..0000000000000 --- a/src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; - -namespace Microsoft.CodeAnalysis.Internal.Log -{ - internal sealed class RequestLatencyTracker : IDisposable - { - private readonly int _tick; - private readonly SyntacticLspLogger.RequestType _requestType; - - public RequestLatencyTracker(SyntacticLspLogger.RequestType requestType) - { - _tick = Environment.TickCount; - _requestType = requestType; - } - - public void Dispose() - { - var delta = Environment.TickCount - _tick; - SyntacticLspLogger.LogRequestLatency(_requestType, delta); - } - } -} diff --git a/src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs b/src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs deleted file mode 100644 index c73a839a3781c..0000000000000 --- a/src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs +++ /dev/null @@ -1,58 +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.Internal.Log -{ - internal sealed class SyntacticLspLogger - { - private static readonly HistogramLogAggregator s_histogramLogAggregator = new(bucketSize: 100, maxBucketValue: 5000); - - internal enum RequestType - { - LexicalClassifications, - SyntacticClassifications, - SyntacticTagger, - } - - internal static void LogRequestLatency(RequestType requestType, decimal latency) - => s_histogramLogAggregator.IncreaseCount(requestType, latency); - - internal static void ReportTelemetry() - { - - foreach (var kv in s_histogramLogAggregator) - { - Report((RequestType)kv.Key, kv.Value); - } - - static void Report(RequestType requestType, HistogramLogAggregator.HistogramCounter counter) - { - FunctionId functionId; - switch (requestType) - { - case RequestType.LexicalClassifications: - functionId = FunctionId.Liveshare_LexicalClassifications; - break; - case RequestType.SyntacticClassifications: - functionId = FunctionId.Liveshare_SyntacticClassifications; - break; - case RequestType.SyntacticTagger: - functionId = FunctionId.Liveshare_SyntacticTagger; - break; - default: - return; - } - - Logger.Log(functionId, KeyValueLogMessage.Create(m => - { - m[$"{requestType.ToString()}.BucketSize"] = counter.BucketSize; - m[$"{requestType.ToString()}.MaxBucketValue"] = counter.MaxBucketValue; - m[$"{requestType.ToString()}.Buckets"] = counter.GetBucketsAsString(); - })); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Log/WorkspaceErrorLogger.cs b/src/Workspaces/Core/Portable/Log/WorkspaceErrorLogger.cs index 43991ebb3ce51..152c5536eb550 100644 --- a/src/Workspaces/Core/Portable/Log/WorkspaceErrorLogger.cs +++ b/src/Workspaces/Core/Portable/Log/WorkspaceErrorLogger.cs @@ -21,7 +21,7 @@ public WorkspaceErrorLogger() } public void LogException(object source, Exception exception) - => Logger.GetLogger()?.Log(FunctionId.Extension_Exception, LogMessage.Create(source.GetType().Name + " : " + ToLogFormat(exception))); + => Logger.GetLogger()?.Log(FunctionId.Extension_Exception, LogMessage.Create(source.GetType().Name + " : " + ToLogFormat(exception), LogLevel.Error)); private static string ToLogFormat(Exception exception) => exception.Message + Environment.NewLine + exception.StackTrace; diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 88a57c943ccb0..ce4e50284c83b 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -118,6 +118,9 @@ + + + diff --git a/src/Workspaces/Core/Portable/OrganizeImports/IOrganizeImportsService.cs b/src/Workspaces/Core/Portable/OrganizeImports/IOrganizeImportsService.cs index 009e470d8c2f6..1107417a29251 100644 --- a/src/Workspaces/Core/Portable/OrganizeImports/IOrganizeImportsService.cs +++ b/src/Workspaces/Core/Portable/OrganizeImports/IOrganizeImportsService.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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; diff --git a/src/Workspaces/Core/Portable/PatternMatching/AllLowerCamelCaseMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/AllLowerCamelCaseMatcher.cs index 292ba6bb42cf5..877e827ddb4df 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/AllLowerCamelCaseMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/AllLowerCamelCaseMatcher.cs @@ -9,6 +9,7 @@ using System.Globalization; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.PatternMatching @@ -20,25 +21,22 @@ internal partial class PatternMatcher /// a candidate using CamelCase matching. i.e. this code is responsible for finding the /// match between "cofipro" and "CodeFixProvider". /// - private struct AllLowerCamelCaseMatcher + private readonly struct AllLowerCamelCaseMatcher { private readonly bool _includeMatchedSpans; private readonly string _candidate; - private readonly ArrayBuilder _candidateHumps; - private readonly TextChunk _patternChunk; private readonly string _patternText; private readonly TextInfo _textInfo; public AllLowerCamelCaseMatcher( - bool includeMatchedSpans, string candidate, - ArrayBuilder candidateHumps, TextChunk patternChunk, + bool includeMatchedSpans, + string candidate, + string patternChunkText, TextInfo textInfo) { _includeMatchedSpans = includeMatchedSpans; _candidate = candidate; - _candidateHumps = candidateHumps; - _patternChunk = patternChunk; - _patternText = _patternChunk.Text; + _patternText = patternChunkText; _textInfo = textInfo; } @@ -47,7 +45,8 @@ public AllLowerCamelCaseMatcher( /// match as found that starts at the beginning of the candidate, and 3 if a contiguous /// match was found that starts at the beginning of the candidate. /// - public PatternMatchKind? TryMatch(out ImmutableArray matchedSpans) + public PatternMatchKind? TryMatch( + in TemporaryArray candidateHumps, out ImmutableArray matchedSpans) { // We have something like cofipro and we want to match CodeFixProvider. // @@ -58,7 +57,7 @@ public AllLowerCamelCaseMatcher( // in the pattern chunk. var result = TryMatch( - patternIndex: 0, candidateHumpIndex: 0, contiguous: null); + patternIndex: 0, candidateHumpIndex: 0, contiguous: null, candidateHumps); if (result == null) { @@ -71,14 +70,14 @@ public AllLowerCamelCaseMatcher( : ImmutableArray.Empty; result?.Free(); - return GetKind(result.Value); + return GetKind(result.Value, candidateHumps); } - private PatternMatchKind GetKind(CamelCaseResult result) - => PatternMatcher.GetCamelCaseKind(result, _candidateHumps); + private static PatternMatchKind GetKind(CamelCaseResult result, in TemporaryArray candidateHumps) + => GetCamelCaseKind(result, candidateHumps); private CamelCaseResult? TryMatch( - int patternIndex, int candidateHumpIndex, bool? contiguous) + int patternIndex, int candidateHumpIndex, bool? contiguous, in TemporaryArray candidateHumps) { if (patternIndex == _patternText.Length) { @@ -96,7 +95,7 @@ private PatternMatchKind GetKind(CamelCaseResult result) // Look for a hump in the candidate that matches the current letter we're on. var patternCharacter = _patternText[patternIndex]; - for (int humpIndex = candidateHumpIndex, n = _candidateHumps.Count; humpIndex < n; humpIndex++) + for (int humpIndex = candidateHumpIndex, n = candidateHumps.Count; humpIndex < n; humpIndex++) { // If we've been contiguous, but we jumped past a hump, then we're no longer contiguous. if (contiguous.HasValue && contiguous.Value) @@ -104,7 +103,7 @@ private PatternMatchKind GetKind(CamelCaseResult result) contiguous = humpIndex == candidateHumpIndex; } - var candidateHump = _candidateHumps[humpIndex]; + var candidateHump = candidateHumps[humpIndex]; if (ToLower(_candidate[candidateHump.Start], _textInfo) == patternCharacter) { // Found a hump in the candidate string that matches the current pattern @@ -124,14 +123,14 @@ private PatternMatchKind GetKind(CamelCaseResult result) var localContiguous = contiguous == null ? true : contiguous.Value; var result = TryConsumePatternOrMatchNextHump( - patternIndex, humpIndex, localContiguous); + patternIndex, humpIndex, localContiguous, candidateHumps); if (result == null) { continue; } - if (UpdateBestResultIfBetter(result.Value, ref bestResult, matchSpanToAdd: null)) + if (UpdateBestResultIfBetter(result.Value, ref bestResult, matchSpanToAdd: null, candidateHumps)) { // We found the best result so far. We can stop immediately. break; @@ -158,11 +157,11 @@ private static char ToLowerAsciiInvariant(char c) : c; private CamelCaseResult? TryConsumePatternOrMatchNextHump( - int patternIndex, int humpIndex, bool contiguous) + int patternIndex, int humpIndex, bool contiguous, in TemporaryArray candidateHumps) { var bestResult = (CamelCaseResult?)null; - var candidateHump = _candidateHumps[humpIndex]; + var candidateHump = candidateHumps[humpIndex]; var maxPatternHumpLength = _patternText.Length - patternIndex; var maxCandidateHumpLength = candidateHump.Length; @@ -184,7 +183,7 @@ private static char ToLowerAsciiInvariant(char c) // of the candidate. var resultOpt = TryMatch( - patternIndex + possibleHumpMatchLength, humpIndex + 1, contiguous); + patternIndex + possibleHumpMatchLength, humpIndex + 1, contiguous, candidateHumps); if (resultOpt == null) { @@ -201,7 +200,7 @@ private static char ToLowerAsciiInvariant(char c) // This is the span of the hump of the candidate we matched. var matchSpanToAdd = new TextSpan(candidateHump.Start, possibleHumpMatchLength); - if (UpdateBestResultIfBetter(result, ref bestResult, matchSpanToAdd)) + if (UpdateBestResultIfBetter(result, ref bestResult, matchSpanToAdd, candidateHumps)) { // We found the best result so far. We can stop immediately. break; @@ -219,15 +218,15 @@ private static char ToLowerAsciiInvariant(char c) /// If 'weight' is better than 'bestWeight' and matchSpanToAdd is not null, then /// matchSpanToAdd will be added to matchedSpansInReverse. /// - private bool UpdateBestResultIfBetter( - CamelCaseResult result, ref CamelCaseResult? bestResult, TextSpan? matchSpanToAdd) + private static bool UpdateBestResultIfBetter( + CamelCaseResult result, ref CamelCaseResult? bestResult, TextSpan? matchSpanToAdd, in TemporaryArray candidateHumps) { if (matchSpanToAdd != null) { result = result.WithAddedMatchedSpan(matchSpanToAdd.Value); } - if (!IsBetter(result, bestResult)) + if (!IsBetter(result, bestResult, candidateHumps)) { // Even though we matched this current candidate hump we failed to match // the remainder of the pattern. Continue to the next candidate hump @@ -246,10 +245,10 @@ private bool UpdateBestResultIfBetter( // We found a path that allowed us to match everything contiguously // from the beginning. This is the best match possible. So we can // just break out now and return this result. - return GetKind(result) == PatternMatchKind.CamelCaseExact; + return GetKind(result, candidateHumps) == PatternMatchKind.CamelCaseExact; } - private bool IsBetter(CamelCaseResult result, CamelCaseResult? currentBestResult) + private static bool IsBetter(CamelCaseResult result, CamelCaseResult? currentBestResult, in TemporaryArray candidateHumps) { if (currentBestResult == null) { @@ -257,7 +256,7 @@ private bool IsBetter(CamelCaseResult result, CamelCaseResult? currentBestResult return true; } - return GetKind(result) < GetKind(currentBestResult.Value); + return GetKind(result, candidateHumps) < GetKind(currentBestResult.Value, candidateHumps); } private bool LowercaseSubstringsMatch( diff --git a/src/Workspaces/Core/Portable/PatternMatching/CamelCaseResult.cs b/src/Workspaces/Core/Portable/PatternMatching/CamelCaseResult.cs index 92b22c5e75bdc..798b4b26aff3c 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/CamelCaseResult.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/CamelCaseResult.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.PatternMatching @@ -42,7 +43,7 @@ public CamelCaseResult WithAddedMatchedSpan(TextSpan value) } } - private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result, ArrayBuilder candidateHumps) + private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result, in TemporaryArray candidateHumps) { var toEnd = result.MatchCount == candidateHumps.Count; if (result.FromStart) diff --git a/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs index a153017dc7d27..da4f39eec73c7 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.PatternMatching { @@ -43,25 +44,25 @@ public override void Dispose() } } - public override bool AddMatches(string container, ArrayBuilder matches) + public override bool AddMatches(string container, ref TemporaryArray matches) { if (SkipMatch(container)) { return false; } - return AddMatches(container, matches, fuzzyMatch: false) || - AddMatches(container, matches, fuzzyMatch: true); + return AddMatches(container, ref matches, fuzzyMatch: false) || + AddMatches(container, ref matches, fuzzyMatch: true); } - private bool AddMatches(string container, ArrayBuilder matches, bool fuzzyMatch) + private bool AddMatches(string container, ref TemporaryArray matches, bool fuzzyMatch) { if (fuzzyMatch && !_allowFuzzyMatching) { return false; } - using var _ = ArrayBuilder.GetInstance(out var tempContainerMatches); + using var tempContainerMatches = TemporaryArray.Empty; var containerParts = container.Split(_containerSplitCharacters, StringSplitOptions.RemoveEmptyEntries); @@ -80,9 +81,8 @@ private bool AddMatches(string container, ArrayBuilder matches, bo i >= 0; i--, j--) { - var segment = _patternSegments[i]; var containerName = containerParts[j]; - if (!MatchPatternSegment(containerName, segment, tempContainerMatches, fuzzyMatch)) + if (!MatchPatternSegment(containerName, _patternSegments[i], ref tempContainerMatches.AsRef(), fuzzyMatch)) { // This container didn't match the pattern piece. So there's no match at all. return false; diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.PatternSegment.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.PatternSegment.cs index 954e2a3365ffe..5f0df83552c97 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.PatternSegment.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.PatternSegment.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.PatternMatching { @@ -16,12 +17,13 @@ internal partial class PatternMatcher /// text between the dots, as well as information about any individual 'Words' that we /// can break the segment into. /// + [NonCopyable] private struct PatternSegment : IDisposable { // Information about the entire piece of text between the dots. For example, if the // text between the dots is 'Get-Keyword', then TotalTextChunk.Text will be 'Get-Keyword' and // TotalTextChunk.CharacterSpans will correspond to 'G', 'et', 'K' and 'eyword'. - public readonly TextChunk TotalTextChunk; + public TextChunk TotalTextChunk; // Information about the subwords compromising the total word. For example, if the // text between the dots is 'Get-Keyword', then the subwords will be 'Get' and 'Keyword' @@ -44,7 +46,7 @@ public void Dispose() } } - public bool IsInvalid => this.SubWordTextChunks.Length == 0; + public readonly bool IsInvalid => this.SubWordTextChunks.Length == 0; private static int CountTextChunks(string pattern) { diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.TextChunk.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.TextChunk.cs index c74952d9d2df4..c804a0c428db2 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.TextChunk.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.TextChunk.cs @@ -5,7 +5,7 @@ #nullable disable using System; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -20,6 +20,7 @@ internal partial class PatternMatcher /// capitalized runs and lowercase runs. i.e. if you have AAbb, then there will be two /// character spans, one for AA and one for BB. /// + [NonCopyable] private struct TextChunk : IDisposable { public readonly string Text; @@ -29,7 +30,7 @@ private struct TextChunk : IDisposable /// capitalized runs and lowercase runs. i.e. if you have AAbb, then there will be two /// character spans, one for AA and one for BB. /// - public readonly ArrayBuilder PatternHumps; + public TemporaryArray PatternHumps; public readonly WordSimilarityChecker SimilarityChecker; @@ -38,7 +39,9 @@ private struct TextChunk : IDisposable public TextChunk(string text, bool allowFuzzingMatching) { this.Text = text; - this.PatternHumps = StringBreaker.GetCharacterParts(text); + PatternHumps = TemporaryArray.Empty; + StringBreaker.AddCharacterParts(text, ref PatternHumps); + this.SimilarityChecker = allowFuzzingMatching ? WordSimilarityChecker.Allocate(text, substringsAreSimilar: false) : null; @@ -48,7 +51,7 @@ public TextChunk(string text, bool allowFuzzingMatching) public void Dispose() { - this.PatternHumps.Free(); + this.PatternHumps.Dispose(); this.SimilarityChecker?.Free(); } } diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs index 2062c49aecb5c..b5b05d1c84d37 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs @@ -10,6 +10,7 @@ using System.Globalization; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -101,7 +102,7 @@ internal static (string name, string containerOpt) GetNameAndContainer(string pa : (name: pattern, containerOpt: null); } - public abstract bool AddMatches(string candidate, ArrayBuilder matches); + public abstract bool AddMatches(string candidate, ref TemporaryArray matches); private bool SkipMatch(string candidate) => _invalidPattern || string.IsNullOrWhiteSpace(candidate); @@ -122,7 +123,7 @@ private static bool ContainsUpperCaseLetter(string pattern) private PatternMatch? MatchPatternChunk( string candidate, - TextChunk patternChunk, + in TextChunk patternChunk, bool punctuationStripped, bool fuzzyMatch) { @@ -133,7 +134,7 @@ private static bool ContainsUpperCaseLetter(string pattern) private static PatternMatch? FuzzyMatchPatternChunk( string candidate, - TextChunk patternChunk, + in TextChunk patternChunk, bool punctuationStripped) { if (patternChunk.SimilarityChecker.AreSimilar(candidate)) @@ -147,7 +148,7 @@ private static bool ContainsUpperCaseLetter(string pattern) private PatternMatch? NonFuzzyMatchPatternChunk( string candidate, - TextChunk patternChunk, + in TextChunk patternChunk, bool punctuationStripped) { var candidateLength = candidate.Length; @@ -174,106 +175,100 @@ private static bool ContainsUpperCaseLetter(string pattern) } } - ArrayBuilder candidateHumpsOpt = null; - try + using var candidateHumps = TemporaryArray.Empty; + + var patternIsLowercase = patternChunk.IsLowercase; + if (caseInsensitiveIndex > 0) { - var patternIsLowercase = patternChunk.IsLowercase; - if (caseInsensitiveIndex > 0) - { - // We found the pattern somewhere in the candidate. This could be a substring match. - // However, we don't want to be overaggressive in returning just any substring results. - // So do a few more checks to make sure this is a good result. + // We found the pattern somewhere in the candidate. This could be a substring match. + // However, we don't want to be overaggressive in returning just any substring results. + // So do a few more checks to make sure this is a good result. - if (!patternIsLowercase) - { - // Pattern contained uppercase letters. This is a strong indication from the - // user that they expect the same letters to be uppercase in the result. As - // such, only return this if we can find this pattern exactly in the candidate. + if (!patternIsLowercase) + { + // Pattern contained uppercase letters. This is a strong indication from the + // user that they expect the same letters to be uppercase in the result. As + // such, only return this if we can find this pattern exactly in the candidate. - var caseSensitiveIndex = _compareInfo.IndexOf(candidate, patternChunk.Text, CompareOptions.None); - if (caseSensitiveIndex > 0) - { - if (char.IsUpper(candidate[caseInsensitiveIndex])) - { - return new PatternMatch( - PatternMatchKind.StartOfWordSubstring, punctuationStripped, isCaseSensitive: true, - matchedSpan: GetMatchedSpan(caseInsensitiveIndex, patternChunk.Text.Length)); - } - else - { - return new PatternMatch( - PatternMatchKind.NonLowercaseSubstring, punctuationStripped, isCaseSensitive: true, - matchedSpan: GetMatchedSpan(caseSensitiveIndex, patternChunk.Text.Length)); - } - } - } - else + var caseSensitiveIndex = _compareInfo.IndexOf(candidate, patternChunk.Text, CompareOptions.None); + if (caseSensitiveIndex > 0) { - // Pattern was all lowercase. This can lead to lots of hits. For example, "bin" in - // "CombineUnits". Instead, we want it to match "Operator[|Bin|]ary" first rather than - // Com[|bin|]eUnits - - // If the lowercase search string matched what looks to be the start of a word then that's a - // reasonable hit. This is equivalent to 'bin' matching 'Operator[|Bin|]ary' if (char.IsUpper(candidate[caseInsensitiveIndex])) { - return new PatternMatch(PatternMatchKind.StartOfWordSubstring, punctuationStripped, - isCaseSensitive: false, + return new PatternMatch( + PatternMatchKind.StartOfWordSubstring, punctuationStripped, isCaseSensitive: true, matchedSpan: GetMatchedSpan(caseInsensitiveIndex, patternChunk.Text.Length)); } - - // Now do the more expensive check to see if we're at the start of a word. This is to catch - // word matches like CombineBinary. We want to find the hit against '[|Bin|]ary' not - // 'Com[|bin|]e' - candidateHumpsOpt = StringBreaker.GetWordParts(candidate); - for (int i = 0, n = candidateHumpsOpt.Count; i < n; i++) + else { - var hump = TextSpan.FromBounds(candidateHumpsOpt[i].Start, candidateLength); - if (PartStartsWith(candidate, hump, patternChunk.Text, CompareOptions.IgnoreCase)) - { - return new PatternMatch(PatternMatchKind.StartOfWordSubstring, punctuationStripped, - isCaseSensitive: PartStartsWith(candidate, hump, patternChunk.Text, CompareOptions.None), - matchedSpan: GetMatchedSpan(hump.Start, patternChunk.Text.Length)); - } + return new PatternMatch( + PatternMatchKind.NonLowercaseSubstring, punctuationStripped, isCaseSensitive: true, + matchedSpan: GetMatchedSpan(caseSensitiveIndex, patternChunk.Text.Length)); } } } + else + { + // Pattern was all lowercase. This can lead to lots of hits. For example, "bin" in + // "CombineUnits". Instead, we want it to match "Operator[|Bin|]ary" first rather than + // Com[|bin|]eUnits - // Didn't have an exact/prefix match, or a high enough quality substring match. - // See if we can find a camel case match. - if (candidateHumpsOpt == null) - candidateHumpsOpt = StringBreaker.GetWordParts(candidate); + // If the lowercase search string matched what looks to be the start of a word then that's a + // reasonable hit. This is equivalent to 'bin' matching 'Operator[|Bin|]ary' + if (char.IsUpper(candidate[caseInsensitiveIndex])) + { + return new PatternMatch(PatternMatchKind.StartOfWordSubstring, punctuationStripped, + isCaseSensitive: false, + matchedSpan: GetMatchedSpan(caseInsensitiveIndex, patternChunk.Text.Length)); + } - // Didn't have an exact/prefix match, or a high enough quality substring match. - // See if we can find a camel case match. - var match = TryCamelCaseMatch(candidate, patternChunk, punctuationStripped, patternIsLowercase, candidateHumpsOpt); - if (match != null) - return match; - - // If pattern was all lowercase, we allow it to match an all lowercase section of the candidate. But - // only after we've tried all other forms first. This is the weakest of all matches. For example, if - // user types 'bin' we want to match 'OperatorBinary' (start of word) or 'BinaryInformationNode' (camel - // humps) before matching 'Combine'. - // - // We only do this for strings longer than three characters to avoid too many false positives when the - // user has only barely started writing a word. - if (patternIsLowercase && caseInsensitiveIndex > 0 && patternChunk.Text.Length >= 3) - { - var caseSensitiveIndex = _compareInfo.IndexOf(candidate, patternChunk.Text, CompareOptions.None); - if (caseSensitiveIndex > 0) + // Now do the more expensive check to see if we're at the start of a word. This is to catch + // word matches like CombineBinary. We want to find the hit against '[|Bin|]ary' not + // 'Com[|bin|]e' + StringBreaker.AddWordParts(candidate, ref candidateHumps.AsRef()); + for (int i = 0, n = candidateHumps.Count; i < n; i++) { - return new PatternMatch( - PatternMatchKind.LowercaseSubstring, punctuationStripped, isCaseSensitive: true, - matchedSpan: GetMatchedSpan(caseSensitiveIndex, patternChunk.Text.Length)); + var hump = TextSpan.FromBounds(candidateHumps[i].Start, candidateLength); + if (PartStartsWith(candidate, hump, patternChunk.Text, CompareOptions.IgnoreCase)) + { + return new PatternMatch(PatternMatchKind.StartOfWordSubstring, punctuationStripped, + isCaseSensitive: PartStartsWith(candidate, hump, patternChunk.Text, CompareOptions.None), + matchedSpan: GetMatchedSpan(hump.Start, patternChunk.Text.Length)); + } } } - - return null; } - finally + + // Didn't have an exact/prefix match, or a high enough quality substring match. + // See if we can find a camel case match. + if (candidateHumps.Count == 0) + StringBreaker.AddWordParts(candidate, ref candidateHumps.AsRef()); + + // Didn't have an exact/prefix match, or a high enough quality substring match. + // See if we can find a camel case match. + var match = TryCamelCaseMatch(candidate, patternChunk, punctuationStripped, patternIsLowercase, candidateHumps); + if (match != null) + return match; + + // If pattern was all lowercase, we allow it to match an all lowercase section of the candidate. But + // only after we've tried all other forms first. This is the weakest of all matches. For example, if + // user types 'bin' we want to match 'OperatorBinary' (start of word) or 'BinaryInformationNode' (camel + // humps) before matching 'Combine'. + // + // We only do this for strings longer than three characters to avoid too many false positives when the + // user has only barely started writing a word. + if (patternIsLowercase && caseInsensitiveIndex > 0 && patternChunk.Text.Length >= 3) { - candidateHumpsOpt?.Free(); + var caseSensitiveIndex = _compareInfo.IndexOf(candidate, patternChunk.Text, CompareOptions.None); + if (caseSensitiveIndex > 0) + { + return new PatternMatch( + PatternMatchKind.LowercaseSubstring, punctuationStripped, isCaseSensitive: true, + matchedSpan: GetMatchedSpan(caseSensitiveIndex, patternChunk.Text.Length)); + } } + + return null; } private TextSpan? GetMatchedSpan(int start, int length) @@ -309,8 +304,8 @@ private static bool ContainsSpaceOrAsterisk(string text) /// If there's only one match, then the return value is that match. Otherwise it is null. private bool MatchPatternSegment( string candidate, - PatternSegment segment, - ArrayBuilder matches, + in PatternSegment segment, + ref TemporaryArray matches, bool fuzzyMatch) { if (fuzzyMatch && !_allowFuzzyMatching) @@ -367,7 +362,7 @@ private bool MatchPatternSegment( } else { - using var _ = ArrayBuilder.GetInstance(out var tempMatches); + using var tempMatches = TemporaryArray.Empty; foreach (var subWordTextChunk in subWordTextChunks) { @@ -375,9 +370,7 @@ private bool MatchPatternSegment( var result = MatchPatternChunk( candidate, subWordTextChunk, punctuationStripped: true, fuzzyMatch: fuzzyMatch); if (result == null) - { return false; - } tempMatches.Add(result.Value); } @@ -425,9 +418,11 @@ private bool PartStartsWith(string candidate, TextSpan candidatePart, string pat => PartStartsWith(candidate, candidatePart, pattern, new TextSpan(0, pattern.Length), compareOptions); private PatternMatch? TryCamelCaseMatch( - string candidate, TextChunk patternChunk, - bool punctuationStripped, bool isLowercase, - ArrayBuilder candidateHumps) + string candidate, + in TextChunk patternChunk, + bool punctuationStripped, + bool isLowercase, + in TemporaryArray candidateHumps) { if (isLowercase) { @@ -471,23 +466,22 @@ private bool PartStartsWith(string candidate, TextSpan candidatePart, string pat private PatternMatchKind? TryAllLowerCamelCaseMatch( string candidate, - ArrayBuilder candidateHumps, - TextChunk patternChunk, + in TemporaryArray candidateHumps, + in TextChunk patternChunk, out ImmutableArray matchedSpans) { - var matcher = new AllLowerCamelCaseMatcher( - _includeMatchedSpans, candidate, candidateHumps, patternChunk, _textInfo); - return matcher.TryMatch(out matchedSpans); + var matcher = new AllLowerCamelCaseMatcher(_includeMatchedSpans, candidate, patternChunk.Text, _textInfo); + return matcher.TryMatch(candidateHumps, out matchedSpans); } private PatternMatchKind? TryUpperCaseCamelCaseMatch( string candidate, - ArrayBuilder candidateHumps, - TextChunk patternChunk, + in TemporaryArray candidateHumps, + in TextChunk patternChunk, CompareOptions compareOption, out ImmutableArray matchedSpans) { - var patternHumps = patternChunk.PatternHumps; + ref readonly var patternHumps = ref patternChunk.PatternHumps; // Note: we may have more pattern parts than candidate parts. This is because multiple // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs index 226b60906085d..8be89a298b1b7 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.PatternMatching { @@ -12,9 +10,9 @@ internal static class PatternMatcherExtensions { public static PatternMatch? GetFirstMatch(this PatternMatcher matcher, string candidate) { - using var _ = ArrayBuilder.GetInstance(out var matches); - matcher.AddMatches(candidate, matches); - return matches.Any() ? (PatternMatch?)matches.First() : null; + using var matches = TemporaryArray.Empty; + matcher.AddMatches(candidate, ref matches.AsRef()); + return matches.Count > 0 ? (PatternMatch?)matches[0] : null; } public static bool Matches(this PatternMatcher matcher, string candidate) diff --git a/src/Workspaces/Core/Portable/PatternMatching/SimplePatternMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/SimplePatternMatcher.cs index bc94ede1f05d6..1e1cb9fb9d8c0 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/SimplePatternMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/SimplePatternMatcher.cs @@ -6,6 +6,7 @@ using System.Globalization; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.PatternMatching { @@ -13,7 +14,7 @@ internal partial class PatternMatcher { private sealed partial class SimplePatternMatcher : PatternMatcher { - private readonly PatternSegment _fullPatternSegment; + private PatternSegment _fullPatternSegment; public SimplePatternMatcher( string pattern, @@ -40,15 +41,15 @@ public override void Dispose() /// /// If this was a match, a set of match types that occurred while matching the /// patterns. If it was not a match, it returns null. - public override bool AddMatches(string candidate, ArrayBuilder matches) + public override bool AddMatches(string candidate, ref TemporaryArray matches) { if (SkipMatch(candidate)) { return false; } - return MatchPatternSegment(candidate, _fullPatternSegment, matches, fuzzyMatch: false) || - MatchPatternSegment(candidate, _fullPatternSegment, matches, fuzzyMatch: true); + return MatchPatternSegment(candidate, in _fullPatternSegment, ref matches, fuzzyMatch: false) || + MatchPatternSegment(candidate, in _fullPatternSegment, ref matches, fuzzyMatch: true); } } } diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt index 6e9efc056ffb1..d38ba56f7da58 100644 --- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt @@ -1,9 +1,9 @@ abstract Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.FixAllAsync(Microsoft.CodeAnalysis.CodeFixes.FixAllContext fixAllContext, Microsoft.CodeAnalysis.Document document, System.Collections.Immutable.ImmutableArray diagnostics) -> System.Threading.Tasks.Task const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.RecordClassName = "record class name" -> string -Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.set -> void +const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.RecordStructName = "record struct name" -> string Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.DocumentBasedFixAllProvider() -> void +Microsoft.CodeAnalysis.CommandLineProject Microsoft.CodeAnalysis.Host.IPersistentStorageService.GetStorageAsync(Microsoft.CodeAnalysis.Solution solution, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.CodeAnalysis.Project.GetSourceGeneratedDocumentAsync(Microsoft.CodeAnalysis.DocumentId documentId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.CodeAnalysis.Project.GetSourceGeneratedDocumentsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> @@ -13,5 +13,7 @@ Microsoft.CodeAnalysis.SourceGeneratedDocument Microsoft.CodeAnalysis.SourceGeneratedDocument.HintName.get -> string override sealed Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.GetFixAsync(Microsoft.CodeAnalysis.CodeFixes.FixAllContext fixAllContext) -> System.Threading.Tasks.Task override sealed Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.GetSupportedFixAllScopes() -> System.Collections.Generic.IEnumerable +static Microsoft.CodeAnalysis.CommandLineProject.CreateProjectInfo(string projectName, string language, string commandLine, string baseDirectory, Microsoft.CodeAnalysis.Workspace workspace = null) -> Microsoft.CodeAnalysis.ProjectInfo +static Microsoft.CodeAnalysis.CommandLineProject.CreateProjectInfo(string projectName, string language, System.Collections.Generic.IEnumerable commandLineArgs, string projectDirectory, Microsoft.CodeAnalysis.Workspace workspace = null) -> Microsoft.CodeAnalysis.ProjectInfo virtual Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.GetFixAllTitle(Microsoft.CodeAnalysis.CodeFixes.FixAllContext fixAllContext) -> string static Microsoft.CodeAnalysis.CodeFixes.FixAllProvider.Create(System.Func, System.Threading.Tasks.Task> fixAllAsync) -> Microsoft.CodeAnalysis.CodeFixes.FixAllProvider diff --git a/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs b/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs new file mode 100644 index 0000000000000..3315b0d0793a2 --- /dev/null +++ b/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs @@ -0,0 +1,331 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ReassignedVariable +{ + internal abstract class AbstractReassignedVariableService< + TParameterSyntax, + TVariableSyntax, + TSingleVariableDesignationSyntax, + TIdentifierNameSyntax> + : IReassignedVariableService + where TParameterSyntax : SyntaxNode + where TVariableSyntax : SyntaxNode + where TSingleVariableDesignationSyntax : SyntaxNode + where TIdentifierNameSyntax : SyntaxNode + { + protected abstract SyntaxNode GetParentScope(SyntaxNode localDeclaration); + protected abstract SyntaxNode GetMemberBlock(SyntaxNode methodOrPropertyDeclaration); + + protected abstract bool HasInitializer(SyntaxNode variable); + protected abstract SyntaxToken GetIdentifierOfVariable(TVariableSyntax variable); + protected abstract SyntaxToken GetIdentifierOfSingleVariableDesignation(TSingleVariableDesignationSyntax variable); + + public async Task> GetLocationsAsync( + Document document, TextSpan span, CancellationToken cancellationToken) + { + var semanticFacts = document.GetRequiredLanguageService(); + var syntaxFacts = document.GetRequiredLanguageService(); + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + using var _1 = PooledDictionary.GetInstance(out var symbolToIsReassigned); + using var _2 = ArrayBuilder.GetInstance(out var result); + using var _3 = ArrayBuilder.GetInstance(out var stack); + + // Walk through all the nodes in the provided span. Directly analyze local or parameter declaration. And + // also analyze any identifiers which might be reference to locals or parameters. Note that we might hit + // locals/parameters without any references in the span, or references that don't have the declarations in + // the span + stack.Add(root.FindNode(span)); + + // Use a stack so we don't blow out the stack with recursion. + while (stack.Count > 0) + { + var current = stack.Last(); + stack.RemoveLast(); + + if (current.Span.IntersectsWith(span)) + { + ProcessNode(current); + + foreach (var child in current.ChildNodesAndTokens()) + { + if (child.IsNode) + stack.Add(child.AsNode()!); + } + } + } + + result.RemoveDuplicates(); + return result.ToImmutable(); + + void ProcessNode(SyntaxNode node) + { + switch (node) + { + case TIdentifierNameSyntax identifier: + ProcessIdentifier(identifier); + break; + case TParameterSyntax parameter: + ProcessParameter(parameter); + break; + case TVariableSyntax variable: + ProcessVariable(variable); + break; + case TSingleVariableDesignationSyntax designation: + ProcessSingleVariableDesignation(designation); + break; + } + } + + void ProcessIdentifier(TIdentifierNameSyntax identifier) + { + // Don't bother even looking at identifiers that aren't standalone (i.e. they're not on the left of some + // expression). These could not refer to locals or fields. + if (syntaxFacts.GetStandaloneExpression(identifier) != identifier) + return; + + var symbol = semanticModel.GetSymbolInfo(identifier, cancellationToken).Symbol; + if (IsSymbolReassigned(symbol)) + result.Add(identifier.Span); + } + + void ProcessParameter(TParameterSyntax parameterSyntax) + { + var parameter = semanticModel.GetDeclaredSymbol(parameterSyntax, cancellationToken) as IParameterSymbol; + if (IsSymbolReassigned(parameter)) + result.Add(syntaxFacts.GetIdentifierOfParameter(parameterSyntax).Span); + } + + void ProcessVariable(TVariableSyntax variable) + { + var local = semanticModel.GetDeclaredSymbol(variable, cancellationToken) as ILocalSymbol; + if (IsSymbolReassigned(local)) + result.Add(GetIdentifierOfVariable(variable).Span); + } + + void ProcessSingleVariableDesignation(TSingleVariableDesignationSyntax designation) + { + var local = semanticModel.GetDeclaredSymbol(designation, cancellationToken) as ILocalSymbol; + if (IsSymbolReassigned(local)) + result.Add(GetIdentifierOfSingleVariableDesignation(designation).Span); + } + + bool IsSymbolReassigned([NotNullWhen(true)] ISymbol? symbol) + { + // Note: we don't need to test range variables, as they are never reassignable. + if (symbol is not IParameterSymbol and not ILocalSymbol) + return false; + + if (!symbolToIsReassigned.TryGetValue(symbol, out var reassignedResult)) + { + reassignedResult = symbol is IParameterSymbol parameter + ? ComputeParameterIsAssigned(parameter) + : ComputeLocalIsAssigned((ILocalSymbol)symbol); + symbolToIsReassigned[symbol] = reassignedResult; + } + + return reassignedResult; + } + + bool ComputeParameterIsAssigned(IParameterSymbol parameter) + { + if (!TryGetParameterLocation(parameter, out var parameterLocation)) + return false; + + var methodOrProperty = parameter.ContainingSymbol; + + // If we're on an accessor parameter. Map up to the matching parameter for the property/indexer. + if (methodOrProperty is IMethodSymbol { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet } method) + methodOrProperty = method.AssociatedSymbol as IPropertySymbol; + + if (methodOrProperty is not IMethodSymbol and not IPropertySymbol) + return false; + + if (methodOrProperty.DeclaringSyntaxReferences.Length == 0) + return false; + + // Be resilient to cases where the parameter might have multiple locations. This + // should not normally happen, but we want to be resilient in case it occurs in + // error scenarios. + var methodOrPropertyDeclaration = methodOrProperty.DeclaringSyntaxReferences.First().GetSyntax(cancellationToken); + if (methodOrPropertyDeclaration.SyntaxTree != semanticModel.SyntaxTree) + return false; + + // All parameters (except for 'out' parameters), come in definitely assigned. + return AnalyzePotentialMatches( + parameter, + parameterLocation, + symbolIsDefinitelyAssigned: parameter.RefKind != RefKind.Out, + GetMemberBlock(methodOrPropertyDeclaration)); + } + + bool TryGetParameterLocation(IParameterSymbol parameter, out TextSpan location) + { + // Be resilient to cases where the parameter might have multiple locations. This + // should not normally happen, but we want to be resilient in case it occurs in + // error scenarios. + if (parameter.Locations.Length > 0) + { + var parameterLocation = parameter.Locations[0]; + if (parameterLocation.SourceTree == semanticModel.SyntaxTree) + { + location = parameterLocation.SourceSpan; + return true; + } + } + else if (parameter.ContainingSymbol.Name == WellKnownMemberNames.TopLevelStatementsEntryPointMethodName) + { + // If this is a parameter of the top-level-main function, then the entire span of the compilation + // unit is what we need to examine. + location = default; + return true; + } + + location = default; + return false; + } + + bool ComputeLocalIsAssigned(ILocalSymbol local) + { + if (local.DeclaringSyntaxReferences.Length == 0) + return false; + + var localDeclaration = local.DeclaringSyntaxReferences.First().GetSyntax(cancellationToken); + if (localDeclaration.SyntaxTree != semanticModel.SyntaxTree) + { + Contract.Fail("Local did not come from same file that we were analyzing?"); + return false; + } + + // A local is definitely assigned during analysis if it had an initializer. + return AnalyzePotentialMatches( + local, + localDeclaration.Span, + symbolIsDefinitelyAssigned: HasInitializer(localDeclaration), + GetParentScope(localDeclaration)); + } + + bool AnalyzePotentialMatches( + ISymbol localOrParameter, + TextSpan localOrParameterDeclarationSpan, + bool symbolIsDefinitelyAssigned, + SyntaxNode parentScope) + { + // Now, walk the scope, looking for all usages of the local. See if any are a reassignment. + using var _ = ArrayBuilder.GetInstance(out var stack); + stack.Push(parentScope); + + while (stack.Count != 0) + { + var current = stack.Last(); + stack.RemoveLast(); + + foreach (var child in current.ChildNodesAndTokens()) + { + if (child.IsNode) + stack.Add(child.AsNode()!); + } + + // Ignore any nodes before the decl. + if (current.SpanStart <= localOrParameterDeclarationSpan.Start) + continue; + + // Only examine identifiers. + if (current is not TIdentifierNameSyntax id) + continue; + + // Ignore identifiers that don't match the local name. + var idToken = syntaxFacts.GetIdentifierOfSimpleName(id); + if (!syntaxFacts.StringComparer.Equals(idToken.ValueText, localOrParameter.Name)) + continue; + + // Ignore identifiers that bind to another symbol. + var symbol = semanticModel.GetSymbolInfo(id, cancellationToken).Symbol; + if (!AreEquivalent(localOrParameter, symbol)) + continue; + + // Ok, we have a reference to the local. See if it was assigned on entry. If not, we don't care + // about this reference. As an assignment here doesn't mean it was reassigned. + // + // If we can statically tell it was definitely assigned, skip the more expensive dataflow check. + if (!symbolIsDefinitelyAssigned) + { + var dataFlow = semanticModel.AnalyzeDataFlow(id); + if (!DefinitelyAssignedOnEntry(dataFlow, localOrParameter)) + continue; + } + + // This was a variable that was already assigned prior to this location. See if this location is + // considered a write. + if (semanticFacts.IsWrittenTo(semanticModel, id, cancellationToken)) + return true; + } + + return false; + } + + bool AreEquivalent(ISymbol localOrParameter, ISymbol? symbol) + { + if (symbol == null) + return false; + + if (localOrParameter.Equals(symbol)) + return true; + + if (localOrParameter.Kind != symbol.Kind) + return false; + + // Special case for property parameters. When we bind to references, we'll bind to the parameters on + // the accessor methods. We need to map these back to the property parameter to see if we have a hit. + if (localOrParameter is IParameterSymbol { ContainingSymbol: IPropertySymbol property } parameter) + { + var getParameter = property.GetMethod?.Parameters[parameter.Ordinal]; + var setParameter = property.SetMethod?.Parameters[parameter.Ordinal]; + return Equals(getParameter, symbol) || Equals(setParameter, symbol); + } + + return false; + } + + bool DefinitelyAssignedOnEntry(DataFlowAnalysis? analysis, ISymbol? localOrParameter) + { + if (analysis == null) + return false; + + if (localOrParameter == null) + return false; + + if (analysis.DefinitelyAssignedOnEntry.Contains(localOrParameter)) + return true; + + // Special case for property parameters. When we bind to references, we'll bind to the parameters on + // the accessor methods. We need to map these back to the property parameter to see if we have a hit. + if (localOrParameter is IParameterSymbol { ContainingSymbol: IPropertySymbol property } parameter) + { + var getParameter = property.GetMethod?.Parameters[parameter.Ordinal]; + var setParameter = property.SetMethod?.Parameters[parameter.Ordinal]; + return DefinitelyAssignedOnEntry(analysis, getParameter) || + DefinitelyAssignedOnEntry(analysis, setParameter); + } + + return false; + } + } + } +} diff --git a/src/Workspaces/Core/Portable/ReassignedVariable/IReassignedVariableService.cs b/src/Workspaces/Core/Portable/ReassignedVariable/IReassignedVariableService.cs new file mode 100644 index 0000000000000..b72f6ae19e2cc --- /dev/null +++ b/src/Workspaces/Core/Portable/ReassignedVariable/IReassignedVariableService.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ReassignedVariable +{ + /// + /// Service which can analyze a span of a document and identify all locations of parameters or locals that are ever + /// reassigned. Note that the locations provided are not the reassignment points. Rather if a local or parameter + /// is ever reassigned, these are all the locations of those locals or parameters within that span. + /// + internal interface IReassignedVariableService : ILanguageService + { + Task> GetLocationsAsync(Document document, TextSpan span, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index ee978a90e8820..2e12f9f7fac59 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -5,7 +5,9 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -19,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Remote #region FindReferences [DataContract] - internal sealed class SerializableSymbolAndProjectId + internal sealed class SerializableSymbolAndProjectId : IEquatable { [DataMember(Order = 0)] public readonly string SymbolKeyData; @@ -33,6 +35,21 @@ public SerializableSymbolAndProjectId(string symbolKeyData, ProjectId projectId) ProjectId = projectId; } + public override bool Equals(object obj) + => Equals(obj as SerializableSymbolAndProjectId); + + public bool Equals(SerializableSymbolAndProjectId other) + { + if (this == other) + return true; + + return this.ProjectId == other?.ProjectId && + this.SymbolKeyData == other?.SymbolKeyData; + } + + public override int GetHashCode() + => Hash.Combine(this.SymbolKeyData, this.ProjectId.GetHashCode()); + public static SerializableSymbolAndProjectId Dehydrate( IAliasSymbol alias, Document document, CancellationToken cancellationToken) { @@ -80,6 +97,7 @@ public static bool TryCreate( result = new SerializableSymbolAndProjectId(SymbolKey.CreateString(symbol, cancellationToken), project.Id); return true; } + public async Task TryRehydrateAsync( Solution solution, CancellationToken cancellationToken) { @@ -167,7 +185,7 @@ public static SerializableReferenceLocation Dehydrate( public async Task RehydrateAsync( Solution solution, CancellationToken cancellationToken) { - var document = solution.GetDocument(this.Document); + var document = await solution.GetDocumentAsync(this.Document, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var aliasSymbol = await RehydrateAliasAsync(solution, cancellationToken).ConfigureAwait(false); var additionalProperties = this.AdditionalProperties; @@ -192,5 +210,49 @@ private async Task RehydrateAliasAsync( } } + [DataContract] + internal class SerializableSymbolGroup : IEquatable + { + [DataMember(Order = 0)] + public readonly HashSet Symbols; + + private int _hashCode; + + public SerializableSymbolGroup(HashSet symbols) + { + Symbols = new HashSet(symbols); + } + + public override bool Equals(object obj) + => obj is SerializableSymbolGroup group && Equals(group); + + public bool Equals(SerializableSymbolGroup other) + { + if (this == other) + return true; + + return other != null && this.Symbols.SetEquals(other.Symbols); + } + + public override int GetHashCode() + { + if (_hashCode == 0) + { + var hashCode = 0; + foreach (var symbol in Symbols) + hashCode += symbol.SymbolKeyData.GetHashCode(); + _hashCode = hashCode; + } + + return _hashCode; + } + + public static SerializableSymbolGroup Dehydrate(Solution solution, SymbolGroup group, CancellationToken cancellationToken) + { + return new SerializableSymbolGroup(new HashSet( + group.Symbols.Select(s => SerializableSymbolAndProjectId.Dehydrate(solution, s, cancellationToken)))); + } + } + #endregion } diff --git a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs index d02d388b52d29..e5f00ba7645a1 100644 --- a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs +++ b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs @@ -26,6 +26,7 @@ internal enum WellKnownSynchronizationKind SolutionAttributes, ProjectAttributes, DocumentAttributes, + SourceGeneratedDocumentIdentity, CompilationOptions, ParseOptions, @@ -36,7 +37,6 @@ internal enum WellKnownSynchronizationKind OptionSet, SerializableSourceText, - RecoverableSourceText, // diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index b2261a39a2e85..a3ba3ff2515df 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -595,7 +595,7 @@ private async Task CheckForConflictAsync( if (conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].IsOverriddenFromMetadata) { - var overridingSymbol = await SymbolFinder.FindSymbolAtPositionAsync(solution.GetDocument(newLocation.SourceTree), newLocation.SourceSpan.Start, cancellationToken: _cancellationToken).ConfigureAwait(false); + var overridingSymbol = await SymbolFinder.FindSymbolAtPositionAsync(solution.GetRequiredDocument(newLocation.SourceTree), newLocation.SourceSpan.Start, cancellationToken: _cancellationToken).ConfigureAwait(false); if (overridingSymbol != null && !Equals(renamedSymbolInNewSolution, overridingSymbol)) { if (!overridingSymbol.IsOverride) @@ -676,7 +676,7 @@ private async Task GetRenamedSymbolInCurrentSolutionAsync(MutableConfli ? conflictResolution.GetAdjustedTokenStartingPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration) : _renameSymbolDeclarationLocation.SourceSpan.Start; - var document = conflictResolution.CurrentSolution.GetDocument(_documentIdOfRenameSymbolDeclaration); + var document = conflictResolution.CurrentSolution.GetRequiredDocument(_documentIdOfRenameSymbolDeclaration); var newSymbol = await SymbolFinder.FindSymbolAtPositionAsync(document, start, cancellationToken: _cancellationToken).ConfigureAwait(false); return newSymbol; } diff --git a/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs b/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs index da0cff41f0a49..0fbb42ff3b1a9 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs @@ -299,8 +299,7 @@ public static async Task> GetRenamableDefinitionL if (originalSymbol.Kind == SymbolKind.Alias) { var location = originalSymbol.Locations.Single(); - Contract.ThrowIfNull(location.SourceTree); - results.Add(new RenameLocation(location, solution.GetRequiredDocument(location.SourceTree).Id)); + AddRenameLocationIfNotGenerated(location); return results.ToImmutableAndFree(); } @@ -309,11 +308,7 @@ public static async Task> GetRenamableDefinitionL { if (location.IsInSource) { - Contract.ThrowIfNull(location.SourceTree); - results.Add(new RenameLocation( - location, - solution.GetRequiredDocument(location.SourceTree).Id, - isRenamableAccessor: isRenamableAccessor)); + AddRenameLocationIfNotGenerated(location, isRenamableAccessor); } } @@ -322,8 +317,7 @@ public static async Task> GetRenamableDefinitionL if (referencedSymbol.Kind == SymbolKind.NamedType && referencedSymbol.Locations.All(l => l.IsInSource)) { var firstLocation = referencedSymbol.Locations[0]; - Contract.ThrowIfNull(firstLocation.SourceTree); - var syntaxFacts = solution.GetRequiredDocument(firstLocation.SourceTree) + var syntaxFacts = solution.GetRequiredDocument(firstLocation.SourceTree!) .GetRequiredLanguageService(); var namedType = (INamedTypeSymbol)referencedSymbol; @@ -341,8 +335,7 @@ public static async Task> GetRenamableDefinitionL if (!syntaxFacts.IsReservedOrContextualKeyword(token) && token.ValueText == referencedSymbol.Name) { - Contract.ThrowIfNull(location.SourceTree); - results.Add(new RenameLocation(location, solution.GetRequiredDocument(location.SourceTree).Id)); + AddRenameLocationIfNotGenerated(location); } } } @@ -351,6 +344,18 @@ public static async Task> GetRenamableDefinitionL } return results.ToImmutableAndFree(); + + void AddRenameLocationIfNotGenerated(Location location, bool isRenamableAccessor = false) + { + RoslynDebug.Assert(location.IsInSource); + var document = solution.GetRequiredDocument(location.SourceTree); + + // If the location is in a source generated file, we won't rename it. Our assumption in this case is we + // have cascaded to this symbol from our original source symbol, and the generator will update this file + // based on the renamed symbol. + if (document is not SourceGeneratedDocument) + results.Add(new RenameLocation(location, document.Id, isRenamableAccessor: isRenamableAccessor)); + } } internal static async Task> GetRenamableReferenceLocationsAsync(ISymbol referencedSymbol, ISymbol originalSymbol, ReferenceLocation location, Solution solution, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs index 82674d4511177..daa63a9d83b14 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs @@ -34,10 +34,10 @@ public static WellKnownSynchronizationKind GetWellKnownSynchronizationKind(this ProjectReference _ => WellKnownSynchronizationKind.ProjectReference, MetadataReference _ => WellKnownSynchronizationKind.MetadataReference, AnalyzerReference _ => WellKnownSynchronizationKind.AnalyzerReference, - TextDocumentState _ => WellKnownSynchronizationKind.RecoverableSourceText, SerializableSourceText _ => WellKnownSynchronizationKind.SerializableSourceText, SourceText _ => WellKnownSynchronizationKind.SourceText, OptionSet _ => WellKnownSynchronizationKind.OptionSet, + SourceGeneratedDocumentIdentity _ => WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity, _ => throw ExceptionUtilities.UnexpectedValue(value), }; diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 8f366a07fcf35..f9853ef0cce50 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -79,6 +79,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken case WellKnownSynchronizationKind.ParseOptions: case WellKnownSynchronizationKind.ProjectReference: case WellKnownSynchronizationKind.OptionSet: + case WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity: return Checksum.Create(kind, value, this); case WellKnownSynchronizationKind.MetadataReference: @@ -124,6 +125,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont case WellKnownSynchronizationKind.SolutionAttributes: case WellKnownSynchronizationKind.ProjectAttributes: case WellKnownSynchronizationKind.DocumentAttributes: + case WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity: ((IObjectWritable)value).WriteTo(writer); return; @@ -197,6 +199,8 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return (T)(object)ProjectInfo.ProjectAttributes.ReadFrom(reader); case WellKnownSynchronizationKind.DocumentAttributes: return (T)(object)DocumentInfo.DocumentAttributes.ReadFrom(reader); + case WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity: + return (T)(object)SourceGeneratedDocumentIdentity.ReadFrom(reader); case WellKnownSynchronizationKind.CompilationOptions: return (T)(object)DeserializeCompilationOptions(reader, cancellationToken); case WellKnownSynchronizationKind.ParseOptions: diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs index dac1adb8eb0fc..bc2e28422f81a 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs @@ -161,7 +161,7 @@ private MetadataReference DeserializeMetadataReference(ObjectReader reader, Canc public void SerializeAnalyzerReference(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - WriteTo(reference, writer, cancellationToken); + WriteAnalyzerReferenceTo(reference, writer, cancellationToken); } private AnalyzerReference DeserializeAnalyzerReference(ObjectReader reader, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index cfad9f4788b98..a4b878cfe5b83 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -91,7 +91,7 @@ public virtual MetadataReference ReadMetadataReferenceFrom(ObjectReader reader, throw ExceptionUtilities.UnexpectedValue(type); } - public static void WriteTo(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken) + public virtual void WriteAnalyzerReferenceTo(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -108,7 +108,7 @@ public static void WriteTo(AnalyzerReference reference, ObjectWriter writer, Can } } - public AnalyzerReference ReadAnalyzerReferenceFrom(ObjectReader reader, CancellationToken cancellationToken) + public virtual AnalyzerReference ReadAnalyzerReferenceFrom(ObjectReader reader, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs index 746560365de01..7db39792c50f6 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions { @@ -15,8 +16,12 @@ internal static partial class DocumentExtensions public static bool IsFromPrimaryBranch(this Document document) => document.Project.Solution.BranchId == document.Project.Solution.Workspace.PrimaryBranchId; - public static ValueTask GetSyntaxTreeIndexAsync(this Document document, CancellationToken cancellationToken) - => SyntaxTreeIndex.GetIndexAsync(document, loadOnly: false, cancellationToken); + public static async ValueTask GetSyntaxTreeIndexAsync(this Document document, CancellationToken cancellationToken) + { + var result = await SyntaxTreeIndex.GetIndexAsync(document, loadOnly: false, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(result); + return result; + } public static ValueTask GetSyntaxTreeIndexAsync(this Document document, bool loadOnly, CancellationToken cancellationToken) => SyntaxTreeIndex.GetIndexAsync(document, loadOnly, cancellationToken); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SemanticEquivalence.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SemanticEquivalence.cs index 22ef13e3536c9..17be202350157 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SemanticEquivalence.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SemanticEquivalence.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -69,6 +70,33 @@ private static bool AreSemanticallyEquivalentWorker( } } + // Original expression and current node being semantically equivalent isn't enough when the original expression + // is a member access via instance reference (either implicit or explicit), the check only ensures that the expression + // and current node are both backed by the same member symbol. So in this case, in addition to SemanticEquivalence check, + // we also check if expression and current node are both instance member access. + // + // For example, even though the first `c` binds to a field and we are introducing a local for it, + // we don't want other references to that field to be replaced as well (i.e. the second `c` in the expression). + // + // class C + // { + // C c; + // void Test() + // { + // var x = [|c|].c; + // } + // } + var originalOperation = semanticModel1.GetOperation(node1); + if (originalOperation != null && IsInstanceMemberReference(originalOperation)) + { + var currentOperation = semanticModel2.GetOperation(node2); + + if (currentOperation is null || !IsInstanceMemberReference(currentOperation)) + { + return false; + } + } + var e1 = node1.ChildNodesAndTokens().GetEnumerator(); var e2 = node2.ChildNodesAndTokens().GetEnumerator(); @@ -97,6 +125,9 @@ private static bool AreSemanticallyEquivalentWorker( } } + private static bool IsInstanceMemberReference(IOperation operation) + => operation is IMemberReferenceOperation { Instance: { Kind: OperationKind.InstanceReference } }; + private static bool AreEquals( SemanticModel semanticModel1, SemanticModel semanticModel2, diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs index aef0945706773..cab95f9ba3627 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs @@ -11,7 +11,9 @@ using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions @@ -20,6 +22,7 @@ internal static partial class SyntaxGeneratorExtensions { public static IMethodSymbol CreateEqualsMethod( this SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, Compilation compilation, ParseOptions parseOptions, INamedTypeSymbol containingType, @@ -28,7 +31,7 @@ public static IMethodSymbol CreateEqualsMethod( SyntaxAnnotation statementAnnotation) { var statements = CreateEqualsMethodStatements( - factory, compilation, parseOptions, containingType, symbols, localNameOpt); + factory, generatorInternal, compilation, parseOptions, containingType, symbols, localNameOpt); statements = statements.SelectAsArray(s => s.WithAdditionalAnnotations(statementAnnotation)); return CreateEqualsMethod(compilation, statements); @@ -51,6 +54,7 @@ public static IMethodSymbol CreateEqualsMethod(this Compilation compilation, Imm public static IMethodSymbol CreateIEquatableEqualsMethod( this SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, SemanticModel semanticModel, INamedTypeSymbol containingType, ImmutableArray symbols, @@ -58,7 +62,7 @@ public static IMethodSymbol CreateIEquatableEqualsMethod( SyntaxAnnotation statementAnnotation) { var statements = CreateIEquatableEqualsMethodStatements( - factory, semanticModel.Compilation, containingType, symbols); + factory, generatorInternal, semanticModel.Compilation, containingType, symbols); statements = statements.SelectAsArray(s => s.WithAdditionalAnnotations(statementAnnotation)); var methodSymbol = constructedEquatableType @@ -95,6 +99,7 @@ public static IMethodSymbol CreateIEquatableEqualsMethod( private static ImmutableArray CreateEqualsMethodStatements( SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, Compilation compilation, ParseOptions parseOptions, INamedTypeSymbol containingType, @@ -190,7 +195,7 @@ private static ImmutableArray CreateEqualsMethodStatements( objNameExpression)); } - AddMemberChecks(factory, compilation, members, localNameExpression, expressions); + AddMemberChecks(factory, generatorInternal, compilation, members, localNameExpression, expressions); // Now combine all the comparison expressions together into one final statement like: // @@ -204,7 +209,7 @@ private static ImmutableArray CreateEqualsMethodStatements( } private static void AddMemberChecks( - SyntaxGenerator factory, Compilation compilation, + SyntaxGenerator factory, SyntaxGeneratorInternal generatorInternal, Compilation compilation, ImmutableArray members, SyntaxNode localNameExpression, ArrayBuilder expressions) { @@ -249,7 +254,7 @@ private static void AddMemberChecks( expressions.Add(factory.InvocationExpression( factory.MemberAccessExpression( - GetDefaultEqualityComparer(factory, compilation, GetType(compilation, member)), + GetDefaultEqualityComparer(factory, generatorInternal, compilation, GetType(compilation, member)), factory.IdentifierName(EqualsName)), thisSymbol, otherSymbol)); @@ -258,6 +263,7 @@ private static void AddMemberChecks( private static ImmutableArray CreateIEquatableEqualsMethodStatements( SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, Compilation compilation, INamedTypeSymbol containingType, ImmutableArray members) @@ -290,7 +296,7 @@ private static ImmutableArray CreateIEquatableEqualsMethodStatements } } - AddMemberChecks(factory, compilation, members, otherNameExpression, expressions); + AddMemberChecks(factory, generatorInternal, compilation, members, otherNameExpression, expressions); // Now combine all the comparison expressions together into one final statement like: // @@ -308,7 +314,8 @@ public static string GetLocalName(this ITypeSymbol containingType) var name = containingType.Name; if (name.Length > 0) { - var parts = StringBreaker.GetWordParts(name); + using var parts = TemporaryArray.Empty; + StringBreaker.AddWordParts(name, ref parts.AsRef()); for (var i = parts.Count - 1; i >= 0; i--) { var p = parts[i]; diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 522170e6cb6d0..759e1e45b6a54 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -23,6 +23,7 @@ internal static class FeatureAttribute public const string GoToImplementation = nameof(GoToImplementation); public const string GraphProvider = nameof(GraphProvider); public const string InfoBar = nameof(InfoBar); + public const string InheritanceMargin = nameof(InheritanceMargin); public const string InlineParameterNameHints = nameof(InlineParameterNameHints); public const string InteractiveEvaluator = nameof(InteractiveEvaluator); public const string KeywordHighlighting = nameof(KeywordHighlighting); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs index 5707e5766bdd0..37e8cdd01a120 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs @@ -2,15 +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.Threading; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Shared.Utilities { internal interface IStreamingProgressTracker { - ValueTask AddItemsAsync(int count); - ValueTask ItemCompletedAsync(); + ValueTask AddItemsAsync(int count, CancellationToken cancellationToken); + ValueTask ItemCompletedAsync(CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs index 9dd492de7766d..e57a545b167e3 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Shared.Utilities @@ -16,21 +15,25 @@ internal static class IStreamingProgressTrackerExtensions /// cref="IStreamingProgressTracker.ItemCompletedAsync"/> on when it is disposed. /// - public static async Task AddSingleItemAsync(this IStreamingProgressTracker progressTracker) + public static async Task AddSingleItemAsync(this IStreamingProgressTracker progressTracker, CancellationToken cancellationToken) { - await progressTracker.AddItemsAsync(1).ConfigureAwait(false); - return new StreamingProgressDisposer(progressTracker); + await progressTracker.AddItemsAsync(1, cancellationToken).ConfigureAwait(false); + return new StreamingProgressDisposer(progressTracker, cancellationToken); } private class StreamingProgressDisposer : IAsyncDisposable { private readonly IStreamingProgressTracker _progressTracker; + private readonly CancellationToken _cancellationToken; - public StreamingProgressDisposer(IStreamingProgressTracker progressTracker) - => _progressTracker = progressTracker; + public StreamingProgressDisposer(IStreamingProgressTracker progressTracker, CancellationToken cancellationToken) + { + _progressTracker = progressTracker; + _cancellationToken = cancellationToken; + } public async ValueTask DisposeAsync() - => await _progressTracker.ItemCompletedAsync().ConfigureAwait(false); + => await _progressTracker.ItemCompletedAsync(_cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs b/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs index 77de02c4e0601..59c57795661c2 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs @@ -16,31 +16,31 @@ internal sealed class StreamingProgressTracker : IStreamingProgressTracker private int _completedItems; private int _totalItems; - private readonly Func? _updateAction; + private readonly Func? _updateAction; - public StreamingProgressTracker(Func? updateAction = null) + public StreamingProgressTracker(Func? updateAction = null) => _updateAction = updateAction; - public ValueTask AddItemsAsync(int count) + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) { Interlocked.Add(ref _totalItems, count); - return UpdateAsync(); + return UpdateAsync(cancellationToken); } - public ValueTask ItemCompletedAsync() + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) { Interlocked.Increment(ref _completedItems); - return UpdateAsync(); + return UpdateAsync(cancellationToken); } - private ValueTask UpdateAsync() + private ValueTask UpdateAsync(CancellationToken cancellationToken) { if (_updateAction == null) { return default; } - return _updateAction(_completedItems, _totalItems); + return _updateAction(_completedItems, _totalItems, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzerExtensions.cs b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzerExtensions.cs deleted file mode 100644 index f5f8e1dea2918..0000000000000 --- a/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzerExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using Microsoft.CodeAnalysis.ExternalAccess.UnitTesting; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.SolutionCrawler -{ - internal static partial class IIncrementalAnalyzerExtensions - { - public static BackgroundAnalysisScope GetOverriddenBackgroundAnalysisScope(this IIncrementalAnalyzer incrementalAnalyzer, OptionSet options, BackgroundAnalysisScope defaultBackgroundAnalysisScope) - { - // Unit testing analyzer has special semantics for analysis scope. - if (incrementalAnalyzer is UnitTestingIncrementalAnalyzer) - { - return UnitTestingIncrementalAnalyzer.GetBackgroundAnalysisScope(options); - } - - return defaultBackgroundAnalysisScope; - } - } -} diff --git a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs index c396453c3bf18..3bd13b06e9b88 100644 --- a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs @@ -39,7 +39,7 @@ protected AbstractPersistentStorageService(IPersistentStorageLocationService loc /// to delete the database and retry opening one more time. If that fails again, the instance will be used. /// - protected abstract ValueTask TryOpenDatabaseAsync(SolutionKey solutionKey, string workingFolderPath, string databaseFilePath); + protected abstract ValueTask TryOpenDatabaseAsync(SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken); protected abstract bool ShouldDeleteDatabase(Exception exception); [Obsolete("Use GetStorageAsync instead")] @@ -62,9 +62,7 @@ public ValueTask GetStorageAsync( Workspace workspace, SolutionKey solutionKey, Solution? bulkLoadSnapshot, bool checkBranchId, CancellationToken cancellationToken) { if (!DatabaseSupported(solutionKey, checkBranchId)) - { - return new(NoOpPersistentStorage.Instance); - } + return new(NoOpPersistentStorage.GetOrThrow(workspace.Options)); return GetStorageWorkerAsync(workspace, solutionKey, bulkLoadSnapshot, cancellationToken); } @@ -84,7 +82,7 @@ internal async ValueTask GetStorageWorkerAsync( var workingFolder = TryGetWorkingFolder(workspace, solutionKey, bulkLoadSnapshot); if (workingFolder == null) - return NoOpPersistentStorage.Instance; + return NoOpPersistentStorage.GetOrThrow(workspace.Options); // If we already had some previous cached service, let's let it start cleaning up if (_currentPersistentStorage != null) @@ -102,7 +100,7 @@ internal async ValueTask GetStorageWorkerAsync( _currentPersistentStorageSolutionId = null; } - var storage = await CreatePersistentStorageAsync(solutionKey, workingFolder).ConfigureAwait(false); + var storage = await CreatePersistentStorageAsync(workspace, solutionKey, workingFolder, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(storage); // Create and cache a new storage instance associated with this particular solution. @@ -146,26 +144,32 @@ private static bool DatabaseSupported(SolutionKey solution, bool checkBranchId) return true; } - private async ValueTask CreatePersistentStorageAsync(SolutionKey solutionKey, string workingFolderPath) + private async ValueTask CreatePersistentStorageAsync( + Workspace workspace, SolutionKey solutionKey, string workingFolderPath, CancellationToken cancellationToken) { // Attempt to create the database up to two times. The first time we may encounter // some sort of issue (like DB corruption). We'll then try to delete the DB and can // try to create it again. If we can't create it the second time, then there's nothing // we can do and we have to store things in memory. - var result = await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath).ConfigureAwait(false) ?? - await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath).ConfigureAwait(false); + var result = await TryCreatePersistentStorageAsync(workspace, solutionKey, workingFolderPath, cancellationToken).ConfigureAwait(false) ?? + await TryCreatePersistentStorageAsync(workspace, solutionKey, workingFolderPath, cancellationToken).ConfigureAwait(false); + if (result != null) return result; - return NoOpPersistentStorage.Instance; + return NoOpPersistentStorage.GetOrThrow(workspace.Options); } - private async ValueTask TryCreatePersistentStorageAsync(SolutionKey solutionKey, string workingFolderPath) + private async ValueTask TryCreatePersistentStorageAsync( + Workspace workspace, + SolutionKey solutionKey, + string workingFolderPath, + CancellationToken cancellationToken) { var databaseFilePath = GetDatabaseFilePath(workingFolderPath); try { - return await TryOpenDatabaseAsync(solutionKey, workingFolderPath, databaseFilePath).ConfigureAwait(false); + return await TryOpenDatabaseAsync(solutionKey, workingFolderPath, databaseFilePath, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -179,6 +183,9 @@ private async ValueTask CreatePersistentStorageAs IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath)!, recursive: true)); } + if (workspace.Options.GetOption(StorageOptions.DatabaseMustSucceed)) + throw; + return null; } } diff --git a/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceFactory.cs new file mode 100644 index 0000000000000..a0a168a7dd385 --- /dev/null +++ b/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceFactory.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Storage.CloudCache +{ + internal interface ICloudCacheStorageServiceFactory : IWorkspaceService + { + AbstractPersistentStorageService Create(IPersistentStorageLocationService locationService); + } +} diff --git a/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs new file mode 100644 index 0000000000000..4408ca3467b3c --- /dev/null +++ b/src/Workspaces/Core/Portable/Storage/DesktopPersistenceStorageServiceFactory.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Experiments; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; + +// When building for source-build, there is no sqlite dependency +#if !DOTNET_BUILD_FROM_SOURCE +using Microsoft.CodeAnalysis.SQLite.v2; +using Microsoft.CodeAnalysis.Storage.CloudCache; +#endif + +namespace Microsoft.CodeAnalysis.Storage +{ + [ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), ServiceLayer.Desktop), Shared] + internal class DesktopPersistenceStorageServiceFactory : IWorkspaceServiceFactory + { +#if DOTNET_BUILD_FROM_SOURCE + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DesktopPersistenceStorageServiceFactory() + { + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + return NoOpPersistentStorageService.Instance; + } + +#else + + private readonly SQLiteConnectionPoolService _connectionPoolService; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DesktopPersistenceStorageServiceFactory(SQLiteConnectionPoolService connectionPoolService) + { + _connectionPoolService = connectionPoolService; + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + var options = workspaceServices.Workspace.Options; + var locationService = workspaceServices.GetService(); + + if (locationService != null) + { + var database = GetDatabase(workspaceServices); + switch (database) + { + case StorageDatabase.SQLite: + return new SQLitePersistentStorageService(options, _connectionPoolService, locationService); + + case StorageDatabase.CloudCache: + var factory = workspaceServices.GetService(); + + return factory == null + ? NoOpPersistentStorageService.GetOrThrow(options) + : factory.Create(locationService); + } + } + + return NoOpPersistentStorageService.GetOrThrow(options); + } + + private static StorageDatabase GetDatabase(HostWorkspaceServices workspaceServices) + { + var experimentationService = workspaceServices.GetService(); + if (experimentationService?.IsExperimentEnabled(WellKnownExperimentNames.CloudCache) == true) + return StorageDatabase.CloudCache; + + var optionService = workspaceServices.GetRequiredService(); + return optionService.GetOption(StorageOptions.Database); + } + +#endif + } +} diff --git a/src/Workspaces/Core/Portable/Storage/PersistenceStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Storage/PersistenceStorageServiceFactory.cs deleted file mode 100644 index 7631e5fa81d25..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/PersistenceStorageServiceFactory.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; - -// When building for source-build, there is no sqlite dependency -#if !DOTNET_BUILD_FROM_SOURCE -using Microsoft.CodeAnalysis.SQLite.v2; -#endif - -namespace Microsoft.CodeAnalysis.Storage -{ - [ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), ServiceLayer.Desktop), Shared] - internal class PersistenceStorageServiceFactory : IWorkspaceServiceFactory - { -#if !DOTNET_BUILD_FROM_SOURCE - private readonly SQLiteConnectionPoolService _connectionPoolService; -#endif - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public PersistenceStorageServiceFactory( -#if !DOTNET_BUILD_FROM_SOURCE - SQLiteConnectionPoolService connectionPoolService -#endif - ) - { -#if !DOTNET_BUILD_FROM_SOURCE - _connectionPoolService = connectionPoolService; -#endif - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { -#if !DOTNET_BUILD_FROM_SOURCE - var optionService = workspaceServices.GetRequiredService(); - var database = optionService.GetOption(StorageOptions.Database); - switch (database) - { - case StorageDatabase.SQLite: - var locationService = workspaceServices.GetService(); - if (locationService != null) - return new SQLite.v2.SQLitePersistentStorageService(_connectionPoolService, locationService); - - break; - } -#endif - - return NoOpPersistentStorageService.Instance; - } - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageConstants.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageConstants.cs index 77e5db7694bdc..788a8d516ed28 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageConstants.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageConstants.cs @@ -59,7 +59,7 @@ internal static class SQLitePersistentStorageConstants /// /// Inside the DB we have a table for data that we want associated with a . /// The data is keyed off of an integral value produced by combining the ID of the Project and - /// the ID of the name of the data (see . + /// the ID of the name of the data (see . /// /// This gives a very efficient integral key, and means that the we only have to store a /// single mapping from stream name to ID in the string table. @@ -76,7 +76,7 @@ internal static class SQLitePersistentStorageConstants /// /// Inside the DB we have a table for data that we want associated with a . /// The data is keyed off of an integral value produced by combining the ID of the Document and - /// the ID of the name of the data (see . + /// the ID of the name of the data (see . /// /// This gives a very efficient integral key, and means that the we only have to store a /// single mapping from stream name to ID in the string table. diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs index 772eec42395af..9baaf0089c3ba 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs @@ -4,8 +4,10 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; using Roslyn.Utilities; @@ -17,19 +19,25 @@ internal class SQLitePersistentStorageService : AbstractSQLitePersistentStorageS private const string PersistentStorageFileName = "storage.ide"; private readonly SQLiteConnectionPoolService _connectionPoolService; + private readonly OptionSet _options; private readonly IPersistentStorageFaultInjector? _faultInjector; - public SQLitePersistentStorageService(SQLiteConnectionPoolService connectionPoolService, IPersistentStorageLocationService locationService) + public SQLitePersistentStorageService( + OptionSet options, + SQLiteConnectionPoolService connectionPoolService, + IPersistentStorageLocationService locationService) : base(locationService) { + _options = options; _connectionPoolService = connectionPoolService; } public SQLitePersistentStorageService( + OptionSet options, SQLiteConnectionPoolService connectionPoolService, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector) - : this(connectionPoolService, locationService) + : this(options, connectionPoolService, locationService) { _faultInjector = faultInjector; } @@ -41,7 +49,7 @@ protected override string GetDatabaseFilePath(string workingFolderPath) } protected override ValueTask TryOpenDatabaseAsync( - SolutionKey solutionKey, string workingFolderPath, string databaseFilePath) + SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken) { if (!TryInitializeLibraries()) { @@ -49,7 +57,9 @@ protected override string GetDatabaseFilePath(string workingFolderPath) return new((IChecksummedPersistentStorage?)null); } - Contract.ThrowIfNull(solutionKey.FilePath); + if (solutionKey.FilePath == null) + return new(NoOpPersistentStorage.GetOrThrow(_options)); + return new(SQLitePersistentStorage.TryCreate( _connectionPoolService, workingFolderPath, diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_DocumentSerialization.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_DocumentSerialization.cs index 0910ec4d5c45d..f214a364f2c82 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_DocumentSerialization.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_DocumentSerialization.cs @@ -14,13 +14,13 @@ namespace Microsoft.CodeAnalysis.SQLite.v2 internal partial class SQLitePersistentStorage { - public override Task ChecksumMatchesAsync(DocumentKey documentKey, string name, Checksum checksum, CancellationToken cancellationToken) + protected override Task ChecksumMatchesAsync(DocumentKey documentKey, Document? document, string name, Checksum checksum, CancellationToken cancellationToken) => _documentAccessor.ChecksumMatchesAsync((documentKey, name), checksum, cancellationToken); - public override Task ReadStreamAsync(DocumentKey documentKey, string name, Checksum? checksum, CancellationToken cancellationToken) + protected override Task ReadStreamAsync(DocumentKey documentKey, Document? document, string name, Checksum? checksum, CancellationToken cancellationToken) => _documentAccessor.ReadStreamAsync((documentKey, name), checksum, cancellationToken); - public override Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + protected override Task WriteStreamAsync(DocumentKey documentKey, Document? document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) => _documentAccessor.WriteStreamAsync((documentKey, name), stream, checksum, cancellationToken); /// diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_ProjectSerialization.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_ProjectSerialization.cs index 01d76ce05645b..d5248c430280d 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_ProjectSerialization.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_ProjectSerialization.cs @@ -14,13 +14,13 @@ namespace Microsoft.CodeAnalysis.SQLite.v2 internal partial class SQLitePersistentStorage { - public override Task ChecksumMatchesAsync(ProjectKey projectKey, string name, Checksum checksum, CancellationToken cancellationToken) + protected override Task ChecksumMatchesAsync(ProjectKey projectKey, Project? project, string name, Checksum checksum, CancellationToken cancellationToken) => _projectAccessor.ChecksumMatchesAsync((projectKey, name), checksum, cancellationToken); - public override Task ReadStreamAsync(ProjectKey projectKey, string name, Checksum? checksum, CancellationToken cancellationToken) + protected override Task ReadStreamAsync(ProjectKey projectKey, Project? project, string name, Checksum? checksum, CancellationToken cancellationToken) => _projectAccessor.ReadStreamAsync((projectKey, name), checksum, cancellationToken); - public override Task WriteStreamAsync(ProjectKey projectKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + protected override Task WriteStreamAsync(ProjectKey projectKey, Project? project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) => _projectAccessor.WriteStreamAsync((projectKey, name), stream, checksum, cancellationToken); /// diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs index ff0557b384d9d..061470a34a43b 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.SQLite.v2 { @@ -28,12 +29,30 @@ private static async Task PerformTaskAsync( // Read tasks go to the concurrent-scheduler where they can run concurrently with other read // tasks. private Task PerformReadAsync(Func func, TArg arg, CancellationToken cancellationToken) where TArg : struct - => PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ConcurrentScheduler, cancellationToken); + { + // Suppress ExecutionContext flow for asynchronous operations that write to the database. In addition to + // avoiding ExecutionContext allocations, this clears the LogicalCallContext and avoids the need to clone + // data set by CallContext.LogicalSetData at each yielding await in the task tree. + // + // ⚠ DO NOT AWAIT INSIDE THE USING. The Dispose method that restores ExecutionContext flow must run on the + // same thread where SuppressFlow was originally run. + using var _ = FlowControlHelper.TrySuppressFlow(); + return PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ConcurrentScheduler, cancellationToken); + } // Write tasks go to the exclusive-scheduler so they run exclusively of all other threading // tasks we need to do. public Task PerformWriteAsync(Func func, TArg arg, CancellationToken cancellationToken) where TArg : struct - => PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ExclusiveScheduler, cancellationToken); + { + // Suppress ExecutionContext flow for asynchronous operations that write to the database. In addition to + // avoiding ExecutionContext allocations, this clears the LogicalCallContext and avoids the need to clone + // data set by CallContext.LogicalSetData at each yielding await in the task tree. + // + // ⚠ DO NOT AWAIT INSIDE THE USING. The Dispose method that restores ExecutionContext flow must run on the + // same thread where SuppressFlow was originally run. + using var _ = FlowControlHelper.TrySuppressFlow(); + return PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ExclusiveScheduler, cancellationToken); + } public Task PerformWriteAsync(Action action, CancellationToken cancellationToken) => PerformWriteAsync(vt => diff --git a/src/Workspaces/Core/Portable/Storage/StorageDatabase.cs b/src/Workspaces/Core/Portable/Storage/StorageDatabase.cs index 4587658fe8c46..150f02a3469e8 100644 --- a/src/Workspaces/Core/Portable/Storage/StorageDatabase.cs +++ b/src/Workspaces/Core/Portable/Storage/StorageDatabase.cs @@ -8,5 +8,6 @@ internal enum StorageDatabase { None = 0, SQLite = 1, + CloudCache = 2, } } diff --git a/src/Workspaces/Core/Portable/Storage/StorageOptions.cs b/src/Workspaces/Core/Portable/Storage/StorageOptions.cs index 39b908bbfd548..cdd19d3676620 100644 --- a/src/Workspaces/Core/Portable/Storage/StorageOptions.cs +++ b/src/Workspaces/Core/Portable/Storage/StorageOptions.cs @@ -18,7 +18,16 @@ internal static class StorageOptions public const string OptionName = "FeatureManager/Storage"; public static readonly Option Database = new( - OptionName, nameof(Database), defaultValue: StorageDatabase.SQLite); + OptionName, nameof(Database), defaultValue: StorageDatabase.SQLite, + storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + nameof(Database))); + + /// + /// Option that can be set in certain scenarios (like tests) to indicate that the client expects the DB to + /// succeed at all work and that it should not ever gracefully fall over. Should not be set in normal host + /// environments, where it is completely reasonable for things to fail (for example, if a client asks for a key + /// that hasn't been stored yet). + /// + public static readonly Option DatabaseMustSucceed = new(OptionName, nameof(DatabaseMustSucceed), defaultValue: false); } [ExportOptionProvider, Shared] @@ -31,6 +40,7 @@ public RemoteHostOptionsProvider() } public ImmutableArray Options { get; } = ImmutableArray.Create( - StorageOptions.Database); + StorageOptions.Database, + StorageOptions.DatabaseMustSucceed); } } diff --git a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.DynamicTypeSymbolKey.cs b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.DynamicTypeSymbolKey.cs index 0971b1927ac43..a01baf8348f62 100644 --- a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.DynamicTypeSymbolKey.cs +++ b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.DynamicTypeSymbolKey.cs @@ -16,6 +16,15 @@ public static void Create(SymbolKeyWriter _) public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? failureReason) { + if (reader.Compilation.Language == LanguageNames.VisualBasic) + { + // TODO: We could consider mapping 'dynamic' to 'object' when resolving these types in Visual Basic. + // However, this should be driven by an actual scenario that is not working that can be traced down + // to this check. + failureReason = $"({nameof(DynamicTypeSymbolKey)} is not supported in {LanguageNames.VisualBasic})"; + return default; + } + failureReason = null; return new SymbolKeyResolution(reader.Compilation.DynamicType); } diff --git a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.FunctionPointerTypeSymbolKey.cs b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.FunctionPointerTypeSymbolKey.cs index b8e44dc07e1a1..c801dd7cb2d66 100644 --- a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.FunctionPointerTypeSymbolKey.cs +++ b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.FunctionPointerTypeSymbolKey.cs @@ -66,12 +66,18 @@ public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? fa return default; } - if (!(returnType.GetAnySymbol() is ITypeSymbol returnTypeSymbol)) + if (returnType.GetAnySymbol() is not ITypeSymbol returnTypeSymbol) { failureReason = $"({nameof(FunctionPointerTypeSymbolKey)} no return type)"; return default; } + if (reader.Compilation.Language == LanguageNames.VisualBasic) + { + failureReason = $"({nameof(FunctionPointerTypeSymbolKey)} is not supported in {LanguageNames.VisualBasic})"; + return default; + } + failureReason = null; return new SymbolKeyResolution(reader.Compilation.CreateFunctionPointerTypeSymbol( returnTypeSymbol, returnRefKind, parameterTypes.ToImmutable(), paramRefKinds.ToImmutable(), callingConvention, callingConventionModifiers)); diff --git a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.PointerTypeSymbolKey.cs b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.PointerTypeSymbolKey.cs index cf2a97b597b6c..7651134d6e601 100644 --- a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.PointerTypeSymbolKey.cs +++ b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.PointerTypeSymbolKey.cs @@ -21,11 +21,15 @@ public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? fa return default; } + if (reader.Compilation.Language == LanguageNames.VisualBasic) + { + failureReason = $"({nameof(PointerTypeSymbolKey)} is not supported in {LanguageNames.VisualBasic})"; + return default; + } + using var result = PooledArrayBuilder.GetInstance(pointedAtTypeResolution.SymbolCount); foreach (var typeSymbol in pointedAtTypeResolution.OfType()) - { result.AddIfNotNull(reader.Compilation.CreatePointerTypeSymbol(typeSymbol)); - } return CreateResolution(result, $"({nameof(PointerTypeSymbolKey)} could not resolve)", out failureReason); } diff --git a/src/Workspaces/Core/Portable/Utilities/FlowControlHelper.cs b/src/Workspaces/Core/Portable/Utilities/FlowControlHelper.cs new file mode 100644 index 0000000000000..686c87f456b96 --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/FlowControlHelper.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.Threading; + +namespace Roslyn.Utilities +{ + internal static class FlowControlHelper + { + public static AsyncFlowControlHelper TrySuppressFlow() + => new(ExecutionContext.IsFlowSuppressed() ? default : ExecutionContext.SuppressFlow()); + + public struct AsyncFlowControlHelper : IDisposable + { + private readonly AsyncFlowControl _asyncFlowControl; + + public AsyncFlowControlHelper(AsyncFlowControl asyncFlowControl) + { + _asyncFlowControl = asyncFlowControl; + } + + public void Dispose() + { + if (_asyncFlowControl != default) + { + _asyncFlowControl.Dispose(); + } + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs b/src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs deleted file mode 100644 index 4b17ad7fa5684..0000000000000 --- a/src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Internal.Log; - -namespace Microsoft.CodeAnalysis.Versions -{ - internal static class PersistedVersionStampLogger - { - // we have 6 different versions to track various changes - private const string Text = nameof(Text); - private const string SyntaxTree = nameof(SyntaxTree); - private const string Project = nameof(Project); - private const string DependentProject = nameof(DependentProject); - - private static readonly LogAggregator s_logAggregator = new(); - - public static void LogPersistedTextVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(Text); - } - - public static void LogPersistedSyntaxTreeVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(SyntaxTree); - } - - public static void LogPersistedProjectVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(Project); - } - - public static void LogPersistedDependentProjectVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(DependentProject); - } - - public static void ReportTelemetry() - { - Logger.Log(FunctionId.PersistedSemanticVersion_Info, KeyValueLogMessage.Create(m => - { - m[Text] = s_logAggregator.GetCount(Text); - m[SyntaxTree] = s_logAggregator.GetCount(SyntaxTree); - m[Project] = s_logAggregator.GetCount(Project); - m[DependentProject] = s_logAggregator.GetCount(DependentProject); - })); - } - } -} diff --git a/src/Workspaces/Core/Desktop/Workspace/CommandLineProject.cs b/src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs similarity index 96% rename from src/Workspaces/Core/Desktop/Workspace/CommandLineProject.cs rename to src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs index ba1c97cdbfa69..5df997ea3f272 100644 --- a/src/Workspaces/Core/Desktop/Workspace/CommandLineProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs @@ -22,7 +22,9 @@ public static class CommandLineProject /// /// Create a structure initialized from a compilers command line arguments. /// +#pragma warning disable RS0026 // Type is forwarded from MS.CA.Workspaces.Desktop. public static ProjectInfo CreateProjectInfo(string projectName, string language, IEnumerable commandLineArgs, string projectDirectory, Workspace workspace = null) +#pragma warning restore RS0026 // Type is forwarded from MS.CA.Workspaces.Desktop. { // TODO (tomat): the method may throw all sorts of exceptions. var tmpWorkspace = workspace ?? new AdhocWorkspace(); @@ -176,7 +178,9 @@ public static ProjectInfo CreateProjectInfo(string projectName, string language, /// /// Create a structure initialized with data from a compiler command line. /// +#pragma warning disable RS0026 // Type is forwarded from MS.CA.Workspaces.Desktop. public static ProjectInfo CreateProjectInfo(string projectName, string language, string commandLine, string baseDirectory, Workspace workspace = null) +#pragma warning restore RS0026 // Type is forwarded from MS.CA.Workspaces.Desktop. { var args = CommandLineParser.SplitCommandLineIntoArguments(commandLine, removeHashComments: true); return CreateProjectInfo(projectName, language, args, baseDirectory, workspace); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs new file mode 100644 index 0000000000000..6079d396466ec --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host +{ + internal abstract class AbstractSpanMappingService : ISpanMappingService + { + public abstract bool SupportsMappingImportDirectives { get; } + + public abstract Task> GetMappedTextChangesAsync( + Document oldDocument, + Document newDocument, + CancellationToken cancellationToken); + + public abstract Task> MapSpansAsync( + Document document, + IEnumerable spans, + CancellationToken cancellationToken); + + protected static ImmutableArray<(string mappedFilePath, TextChange mappedTextChange)> MatchMappedSpansToTextChanges( + ImmutableArray textChanges, + ImmutableArray mappedSpanResults) + { + Contract.ThrowIfFalse(mappedSpanResults.Length == textChanges.Length); + + using var _ = ArrayBuilder<(string, TextChange)>.GetInstance(out var mappedFilePathAndTextChange); + for (var i = 0; i < mappedSpanResults.Length; i++) + { + // Only include changes that could be mapped. + var newText = textChanges[i].NewText; + if (!mappedSpanResults[i].IsDefault && newText != null) + { + var newTextChange = new TextChange(mappedSpanResults[i].Span, newText); + mappedFilePathAndTextChange.Add((mappedSpanResults[i].FilePath, newTextChange)); + } + } + + return mappedFilePathAndTextChange.ToImmutable(); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISpanMappingService.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISpanMappingService.cs index 69eb38fc6c35d..9d91ccd31c20b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISpanMappingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISpanMappingService.cs @@ -28,6 +28,11 @@ internal interface ISpanMappingService : IDocumentService /// bool SupportsMappingImportDirectives { get; } + Task> GetMappedTextChangesAsync( + Document oldDocument, + Document newDocument, + CancellationToken cancellationToken); + /// /// Map spans in the document to more appropriate locations /// diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs index 8fe74c72239b2..80e423375e559 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs @@ -41,24 +41,42 @@ protected AbstractPersistentStorage( public abstract Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken); public abstract Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); - public abstract Task ChecksumMatchesAsync(ProjectKey projectKey, string name, Checksum checksum, CancellationToken cancellationToken); - public abstract Task ChecksumMatchesAsync(DocumentKey documentKey, string name, Checksum checksum, CancellationToken cancellationToken); - public abstract Task ReadStreamAsync(ProjectKey projectKey, string name, Checksum? checksum, CancellationToken cancellationToken); - public abstract Task ReadStreamAsync(DocumentKey documentKey, string name, Checksum? checksum, CancellationToken cancellationToken); - public abstract Task WriteStreamAsync(ProjectKey projectKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); - public abstract Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); + protected abstract Task ChecksumMatchesAsync(ProjectKey projectKey, Project? project, string name, Checksum checksum, CancellationToken cancellationToken); + protected abstract Task ChecksumMatchesAsync(DocumentKey documentKey, Document? document, string name, Checksum checksum, CancellationToken cancellationToken); + protected abstract Task ReadStreamAsync(ProjectKey projectKey, Project? project, string name, Checksum? checksum, CancellationToken cancellationToken); + protected abstract Task ReadStreamAsync(DocumentKey documentKey, Document? document, string name, Checksum? checksum, CancellationToken cancellationToken); + protected abstract Task WriteStreamAsync(ProjectKey projectKey, Project? project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); + protected abstract Task WriteStreamAsync(DocumentKey documentKey, Document? document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); + + public Task ChecksumMatchesAsync(ProjectKey projectKey, string name, Checksum checksum, CancellationToken cancellationToken) + => ChecksumMatchesAsync(projectKey, project: null, name, checksum, cancellationToken); + + public Task ChecksumMatchesAsync(DocumentKey documentKey, string name, Checksum checksum, CancellationToken cancellationToken) + => ChecksumMatchesAsync(documentKey, document: null, name, checksum, cancellationToken); + + public Task ReadStreamAsync(ProjectKey projectKey, string name, Checksum? checksum, CancellationToken cancellationToken) + => ReadStreamAsync(projectKey, project: null, name, checksum, cancellationToken); + + public Task ReadStreamAsync(DocumentKey documentKey, string name, Checksum? checksum, CancellationToken cancellationToken) + => ReadStreamAsync(documentKey, document: null, name, checksum, cancellationToken); + + public Task WriteStreamAsync(ProjectKey projectKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + => WriteStreamAsync(projectKey, project: null, name, stream, checksum, cancellationToken); + + public Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) + => WriteStreamAsync(documentKey, document: null, name, stream, checksum, cancellationToken); public Task ChecksumMatchesAsync(Project project, string name, Checksum checksum, CancellationToken cancellationToken) - => ChecksumMatchesAsync(ProjectKey.ToProjectKey(project), name, checksum, cancellationToken); + => ChecksumMatchesAsync(ProjectKey.ToProjectKey(project), project, name, checksum, cancellationToken); public Task ChecksumMatchesAsync(Document document, string name, Checksum checksum, CancellationToken cancellationToken) - => ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), name, checksum, cancellationToken); + => ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), document, name, checksum, cancellationToken); public Task ReadStreamAsync(Project project, string name, Checksum? checksum, CancellationToken cancellationToken) - => ReadStreamAsync(ProjectKey.ToProjectKey(project), name, checksum, cancellationToken); + => ReadStreamAsync(ProjectKey.ToProjectKey(project), project, name, checksum, cancellationToken); public Task ReadStreamAsync(Document document, string name, Checksum? checksum, CancellationToken cancellationToken) - => ReadStreamAsync(DocumentKey.ToDocumentKey(document), name, checksum, cancellationToken); + => ReadStreamAsync(DocumentKey.ToDocumentKey(document), document, name, checksum, cancellationToken); public Task ReadStreamAsync(string name, CancellationToken cancellationToken) => ReadStreamAsync(name, checksum: null, cancellationToken); @@ -70,10 +88,10 @@ public Task ChecksumMatchesAsync(Document document, string name, Checksum => ReadStreamAsync(document, name, checksum: null, cancellationToken); public Task WriteStreamAsync(Project project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => WriteStreamAsync(ProjectKey.ToProjectKey(project), name, stream, checksum, cancellationToken); + => WriteStreamAsync(ProjectKey.ToProjectKey(project), project, name, stream, checksum, cancellationToken); public Task WriteStreamAsync(Document document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => WriteStreamAsync(DocumentKey.ToDocumentKey(document), name, stream, checksum, cancellationToken); + => WriteStreamAsync(DocumentKey.ToDocumentKey(document), document, name, stream, checksum, cancellationToken); public Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken) => WriteStreamAsync(name, stream, checksum: null, cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/PersistentStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DefaultPersistentStorageServiceFactory.cs similarity index 78% rename from src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/PersistentStorageServiceFactory.cs rename to src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DefaultPersistentStorageServiceFactory.cs index f9882b2c84b79..274c98d2c062f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/PersistentStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DefaultPersistentStorageServiceFactory.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.Composition; using Microsoft.CodeAnalysis.Host.Mef; @@ -15,15 +13,15 @@ namespace Microsoft.CodeAnalysis.Host /// projects or documents across runtime sessions. /// [ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), ServiceLayer.Default), Shared] - internal class PersistentStorageServiceFactory : IWorkspaceServiceFactory + internal class DefaultPersistentStorageServiceFactory : IWorkspaceServiceFactory { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public PersistentStorageServiceFactory() + public DefaultPersistentStorageServiceFactory() { } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => NoOpPersistentStorageService.Instance; + => NoOpPersistentStorageService.GetOrThrow(workspaceServices.Workspace.Options); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs index b17e84e027093..3317ed9596bc7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs @@ -38,7 +38,10 @@ public DocumentKey(ProjectKey project, DocumentId id, string? filePath, string n } public static DocumentKey ToDocumentKey(Document document) - => new(ProjectKey.ToProjectKey(document.Project), document.Id, document.FilePath, document.Name); + => ToDocumentKey(ProjectKey.ToProjectKey(document.Project), document.State); + + public static DocumentKey ToDocumentKey(ProjectKey projectKey, TextDocumentState state) + => new(projectKey, state.Id, state.FilePath, state.Name); public bool Equals(DocumentKey x, DocumentKey y) => x.Id == y.Id; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs index b418ca5a3890a..8feb549367738 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs @@ -60,27 +60,48 @@ internal interface IChecksummedPersistentStorage : IPersistentStorage Task ReadStreamAsync(DocumentKey document, string name, Checksum checksum = null, CancellationToken cancellationToken = default); /// - /// Reads the stream for the solution with the given . An optional - /// can be provided to store along with the data. This can be used along with ReadStreamAsync with future - /// reads to ensure the data is only read back if it matches that checksum. + /// Reads the stream for the solution with the given . An optional can be provided to store along with the data. This can be used along with ReadStreamAsync + /// with future reads to ensure the data is only read back if it matches that checksum. + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// /// Task WriteStreamAsync(string name, Stream stream, Checksum checksum = null, CancellationToken cancellationToken = default); /// - /// Reads the stream for the with the given . An optional - /// can be provided to store along with the data. This can be used along with ReadStreamAsync with future - /// reads to ensure the data is only read back if it matches that checksum. + /// Reads the stream for the with the given . An optional + /// can be provided to store along with the data. This can be used along with + /// ReadStreamAsync with future reads to ensure the data is only read back if it matches that checksum. + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// /// Task WriteStreamAsync(Project project, string name, Stream stream, Checksum checksum = null, CancellationToken cancellationToken = default); /// - /// Reads the stream for the with the given . An optional - /// can be provided to store along with the data. This can be used along with ReadStreamAsync with future - /// reads to ensure the data is only read back if it matches that checksum. + /// Reads the stream for the with the given . An optional + /// can be provided to store along with the data. This can be used along with + /// ReadStreamAsync with future reads to ensure the data is only read back if it matches that checksum. + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// /// Task WriteStreamAsync(Document document, string name, Stream stream, Checksum checksum = null, CancellationToken cancellationToken = default); + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// Task WriteStreamAsync(ProjectKey projectKey, string name, Stream stream, Checksum checksum = null, CancellationToken cancellationToken = default); + + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum checksum = null, CancellationToken cancellationToken = default); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs index c15c8d1601937..a602e12aceedd 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs @@ -20,8 +20,22 @@ public interface IPersistentStorage : IDisposable, IAsyncDisposable Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default); Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default); + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default); + + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken = default); + + /// + /// Returns if the data was successfully persisted to the storage subsystem. Subsequent + /// calls to read the same keys should succeed if called within the same session. + /// Task WriteStreamAsync(Document document, 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 d2bf20ad513a6..0f2784a222134 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs @@ -2,22 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host { internal class NoOpPersistentStorage : IChecksummedPersistentStorage { - public static readonly IChecksummedPersistentStorage Instance = new NoOpPersistentStorage(); + private static readonly IChecksummedPersistentStorage Instance = new NoOpPersistentStorage(); private NoOpPersistentStorage() { } + public static IChecksummedPersistentStorage GetOrThrow(OptionSet optionSet) + => optionSet.GetOption(StorageOptions.DatabaseMustSucceed) + ? throw new InvalidOperationException("Database was not supported") + : Instance; + public void Dispose() { } @@ -89,5 +97,10 @@ public Task WriteStreamAsync(ProjectKey projectKey, string name, Stream st public Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum checksum, CancellationToken cancellationToken) => SpecializedTasks.False; + + public struct TestAccessor + { + public static readonly IChecksummedPersistentStorage StorageInstance = Instance; + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs index 4f00907cfeb2e..404d7cd1dccf6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs @@ -2,33 +2,45 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.CodeAnalysis.Storage; namespace Microsoft.CodeAnalysis.Host { internal class NoOpPersistentStorageService : IChecksummedPersistentStorageService { +#if DOTNET_BUILD_FROM_SOURCE public static readonly IPersistentStorageService Instance = new NoOpPersistentStorageService(); +#else + private static readonly IPersistentStorageService Instance = new NoOpPersistentStorageService(); +#endif private NoOpPersistentStorageService() { } + public static IPersistentStorageService GetOrThrow(OptionSet optionSet) + => optionSet.GetOption(StorageOptions.DatabaseMustSucceed) + ? throw new InvalidOperationException("Database was not supported") + : Instance; + public IPersistentStorage GetStorage(Solution solution) - => NoOpPersistentStorage.Instance; + => NoOpPersistentStorage.GetOrThrow(solution.Options); public ValueTask GetStorageAsync(Solution solution, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.Instance); + => new(NoOpPersistentStorage.GetOrThrow(solution.Options)); ValueTask IChecksummedPersistentStorageService.GetStorageAsync(Solution solution, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.Instance); + => new(NoOpPersistentStorage.GetOrThrow(solution.Options)); ValueTask IChecksummedPersistentStorageService.GetStorageAsync(Solution solution, bool checkBranchId, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.Instance); + => new(NoOpPersistentStorage.GetOrThrow(solution.Options)); ValueTask IChecksummedPersistentStorageService.GetStorageAsync(Workspace workspace, SolutionKey solutionKey, bool checkBranchId, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.Instance); + => new(NoOpPersistentStorage.GetOrThrow(workspace.Options)); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/ProjectKey.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/ProjectKey.cs index 5a36a9dc26704..e93a41f540a19 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/ProjectKey.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/ProjectKey.cs @@ -44,6 +44,9 @@ public static ProjectKey ToProjectKey(Project project) => ToProjectKey(project.Solution.State, project.State); public static ProjectKey ToProjectKey(SolutionState solutionState, ProjectState projectState) - => new(SolutionKey.ToSolutionKey(solutionState), projectState.Id, projectState.FilePath, projectState.Name, projectState.GetParseOptionsChecksum()); + => ToProjectKey(SolutionKey.ToSolutionKey(solutionState), projectState); + + public static ProjectKey ToProjectKey(SolutionKey solutionKey, ProjectState projectState) + => new(solutionKey, projectState.Id, projectState.FilePath, projectState.Name, projectState.GetParseOptionsChecksum()); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/Status/IWorkspaceStatusService.cs b/src/Workspaces/Core/Portable/Workspace/Host/Status/IWorkspaceStatusService.cs index f1b44f1badbbb..7d90dc7e6af2e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/Status/IWorkspaceStatusService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/Status/IWorkspaceStatusService.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.Threading; using System.Threading.Tasks; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs index 76d1d2b4a3054..b823cdd355190 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumWithChildren.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.Collections.Generic; using System.Linq; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 7340b38f9aa5e..d18412e3f4bd7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -155,9 +155,11 @@ public bool SupportsSemanticModel /// Gets the for this document asynchronously. /// /// - /// The returned syntax tree can be if the returns . This function will return - /// the same value if called multiple times. + /// The returned syntax tree can be if the returns . This function may cause computation to occur the first time it is called, but will return + /// a cached result every subsequent time. 's can hold onto their roots lazily. So calls + /// to or may end up causing computation + /// to occur at that point. /// public Task GetSyntaxTreeAsync(CancellationToken cancellationToken = default) { @@ -440,7 +442,7 @@ public ImmutableArray GetLinkedDocumentIds() /// /// Use this method to gain access to potentially incomplete semantics quickly. /// - internal Document WithFrozenPartialSemantics(CancellationToken cancellationToken) + internal virtual Document WithFrozenPartialSemantics(CancellationToken cancellationToken) { var solution = this.Project.Solution; var workspace = solution.Workspace; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index fcab0b0159287..1d895839cc2b5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -107,7 +107,7 @@ private static string GetSyntaxTreeFilePath(DocumentInfo.DocumentAttributes info : ""; } - private static ValueSource CreateLazyFullyParsedTree( + protected static ValueSource CreateLazyFullyParsedTree( ValueSource newTextSource, ProjectId cacheKey, string? filePath, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index be943efd7c01a..5f2e39a2f8fc6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -255,6 +255,20 @@ public bool ContainsAnalyzerConfigDocument(DocumentId documentId) return await GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); } + /// + /// Gets a document, additional document, analyzer config document or a source generated document in this solution with the specified document ID. + /// + internal async ValueTask GetTextDocumentAsync(DocumentId documentId, CancellationToken cancellationToken = default) + { + var document = GetDocument(documentId) ?? GetAdditionalDocument(documentId) ?? GetAnalyzerConfigDocument(documentId); + if (document != null) + { + return document; + } + + return await GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); + } + /// /// Gets all source generated documents in this project. /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 300b3bb806e4b..4c96f8797db96 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -56,6 +56,11 @@ internal partial class ProjectState private AnalyzerOptions? _lazyAnalyzerOptions; + /// + /// Backing field for ; this is a default ImmutableArray if it hasn't been computed yet. + /// + private ImmutableArray _lazySourceGenerators; + private ProjectState( ProjectInfo projectInfo, HostLanguageServices languageServices, @@ -581,6 +586,20 @@ public ProjectState WithAnalyzerReferences(IEnumerable analyz return With(projectInfo: ProjectInfo.WithAnalyzerReferences(analyzerReferences).WithVersion(Version.GetNewerVersion())); } + public ImmutableArray SourceGenerators + { + get + { + if (_lazySourceGenerators.IsDefault) + { + var generators = AnalyzerReferences.SelectMany(a => a.GetGenerators(this.Language)).ToImmutableArray(); + ImmutableInterlocked.InterlockedInitialize(ref _lazySourceGenerators, generators); + } + + return _lazySourceGenerators; + } + } + public ProjectState AddDocuments(ImmutableArray documents) { Debug.Assert(!documents.Any(d => DocumentStates.Contains(d.Id))); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index d515dfa7419f8..63e17623c5e5a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -203,6 +203,21 @@ private static Project CreateProject(ProjectId projectId, Solution solution) return project.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken); } + /// + /// Gets a document, additional document, analyzer config document or a source generated document in this solution with the specified document ID. + /// + internal ValueTask GetTextDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken = default) + { + var project = GetProject(documentId?.ProjectId); + if (project == null) + { + return default; + } + + Contract.ThrowIfNull(documentId); + return project.GetTextDocumentAsync(documentId, cancellationToken); + } + /// /// Gets the additional document in this solution with the specified document ID. /// @@ -1644,7 +1659,7 @@ internal async Task WithMergedLinkedFileChangesAsync( CancellationToken cancellationToken = default) { // we only log sessioninfo for actual changes committed to workspace which should exclude ones from preview - var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution), logSessionInfo: solutionChanges != null); + var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution)); return (await session.MergeDiffsAsync(mergeConflictHandler, cancellationToken).ConfigureAwait(false)).MergedSolution; } @@ -1729,6 +1744,21 @@ public Solution WithDocumentText(IEnumerable documentIds, SourceTex return new Solution(newState); } + /// + /// Returns a new Solution that will always produce a specific output for a generated file. This is used only in the + /// implementation of where if a user has a source + /// generated file open, we need to make sure everything lines up. + /// + internal Document WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdentity documentIdentity, SourceText text) + { + var newState = _state.WithFrozenSourceGeneratedDocument(documentIdentity, text); + var newSolution = newState != _state ? new Solution(newState) : this; + var newDocumentState = newState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); + Contract.ThrowIfNull(newDocumentState, "Because we just froze this document, it should always exist."); + var newProject = newSolution.GetRequiredProject(newDocumentState.Id.ProjectId); + return newProject.GetOrCreateSourceGeneratedDocument(newDocumentState); + } + /// /// Gets an objects that lists the added, changed and removed projects between /// this solution and the specified solution. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs index f95effe3f3b8f..3af5e1a6c1c03 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs @@ -15,6 +15,27 @@ public virtual Task TransformCompilationAsync(Compilation oldCompil { return Task.FromResult(oldCompilation); } + + /// + /// Whether or not can be called on Compilations that may contain + /// generated documents. + /// + /// + /// Most translation actions add or remove a single syntax tree which means we can do the "same" change + /// to a compilation that contains the generated files and one that doesn't; however some translation actions + /// (like ) will unilaterally remove all trees, and would have unexpected + /// side effects. This opts those out of operating on ones with generated documents where there would be side effects. + /// + public abstract bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput { get; } + + /// + /// When changes are made to a solution, we make a list of translation actions. If multiple similar changes happen in rapid + /// succession, we may be able to merge them without holding onto intermediate state. + /// + /// The action prior to this one. May be a different type. + /// A non-null if we could create a merged one, null otherwise. + public virtual CompilationAndGeneratorDriverTranslationAction? TryMergeWithPrior(CompilationAndGeneratorDriverTranslationAction priorAction) + => null; } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs index 44c59dc629cd8..1ae69d5f48c37 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs @@ -31,6 +31,21 @@ public override Task TransformCompilationAsync(Compilation oldCompi } public DocumentId DocumentId => _newState.Attributes.Id; + + // Replacing a single tree doesn't impact the generated trees in a compilation, so we can use this against + // compilations that have generated trees. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + + public override CompilationAndGeneratorDriverTranslationAction? TryMergeWithPrior(CompilationAndGeneratorDriverTranslationAction priorAction) + { + if (priorAction is TouchDocumentAction priorTouchAction && + priorTouchAction._newState == this._oldState) + { + return new TouchDocumentAction(priorTouchAction._oldState, this._newState); + } + + return null; + } } internal sealed class TouchAdditionalDocumentAction : CompilationAndGeneratorDriverTranslationAction @@ -46,6 +61,22 @@ public TouchAdditionalDocumentAction(TextDocumentState oldState, TextDocumentSta _oldState = oldState; _newState = newState; } + + // Changing an additional document doesn't change the compilation directly, so we can "apply" the + // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping + // the compilation with stale trees around, answering true is still important. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; + + public override CompilationAndGeneratorDriverTranslationAction? TryMergeWithPrior(CompilationAndGeneratorDriverTranslationAction priorAction) + { + if (priorAction is TouchAdditionalDocumentAction priorTouchAction && + priorTouchAction._newState == this._oldState) + { + return new TouchAdditionalDocumentAction(priorTouchAction._oldState, this._newState); + } + + return null; + } } internal sealed class RemoveDocumentsAction : CompilationAndGeneratorDriverTranslationAction @@ -68,6 +99,9 @@ public override async Task TransformCompilationAsync(Compilation ol return oldCompilation.RemoveSyntaxTrees(syntaxTrees); } + + // This action removes the specified trees, but leaves the generated trees untouched. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class AddDocumentsAction : CompilationAndGeneratorDriverTranslationAction @@ -90,6 +124,9 @@ public override async Task TransformCompilationAsync(Compilation ol return oldCompilation.AddSyntaxTrees(syntaxTrees); } + + // This action adds the specified trees, but leaves the generated trees untouched. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class ReplaceAllSyntaxTreesAction : CompilationAndGeneratorDriverTranslationAction @@ -113,6 +150,9 @@ public override async Task TransformCompilationAsync(Compilation ol return oldCompilation.RemoveAllSyntaxTrees().AddSyntaxTrees(syntaxTrees); } + + // Because this removes all trees, it'd also remove the generated trees. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => false; } internal sealed class ProjectCompilationOptionsAction : CompilationAndGeneratorDriverTranslationAction @@ -128,6 +168,10 @@ public override Task TransformCompilationAsync(Compilation oldCompi { return Task.FromResult(oldCompilation.WithOptions(_options)); } + + // Updating the options of a compilation doesn't require us to reparse trees, so we can use this to update + // compilations with stale generated trees. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class ProjectAssemblyNameAction : CompilationAndGeneratorDriverTranslationAction @@ -143,6 +187,10 @@ public override Task TransformCompilationAsync(Compilation oldCompi { return Task.FromResult(oldCompilation.WithAssemblyName(_assemblyName)); } + + // Updating the options of a compilation doesn't require us to reparse trees, so we can use this to update + // compilations with stale generated trees. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class AddAnalyzerReferencesAction : CompilationAndGeneratorDriverTranslationAction @@ -158,6 +206,11 @@ public AddAnalyzerReferencesAction(ImmutableArray analyzerRef _analyzerReferences = analyzerReferences; _language = language; } + + // Changing analyzer references doesn't change the compilation directly, so we can "apply" the + // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping + // the compilation with stale trees around, answering true is still important. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class RemoveAnalyzerReferencesAction : CompilationAndGeneratorDriverTranslationAction @@ -173,6 +226,11 @@ public RemoveAnalyzerReferencesAction(ImmutableArray analyzer _analyzerReferences = analyzerReferences; _language = language; } + + // Changing analyzer references doesn't change the compilation directly, so we can "apply" the + // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping + // the compilation with stale trees around, answering true is still important. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class AddAdditionalDocumentsAction : CompilationAndGeneratorDriverTranslationAction @@ -186,6 +244,11 @@ public AddAdditionalDocumentsAction(ImmutableArray additional { _additionalDocuments = additionalDocuments; } + + // Changing an additional document doesn't change the compilation directly, so we can "apply" the + // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping + // the compilation with stale trees around, answering true is still important. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } internal sealed class RemoveAdditionalDocumentsAction : CompilationAndGeneratorDriverTranslationAction @@ -199,6 +262,11 @@ public RemoveAdditionalDocumentsAction(ImmutableArray additio { _additionalDocuments = additionalDocuments; } + + // Changing an additional document doesn't change the compilation directly, so we can "apply" the + // translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping + // the compilation with stale trees around, answering true is still important. + public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index ca62af4dfa339..96ee1b8160480 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -92,6 +92,7 @@ protected State( public static State Create( Compilation compilation, TextDocumentStates generatedDocuments, + Compilation? compilationWithGeneratedDocuments, ImmutableArray> intermediateProjects) { Contract.ThrowIfTrue(intermediateProjects.IsDefault); @@ -101,7 +102,7 @@ public static State Create( // if our referenced projects are changing, so we'll have to rerun to consume changes. return intermediateProjects.Length == 0 ? new FullDeclarationState(compilation, generatedDocuments, generatedDocumentsAreFinal: false) - : (State)new InProgressState(compilation, generatedDocuments, intermediateProjects); + : (State)new InProgressState(compilation, generatedDocuments, compilationWithGeneratedDocuments, intermediateProjects); } public static ValueSource> CreateValueSource( @@ -120,11 +121,24 @@ public static ValueSource> CreateValueSource( /// private sealed class InProgressState : State { - public ImmutableArray<(ProjectState state, CompilationAndGeneratorDriverTranslationAction action)> IntermediateProjects { get; } + /// + /// The list of changes that have happened since we last computed a compilation. The oldState corresponds to + /// the state of the project prior to the mutation. + /// + public ImmutableArray<(ProjectState oldState, CompilationAndGeneratorDriverTranslationAction action)> IntermediateProjects { get; } + + /// + /// The result of taking the original completed compilation that had generated documents and updating them by + /// apply the ; this is not a correct snapshot in that + /// the generators have not been rerun, but may be reusable if the generators are later found to give the + /// same output. + /// + public Compilation? CompilationWithGeneratedDocuments { get; } public InProgressState( Compilation inProgressCompilation, TextDocumentStates generatedDocuments, + Compilation? compilationWithGeneratedDocuments, ImmutableArray<(ProjectState state, CompilationAndGeneratorDriverTranslationAction action)> intermediateProjects) : base(compilationWithoutGeneratedDocuments: new ConstantValueSource>(inProgressCompilation), declarationOnlyCompilation: null, @@ -135,6 +149,7 @@ public InProgressState( Contract.ThrowIfFalse(intermediateProjects.Length > 0); this.IntermediateProjects = intermediateProjects; + this.CompilationWithGeneratedDocuments = compilationWithGeneratedDocuments; } } @@ -244,7 +259,7 @@ public static FinalState Create( // Keep track of information about symbols from this Compilation. This will help support other APIs // the solution exposes that allows the user to map back from symbols to project information. - var unrootedSymbolSet = GetUnrootedSymbols(finalCompilation); + var unrootedSymbolSet = UnrootedSymbolSet.Create(finalCompilation); RecordAssemblySymbols(projectId, finalCompilation, metadataReferenceToProjectId); return new FinalState( @@ -292,36 +307,6 @@ private static void RecordSourceOfAssemblySymbol(ISymbol? assemblyOrModuleSymbol Debug.Assert(tmp == projectId); } } - - private static UnrootedSymbolSet GetUnrootedSymbols(Compilation compilation) - { - var primaryAssembly = new WeakReference(compilation.Assembly); - - // The dynamic type is also unrooted (i.e. doesn't point back at the compilation or source - // assembly). So we have to keep track of it so we can get back from it to a project in case the - // underlying compilation is GC'ed. - var primaryDynamic = new WeakReference( - compilation.Language == LanguageNames.CSharp ? compilation.DynamicType : null); - - // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. - using var _ = ArrayBuilder<(int hashcode, WeakReference symbol)>.GetInstance( - compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols); - - foreach (var reference in compilation.References) - { - var symbol = compilation.GetAssemblyOrModuleSymbol(reference); - if (symbol == null) - continue; - - secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), new WeakReference(symbol))); - } - - // Sort all the secondary symbols by their hash. This will allow us to easily binary search for - // them afterwards. Note: it is fine for multiple symbols to have the same reference hash. The - // search algorithm will account for that. - secondarySymbols.Sort(WeakSymbolComparer.Instance); - return new UnrootedSymbolSet(primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); - } } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 3f9acc2af2fdb..1ccf65b54804c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -30,7 +30,7 @@ internal partial class SolutionState /// compilation for that project. As the compilation is being built, the partial results are /// stored as well so that they can be used in the 'in progress' workspace snapshot. /// - private partial class CompilationTracker + private partial class CompilationTracker : ICompilationTracker { private static readonly Func s_logBuildCompilationAsync = state => string.Join(",", state.AssemblyName, state.DocumentStates.Count); @@ -80,19 +80,6 @@ private void WriteState(State state, SolutionServices solutionServices) Volatile.Write(ref _stateDoNotAccessDirectly, state); } - /// - /// Returns true if this tracker currently either points to a compilation, has an in-progress - /// compilation being computed, or has a skeleton reference. Note: this is simply a weak - /// statement about the tracker at this exact moment in time. Immediately after this returns - /// the tracker might change and may no longer have a final compilation (for example, if the - /// retainer let go of it) or might not have an in-progress compilation (for example, if the - /// background compiler finished with it). - /// - /// Because of the above limitations, this should only be used by clients as a weak form of - /// information about the tracker. For example, a client may see that a tracker has no - /// compilation and may choose to throw it away knowing that it could be reconstructed at a - /// later point if necessary. - /// public bool HasCompilation { get @@ -102,20 +89,6 @@ public bool HasCompilation } } - /// - /// Returns if this / could produce the - /// given . The symbol must be a , or . - /// - /// - /// If is true, then will not be considered - /// when answering this question. In other words, if is an and is then this will only - /// return true if the symbol is . If is - /// false, then it can return true if is or any - /// of the symbols returned by for - /// any of the references of the . - /// public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary) { Debug.Assert(symbol.Kind == SymbolKind.Assembly || @@ -131,59 +104,14 @@ public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary) return false; } - if (primary) - { - return symbol.Equals(unrootedSymbolSet.Value.PrimaryAssemblySymbol.GetTarget()) || - symbol.Equals(unrootedSymbolSet.Value.PrimaryDynamicSymbol.GetTarget()); - } - else - { - var secondarySymbols = unrootedSymbolSet.Value.SecondaryReferencedSymbols; - - var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); - - // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find - // the location we should start looking at. - var index = secondarySymbols.BinarySearch((symbolHash, null!), WeakSymbolComparer.Instance); - if (index < 0) - return false; - - // Could have multiple symbols with the same hash. They will all be placed next to each other, - // so walk backward to hit the first. - while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash) - index--; - - // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match. - while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) - { - var cached = secondarySymbols[index].symbol; - if (cached.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) - return true; - - index++; - } - - return false; - } - } - - private class WeakSymbolComparer : IComparer<(int hashcode, WeakReference symbol)> - { - public static readonly WeakSymbolComparer Instance = new WeakSymbolComparer(); - - private WeakSymbolComparer() - { - } - - public int Compare((int hashcode, WeakReference symbol) x, (int hashcode, WeakReference symbol) y) - => x.hashcode - y.hashcode; + return unrootedSymbolSet.Value.ContainsAssemblyOrModuleOrDynamic(symbol, primary); } /// /// Creates a new instance of the compilation info, retaining any already built /// compilation state as the now 'old' state /// - public CompilationTracker Fork( + public ICompilationTracker Fork( ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate = null, bool clone = false, @@ -201,13 +129,33 @@ public CompilationTracker Fork( var intermediateProjects = state is InProgressState inProgressState ? inProgressState.IntermediateProjects - : ImmutableArray.Create<(ProjectState, CompilationAndGeneratorDriverTranslationAction)>(); + : ImmutableArray.Create<(ProjectState oldState, CompilationAndGeneratorDriverTranslationAction action)>(); + + if (translate is not null) + { + // We have a translation action; are we able to merge it with the prior one? + var merged = false; + if (intermediateProjects.Any()) + { + var (priorState, priorAction) = intermediateProjects.Last(); + var mergedTranslation = translate.TryMergeWithPrior(priorAction); + if (mergedTranslation != null) + { + // We can replace the prior action with this new one + intermediateProjects = intermediateProjects.SetItem(intermediateProjects.Length - 1, + (oldState: priorState, mergedTranslation)); + merged = true; + } + } - var newIntermediateProjects = translate == null - ? intermediateProjects - : intermediateProjects.Add((ProjectState, translate)); + if (!merged) + { + // Just add it to the end + intermediateProjects = intermediateProjects.Add((oldState: this.ProjectState, translate)); + } + } - var newState = State.Create(newInProgressCompilation, state.GeneratedDocuments, newIntermediateProjects); + var newState = State.Create(newInProgressCompilation, state.GeneratedDocuments, state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects); return new CompilationTracker(newProject, newState); } @@ -218,7 +166,7 @@ public CompilationTracker Fork( if (translate != null) { var intermediateProjects = ImmutableArray.Create((this.ProjectState, translate)); - return new CompilationTracker(newProject, new InProgressState(declarationOnlyCompilation, state.GeneratedDocuments, intermediateProjects)); + return new CompilationTracker(newProject, new InProgressState(declarationOnlyCompilation, state.GeneratedDocuments, compilationWithGeneratedDocuments: state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects)); } return new CompilationTracker(newProject, new LightDeclarationState(declarationOnlyCompilation, state.GeneratedDocuments, generatedDocumentsAreFinal: false)); @@ -232,10 +180,10 @@ public CompilationTracker Fork( /// /// Creates a fork with the same final project. /// - public CompilationTracker Clone() + public ICompilationTracker Clone() => this.Fork(this.ProjectState, clone: true); - public CompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) + public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) { GetPartialCompilationState( solution, docState.Id, @@ -311,7 +259,7 @@ private void GetPartialCompilationState( // We'll add in whatever generated documents we do have; these may be from a prior run prior to some changes // being made to the project, but it's the best we have so we'll use it. - inProgressCompilation = compilationWithoutGeneratedDocuments.AddSyntaxTrees(sourceGeneratedDocuments.States.Select(state => state.SyntaxTree)); + inProgressCompilation = compilationWithoutGeneratedDocuments.AddSyntaxTrees(sourceGeneratedDocuments.States.Select(state => state.GetSyntaxTree(cancellationToken))); // This is likely a bug. It seems possible to pass out a partial compilation state that we don't // properly record assembly symbols for. @@ -320,7 +268,7 @@ private void GetPartialCompilationState( return; } - inProgressProject = inProgressState != null ? inProgressState.IntermediateProjects.First().state : this.ProjectState; + inProgressProject = inProgressState != null ? inProgressState.IntermediateProjects.First().oldState : this.ProjectState; // if we already have a final compilation we are done. if (compilationWithoutGeneratedDocuments != null && state is FinalState finalState) @@ -354,7 +302,7 @@ private void GetPartialCompilationState( inProgressCompilation = compilationWithoutGeneratedDocuments; } - inProgressCompilation = inProgressCompilation.AddSyntaxTrees(sourceGeneratedDocuments.States.Select(state => state.SyntaxTree)); + inProgressCompilation = inProgressCompilation.AddSyntaxTrees(sourceGeneratedDocuments.States.Select(state => state.GetSyntaxTree(cancellationToken))); // Now add in back a consistent set of project references. For project references // try to get either a CompilationReference or a SkeletonReference. This ensures @@ -495,7 +443,7 @@ private async Task GetOrBuildDeclarationCompilationAsync(SolutionSe return compilation; } - compilation = await BuildDeclarationCompilationFromInProgressAsync(solutionServices, (InProgressState)state, compilation, cancellationToken).ConfigureAwait(false); + (compilation, _) = await BuildDeclarationCompilationFromInProgressAsync(solutionServices, (InProgressState)state, compilation, cancellationToken).ConfigureAwait(false); // We must have an in progress compilation. Build off of that. return compilation; @@ -587,7 +535,7 @@ private Task BuildCompilationInfoAsync( if (state.DeclarationOnlyCompilation != null) { // we have declaration only compilation. build final one from it. - return FinalizeCompilationAsync(solution, state.DeclarationOnlyCompilation, authoritativeGeneratedDocuments, nonAuthoritativeGeneratedDocuments, cancellationToken); + return FinalizeCompilationAsync(solution, state.DeclarationOnlyCompilation, authoritativeGeneratedDocuments, nonAuthoritativeGeneratedDocuments, compilationWithStaleGeneratedTrees: null, cancellationToken); } // We've got nothing. Build it from scratch :( @@ -597,7 +545,13 @@ private Task BuildCompilationInfoAsync( if (state is FullDeclarationState or FinalState) { // We have a declaration compilation, use it to reconstruct the final compilation - return FinalizeCompilationAsync(solution, compilation, authoritativeGeneratedDocuments, nonAuthoritativeGeneratedDocuments, cancellationToken); + return FinalizeCompilationAsync( + solution, + compilation, + authoritativeGeneratedDocuments, + nonAuthoritativeGeneratedDocuments, + compilationWithStaleGeneratedTrees: null, + cancellationToken); } else { @@ -613,7 +567,12 @@ private async Task BuildCompilationInfoFromScratchAsync( { var compilation = await BuildDeclarationCompilationFromScratchAsync(solution.Services, cancellationToken).ConfigureAwait(false); - return await FinalizeCompilationAsync(solution, compilation, authoritativeGeneratedDocuments: null, nonAuthoritativeGeneratedDocuments: TextDocumentStates.Empty, cancellationToken).ConfigureAwait(false); + return await FinalizeCompilationAsync( + solution, compilation, + authoritativeGeneratedDocuments: null, + nonAuthoritativeGeneratedDocuments: TextDocumentStates.Empty, + compilationWithStaleGeneratedTrees: null, + cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -675,8 +634,8 @@ private async Task BuildFinalStateFromInProgressStateAsync( { try { - var compilation = await BuildDeclarationCompilationFromInProgressAsync(solution.Services, state, inProgressCompilation, cancellationToken).ConfigureAwait(false); - return await FinalizeCompilationAsync(solution, compilation, authoritativeGeneratedDocuments: null, nonAuthoritativeGeneratedDocuments: state.GeneratedDocuments, cancellationToken).ConfigureAwait(false); + var (compilationWithoutGenerators, compilationWithGenerators) = await BuildDeclarationCompilationFromInProgressAsync(solution.Services, state, inProgressCompilation, cancellationToken).ConfigureAwait(false); + return await FinalizeCompilationAsync(solution, compilationWithoutGenerators, authoritativeGeneratedDocuments: null, nonAuthoritativeGeneratedDocuments: state.GeneratedDocuments, compilationWithGenerators, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -684,25 +643,57 @@ private async Task BuildFinalStateFromInProgressStateAsync( } } - private async Task BuildDeclarationCompilationFromInProgressAsync( - SolutionServices solutionServices, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken) + private async Task<(Compilation compilationWithoutGenerators, Compilation? compilationWithGenerators)> BuildDeclarationCompilationFromInProgressAsync( + SolutionServices solutionServices, InProgressState state, Compilation compilationWithoutGenerators, CancellationToken cancellationToken) { try { + var compilationWithGenerators = state.CompilationWithGeneratedDocuments; + + // If compilationWithGenerators is the same as compilationWithoutGenerators, then it means a prior run of generators + // didn't produce any files. In that case, we'll just make compilationWithGenerators null so we avoid doing any + // transformations of it multiple times. Otherwise the transformations below and in FinalizeCompilationAsync will try + // to update both at once, which is functionally fine but just unnecessary work. This function is always allowed to return + // null for compilationWithGenerators in the end, so there's no harm there. + if (compilationWithGenerators == compilationWithoutGenerators) + { + compilationWithGenerators = null; + } + var intermediateProjects = state.IntermediateProjects; while (intermediateProjects.Length > 0) { cancellationToken.ThrowIfCancellationRequested(); - var compilationTranslationAction = intermediateProjects[0].action; - inProgressCompilation = await compilationTranslationAction.TransformCompilationAsync(inProgressCompilation, cancellationToken).ConfigureAwait(false); + // We have a list of transformations to get to our final compilation; take the first transformation and apply it. + var intermediateProject = intermediateProjects[0]; + + compilationWithoutGenerators = await intermediateProject.action.TransformCompilationAsync(compilationWithoutGenerators, cancellationToken).ConfigureAwait(false); + + if (compilationWithGenerators != null) + { + // Also transform the compilation that has generated files; we won't do that though if the transformation either would cause problems with + // the generated documents, or if don't have any source generators in the first place. + if (intermediateProject.action.CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput && + intermediateProject.oldState.SourceGenerators.Any()) + { + compilationWithGenerators = await intermediateProject.action.TransformCompilationAsync(compilationWithGenerators, cancellationToken).ConfigureAwait(false); + } + else + { + compilationWithGenerators = null; + } + } + + // We have updated state, so store this new result; this allows us to drop the intermediate state we already processed + // even if we were to get cancelled at a later point. intermediateProjects = intermediateProjects.RemoveAt(0); - this.WriteState(State.Create(inProgressCompilation, state.GeneratedDocuments, intermediateProjects), solutionServices); + this.WriteState(State.Create(compilationWithoutGenerators, state.GeneratedDocuments, compilationWithGenerators, intermediateProjects), solutionServices); } - return inProgressCompilation; + return (compilationWithoutGenerators, compilationWithGenerators); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -737,9 +728,10 @@ public CompilationInfo(Compilation compilation, bool hasSuccessfullyLoaded, Text /// and when applicable for the current compilation. private async Task FinalizeCompilationAsync( SolutionState solution, - Compilation compilation, + Compilation compilationWithoutGenerators, TextDocumentStates? authoritativeGeneratedDocuments, TextDocumentStates nonAuthoritativeGeneratedDocuments, + Compilation? compilationWithStaleGeneratedTrees, CancellationToken cancellationToken) { try @@ -766,11 +758,19 @@ private async Task FinalizeCompilationAsync( // if the referenced project is a submission project must be a submission as well: Debug.Assert(this.ProjectState.IsSubmission); + // We now need to (potentially) update the prior submission compilation. That Compilation is held in the + // ScriptCompilationInfo that we need to replace as a unit. var previousSubmissionCompilation = await solution.GetCompilationAsync(projectReference.ProjectId, cancellationToken).ConfigureAwait(false); - compilation = compilation.WithScriptCompilationInfo( - compilation.ScriptCompilationInfo!.WithPreviousScriptCompilation(previousSubmissionCompilation!)); + if (compilationWithoutGenerators.ScriptCompilationInfo!.PreviousScriptCompilation != previousSubmissionCompilation) + { + compilationWithoutGenerators = compilationWithoutGenerators.WithScriptCompilationInfo( + compilationWithoutGenerators.ScriptCompilationInfo!.WithPreviousScriptCompilation(previousSubmissionCompilation!)); + + compilationWithStaleGeneratedTrees = compilationWithStaleGeneratedTrees?.WithScriptCompilationInfo( + compilationWithStaleGeneratedTrees.ScriptCompilationInfo!.WithPreviousScriptCompilation(previousSubmissionCompilation!)); + } } else { @@ -791,43 +791,63 @@ private async Task FinalizeCompilationAsync( } } - if (!Enumerable.SequenceEqual(compilation.ExternalReferences, newReferences)) + // Now that we know the set of references this compilation should have, update them if they're not already. + // Generators cannot add references, so we can use the same set of references both for the compilation + // that doesn't have generated files, and the one we're trying to reuse that has generated files. + // Since we updated both of these compilations together in response to edits, we only have to check one + // for a potential mismatch. + if (!Enumerable.SequenceEqual(compilationWithoutGenerators.ExternalReferences, newReferences)) { - compilation = compilation.WithReferences(newReferences); + compilationWithoutGenerators = compilationWithoutGenerators.WithReferences(newReferences); + compilationWithStaleGeneratedTrees = compilationWithStaleGeneratedTrees?.WithReferences(newReferences); } - var generators = this.ProjectState.AnalyzerReferences.SelectMany(a => a.GetGenerators(this.ProjectState.Language)).ToImmutableArray(); - // We will finalize the compilation by adding full contents here. // TODO: allow finalize compilation to incrementally update a prior version // https://github.com/dotnet/roslyn/issues/46418 - var compilationWithoutGeneratedFiles = compilation; + Compilation compilationWithGenerators; TextDocumentStates generatedDocuments; if (authoritativeGeneratedDocuments.HasValue) { generatedDocuments = authoritativeGeneratedDocuments.Value; + compilationWithGenerators = compilationWithoutGenerators.AddSyntaxTrees( + await generatedDocuments.States.SelectAsArrayAsync(state => state.GetSyntaxTreeAsync(cancellationToken)).ConfigureAwait(false)); } else { using var generatedDocumentsBuilder = new TemporaryArray(); - if (generators.Any()) + if (ProjectState.SourceGenerators.Any()) { var additionalTexts = this.ProjectState.AdditionalDocumentStates.SelectAsArray(state => new AdditionalTextWithState(state)); var compilationFactory = this.ProjectState.LanguageServices.GetRequiredService(); var generatorDriver = compilationFactory.CreateGeneratorDriver( this.ProjectState.ParseOptions!, - generators, + ProjectState.SourceGenerators, this.ProjectState.AnalyzerOptions.AnalyzerConfigOptionsProvider, additionalTexts); if (generatorDriver != null) { - generatorDriver = generatorDriver.RunGenerators(compilationWithoutGeneratedFiles, cancellationToken); + generatorDriver = generatorDriver.RunGenerators(compilationWithoutGenerators, cancellationToken); + var runResult = generatorDriver.GetRunResult(); + + // We may be able to reuse compilationWithStaleGeneratedTrees if the generated trees are identical. We will assign null + // to compilationWithStaleGeneratedTrees if we at any point realize it can't be used. We'll first check the count of trees + // if that changed then we absolutely can't reuse it. But if the counts match, we'll then see if each generated tree + // content is identical to the prior generation run; if we find a match each time, then the set of the generated trees + // and the prior generated trees are identical. + if (compilationWithStaleGeneratedTrees != null) + { + if (nonAuthoritativeGeneratedDocuments.Count != runResult.Results.Sum(r => r.GeneratedSources.Length)) + { + compilationWithStaleGeneratedTrees = null; + } + } - foreach (var generatorResult in generatorDriver.GetRunResult().Results) + foreach (var generatorResult in runResult.Results) { foreach (var generatedSource in generatorResult.GeneratedSources) { @@ -838,49 +858,70 @@ private async Task FinalizeCompilationAsync( if (existing != null) { - generatedDocumentsBuilder.Add( - existing.WithUpdatedGeneratedContent( + var newDocument = existing.WithUpdatedGeneratedContent( generatedSource.SourceText, - generatedSource.SyntaxTree, - this.ProjectState.ParseOptions!, - cancellationToken)); + this.ProjectState.ParseOptions!); + + generatedDocumentsBuilder.Add(newDocument); + + if (newDocument != existing) + compilationWithStaleGeneratedTrees = null; } else { + // NOTE: the use of generatedSource.SyntaxTree to fetch the path and options is OK, + // since the tree is a lazy tree and that won't trigger the parse. + var identity = SourceGeneratedDocumentIdentity.Generate( + ProjectState.Id, + generatedSource.HintName, + generatorResult.Generator, + generatedSource.SyntaxTree.FilePath); + generatedDocumentsBuilder.Add( SourceGeneratedDocumentState.Create( - generatedSource.HintName, + identity, generatedSource.SourceText, - generatedSource.SyntaxTree, - CreateStableSourceGeneratedDocumentId(ProjectState.Id, generatorResult.Generator, generatedSource.HintName), - generatorResult.Generator, + generatedSource.SyntaxTree.Options, this.ProjectState.LanguageServices, - solution.Services, - cancellationToken)); + solution.Services)); + + // The count of trees was the same, but something didn't match up. Since we're here, at least one tree + // was added, and an equal number must have been removed. Rather than trying to incrementally update + // this compilation, we'll just toss this and re-add all the trees. + compilationWithStaleGeneratedTrees = null; } } } } } - generatedDocuments = new TextDocumentStates(generatedDocumentsBuilder.ToImmutableAndClear()); + // If we didn't null out this compilation, it means we can actually use it + if (compilationWithStaleGeneratedTrees != null) + { + generatedDocuments = nonAuthoritativeGeneratedDocuments; + compilationWithGenerators = compilationWithStaleGeneratedTrees; + } + else + { + generatedDocuments = new TextDocumentStates(generatedDocumentsBuilder.ToImmutableAndClear()); + compilationWithGenerators = compilationWithoutGenerators.AddSyntaxTrees( + await generatedDocuments.States.SelectAsArrayAsync(state => state.GetSyntaxTreeAsync(cancellationToken)).ConfigureAwait(false)); + } } - compilation = compilation.AddSyntaxTrees(generatedDocuments.States.Select(state => state.SyntaxTree)); - var finalState = FinalState.Create( - State.CreateValueSource(compilation, solution.Services), - State.CreateValueSource(compilationWithoutGeneratedFiles, solution.Services), - compilationWithoutGeneratedFiles, + State.CreateValueSource(compilationWithGenerators, solution.Services), + State.CreateValueSource(compilationWithoutGenerators, solution.Services), + compilationWithoutGenerators, hasSuccessfullyLoaded, generatedDocuments, - compilation, + compilationWithGenerators, this.ProjectState.Id, metadataReferenceToProjectId); this.WriteState(finalState, solution.Services); - return new CompilationInfo(compilation, hasSuccessfullyLoaded, generatedDocuments); + return new CompilationInfo(compilationWithGenerators, hasSuccessfullyLoaded, generatedDocuments); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -893,9 +934,15 @@ private async Task FinalizeCompilationAsync( ISourceGenerator generator, string hintName) { + var generatorAssemblyName = SourceGeneratedDocumentIdentity.GetGeneratorAssemblyName(generator); + var generatorTypeName = SourceGeneratedDocumentIdentity.GetGeneratorTypeName(generator); + foreach (var state in states.States) { - if (state.SourceGenerator != generator) + if (state.SourceGeneratorAssemblyName != generatorAssemblyName) + continue; + + if (state.SourceGeneratorTypeName != generatorTypeName) continue; if (state.HintName != hintName) @@ -908,41 +955,6 @@ private async Task FinalizeCompilationAsync( } } - private static DocumentId CreateStableSourceGeneratedDocumentId(ProjectId projectId, ISourceGenerator generator, string hintName) - { - // We want the DocumentId generated for a generated output to be stable between Compilations; this is so features that track - // a document by DocumentId can find it after some change has happened that requires generators to run again. - // To achieve this we'll just do a crytographic hash of the generator name and hint name; the choice of a cryptographic hash - // as opposed to a more generic string hash is we actually want to ensure we don't have collisions. - var generatorName = generator.GetType().FullName; - Contract.ThrowIfNull(generatorName); - - // Combine the strings together; we'll use Encoding.Unicode since that'll match the underlying format; this can be made much - // faster once we're on .NET Core since we could directly treat the strings as ReadOnlySpan. - var projectIdBytes = projectId.Id.ToByteArray(); - using var _ = ArrayBuilder.GetInstance(capacity: (generatorName.Length + hintName.Length + 1) * 2 + projectIdBytes.Length, out var hashInput); - hashInput.AddRange(projectIdBytes); - hashInput.AddRange(Encoding.Unicode.GetBytes(generatorName)); - - // Add a null to separate the generator name and hint name; since this is effectively a joining of UTF-16 bytes - // we'll use a UTF-16 null just to make sure there's absolutely no risk of collision. - hashInput.AddRange(0, 0); - hashInput.AddRange(Encoding.Unicode.GetBytes(hintName)); - - // The particular choice of crypto algorithm here is arbitrary and can be always changed as necessary. - var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(hashInput.ToArray()); - Array.Resize(ref hash, 16); - var guid = new Guid(hash); - - return DocumentId.CreateFromSerialized(projectId, guid, hintName); - } - - /// - /// Get a metadata reference to this compilation info's compilation with respect to - /// another project. For cross language references produce a skeletal assembly. If the - /// compilation is not available, it is built. If a skeletal assembly reference is - /// needed and does not exist, it is also built. - /// public async Task GetMetadataReferenceAsync( SolutionState solution, ProjectState fromProject, @@ -951,7 +963,6 @@ public async Task GetMetadataReferenceAsync( { try { - // if we already have the compilation and its right kind then use it. if (this.ProjectState.LanguageServices == fromProject.LanguageServices && this.TryGetCompilation(out var compilation)) @@ -983,7 +994,7 @@ public async Task GetMetadataReferenceAsync( /// compilation. Only actual compilation references are returned. Could potentially /// return null if nothing can be provided. /// - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) + public CompilationReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) { var state = ReadState(); @@ -1107,6 +1118,12 @@ private async Task HasSuccessfullyLoadedSlowAsync(SolutionState solution, public async ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken) { + // If we don't have any generators, then we know we have no generated files, so we can skip the computation entirely. + if (!this.ProjectState.SourceGenerators.Any()) + { + return TextDocumentStates.Empty; + } + var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); return compilationInfo.GeneratedDocuments; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs new file mode 100644 index 0000000000000..afa606bc6fde1 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + internal partial class SolutionState + { + /// + /// An implementation of that takes a compilation from another compilation tracker and updates it + /// to return a generated document with a specific content, regardless of what the generator actually produces. In other words, it says + /// "take the compilation this other thing produced, and pretend the generator gave this content, even if it wouldn't." + /// + private class GeneratedFileReplacingCompilationTracker : ICompilationTracker + { + private readonly ICompilationTracker _underlyingTracker; + private readonly SourceGeneratedDocumentState _replacedGeneratedDocumentState; + + /// + /// The lazily-produced compilation that has the generated document updated. This is initialized by call to + /// . + /// + [DisallowNull] + private Compilation? _compilationWithReplacement; + + public GeneratedFileReplacingCompilationTracker(ICompilationTracker underlyingTracker, SourceGeneratedDocumentState replacementDocumentState) + { + _underlyingTracker = underlyingTracker; + _replacedGeneratedDocumentState = replacementDocumentState; + } + + public bool HasCompilation => _underlyingTracker.HasCompilation; + + public ProjectState ProjectState => _underlyingTracker.ProjectState; + + public ICompilationTracker Clone() + { + return new GeneratedFileReplacingCompilationTracker(_underlyingTracker.Clone(), _replacedGeneratedDocumentState); + } + + public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary) + { + if (_compilationWithReplacement == null) + { + // We don't have a compilation yet, so this couldn't have came from us + return false; + } + else + { + return UnrootedSymbolSet.Create(_compilationWithReplacement).ContainsAssemblyOrModuleOrDynamic(symbol, primary); + } + } + + public bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken) + { + // TODO: This only needs to be implemented if a feature that operates from a source generated file needs to search for declarations + // with other names; those APIs are only used by certain code fixes which isn't a need for now. This will need to be fixed up when + // we complete https://github.com/dotnet/roslyn/issues/49533. + throw new NotImplementedException(); + } + + public bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(string name, SymbolFilter filter, CancellationToken cancellationToken) + { + // TODO: This only needs to be implemented if a feature that operates from a source generated file needs to search for declarations + // with other names; those APIs are only used by certain code fixes which isn't a need for now. This will need to be fixed up when + // we complete https://github.com/dotnet/roslyn/issues/49533. + throw new NotImplementedException(); + } + + public ICompilationTracker Fork(ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate = null, bool clone = false, CancellationToken cancellationToken = default) + { + // TODO: This only needs to be implemented if a feature that operates from a source generated file then makes + // further mutations to that project, which isn't needed for now. This will be need to be fixed up when we complete + // https://github.com/dotnet/roslyn/issues/49533. + throw new NotImplementedException(); + } + + public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) + { + // Because we override SourceGeneratedDocument.WithFrozenPartialSemantics directly, we shouldn't be able to get here. + throw ExceptionUtilities.Unreachable; + } + + public async Task GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken) + { + // Fast path if we've definitely already done this before + if (_compilationWithReplacement != null) + { + return _compilationWithReplacement; + } + + var underlyingCompilation = await _underlyingTracker.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); + var underlyingSourceGeneratedDocuments = await _underlyingTracker.GetSourceGeneratedDocumentStatesAsync(solution, cancellationToken).ConfigureAwait(false); + + underlyingSourceGeneratedDocuments.TryGetState(_replacedGeneratedDocumentState.Id, out var existingState); + + Compilation newCompilation; + + var newSyntaxTree = await _replacedGeneratedDocumentState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + if (existingState != null) + { + // The generated file still exists in the underlying compilation, but the contents may not match the open file if the open file + // is stale. Replace the syntax tree so we have a tree that matches the text. + var existingSyntaxTree = await existingState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + newCompilation = underlyingCompilation.ReplaceSyntaxTree(existingSyntaxTree, newSyntaxTree); + } + else + { + // The existing output no longer exists in the underlying compilation. This could happen if the user made + // an edit which would cause this file to no longer exist, but they're still operating on an open representation + // of that file. To ensure that this snapshot is still usable, we'll just add this document back in. This is not a + // semantically correct operation, but working on stale snapshots never has that guarantee. + newCompilation = underlyingCompilation.AddSyntaxTrees(newSyntaxTree); + } + + Interlocked.CompareExchange(ref _compilationWithReplacement, newCompilation, null); + + return _compilationWithReplacement; + } + + public Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) + { + return _underlyingTracker.GetDependentSemanticVersionAsync(solution, cancellationToken); + } + + public Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) + { + return _underlyingTracker.GetDependentVersionAsync(solution, cancellationToken); + } + + public async Task GetMetadataReferenceAsync(SolutionState solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) + { + var compilation = await GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); + + // If it's the same language we can just make a CompilationReference + if (this.ProjectState.LanguageServices == fromProject.LanguageServices) + { + return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); + } + else + { + var version = await GetDependentSemanticVersionAsync(solution, cancellationToken).ConfigureAwait(false); + return MetadataOnlyReference.GetOrBuildReference(solution, projectReference, compilation, version, cancellationToken); + } + } + + public CompilationReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) + { + // This method is used if you're forking a solution with partial semantics, and used to quickly produce references. + // So this method should only be called if: + // + // 1. Project A has a open source generated document, and this CompilationTracker represents A + // 2. Project B references that A, and is being frozen for partial semantics. + // + // We generally don't use partial semantics in a different project than the open file, so this isn't a scenario we need to support. + throw new NotImplementedException(); + } + + public async ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken) + { + var underlyingGeneratedDocumentStates = await _underlyingTracker.GetSourceGeneratedDocumentStatesAsync(solution, cancellationToken).ConfigureAwait(false); + + if (underlyingGeneratedDocumentStates.Contains(_replacedGeneratedDocumentState.Id)) + { + // The generated file still exists in the underlying compilation, but the contents may not match the open file if the open file + // is stale. Replace the syntax tree so we have a tree that matches the text. + return underlyingGeneratedDocumentStates.SetState(_replacedGeneratedDocumentState.Id, _replacedGeneratedDocumentState); + } + else + { + // The generated output no longer exists in the underlying compilation. This could happen if the user made + // an edit which would cause this file to no longer exist, but they're still operating on an open representation + // of that file. To ensure that this snapshot is still usable, we'll just add this document back in. This is not a + // semantically correct operation, but working on stale snapshots never has that guarantee. + return underlyingGeneratedDocumentStates.AddRange(ImmutableArray.Create(_replacedGeneratedDocumentState)); + } + } + + public IEnumerable? GetSyntaxTreesWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken) + { + return _underlyingTracker.GetSyntaxTreesWithNameFromDeclarationOnlyCompilation(predicate, filter, cancellationToken); + } + + public Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken) + { + return _underlyingTracker.HasSuccessfullyLoadedAsync(solution, cancellationToken); + } + + public bool TryGetCompilation([NotNullWhen(true)] out Compilation? compilation) + { + compilation = _compilationWithReplacement; + return compilation != null; + } + + public SourceGeneratedDocumentState? TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(DocumentId documentId) + { + if (documentId == _replacedGeneratedDocumentState.Id) + { + return _replacedGeneratedDocumentState; + } + else + { + return _underlyingTracker.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); + } + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs new file mode 100644 index 0000000000000..8240052ca7b65 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis +{ + internal partial class SolutionState + { + private interface ICompilationTracker + { + /// + /// Returns true if this tracker currently either points to a compilation, has an in-progress + /// compilation being computed, or has a skeleton reference. Note: this is simply a weak + /// statement about the tracker at this exact moment in time. Immediately after this returns + /// the tracker might change and may no longer have a final compilation (for example, if the + /// retainer let go of it) or might not have an in-progress compilation (for example, if the + /// background compiler finished with it). + /// + /// Because of the above limitations, this should only be used by clients as a weak form of + /// information about the tracker. For example, a client may see that a tracker has no + /// compilation and may choose to throw it away knowing that it could be reconstructed at a + /// later point if necessary. + /// + bool HasCompilation { get; } + ProjectState ProjectState { get; } + + ICompilationTracker Clone(); + + /// + /// Returns if this / could produce the + /// given . The symbol must be a , or . + /// + /// + /// If is true, then will not be considered + /// when answering this question. In other words, if is an and is then this will only + /// return true if the symbol is . If is + /// false, then it can return true if is or any + /// of the symbols returned by for + /// any of the references of the . + /// + bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary); + bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken); + bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(string name, SymbolFilter filter, CancellationToken cancellationToken); + ICompilationTracker Fork(ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate = null, bool clone = false, CancellationToken cancellationToken = default); + ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken); + Task GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken); + Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken); + Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken); + + /// + /// Get a metadata reference to this compilation info's compilation with respect to + /// another project. For cross language references produce a skeletal assembly. If the + /// compilation is not available, it is built. If a skeletal assembly reference is + /// needed and does not exist, it is also built. + /// + Task GetMetadataReferenceAsync(SolutionState solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken); + CompilationReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference); + ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken); + IEnumerable? GetSyntaxTreesWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken); + Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken); + bool TryGetCompilation([NotNullWhen(true)] out Compilation? compilation); + SourceGeneratedDocumentState? TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(DocumentId documentId); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs index 06c155d986957..13afd33893bb7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -50,7 +52,7 @@ private readonly struct UnrootedSymbolSet /// public readonly ImmutableArray<(int hashCode, WeakReference symbol)> SecondaryReferencedSymbols; - public UnrootedSymbolSet( + private UnrootedSymbolSet( WeakReference primaryAssemblySymbol, WeakReference primaryDynamicSymbol, ImmutableArray<(int hashCode, WeakReference symbol)> secondaryReferencedSymbols) @@ -59,6 +61,86 @@ public UnrootedSymbolSet( PrimaryDynamicSymbol = primaryDynamicSymbol; SecondaryReferencedSymbols = secondaryReferencedSymbols; } + + public static UnrootedSymbolSet Create(Compilation compilation) + { + var primaryAssembly = new WeakReference(compilation.Assembly); + + // The dynamic type is also unrooted (i.e. doesn't point back at the compilation or source + // assembly). So we have to keep track of it so we can get back from it to a project in case the + // underlying compilation is GC'ed. + var primaryDynamic = new WeakReference( + compilation.Language == LanguageNames.CSharp ? compilation.DynamicType : null); + + // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. + using var _ = ArrayBuilder<(int hashcode, WeakReference symbol)>.GetInstance( + compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols); + + foreach (var reference in compilation.References) + { + var symbol = compilation.GetAssemblyOrModuleSymbol(reference); + if (symbol == null) + continue; + + secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), new WeakReference(symbol))); + } + + // Sort all the secondary symbols by their hash. This will allow us to easily binary search for + // them afterwards. Note: it is fine for multiple symbols to have the same reference hash. The + // search algorithm will account for that. + secondarySymbols.Sort(WeakSymbolComparer.Instance); + return new UnrootedSymbolSet(primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); + } + + public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary) + { + if (primary) + { + return symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || + symbol.Equals(this.PrimaryDynamicSymbol.GetTarget()); + } + else + { + var secondarySymbols = this.SecondaryReferencedSymbols; + + var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); + + // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find + // the location we should start looking at. + var index = secondarySymbols.BinarySearch((symbolHash, null!), WeakSymbolComparer.Instance); + if (index < 0) + return false; + + // Could have multiple symbols with the same hash. They will all be placed next to each other, + // so walk backward to hit the first. + while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash) + index--; + + // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match. + while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) + { + var cached = secondarySymbols[index].symbol; + if (cached.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) + return true; + + index++; + } + + return false; + } + } + + private class WeakSymbolComparer : IComparer<(int hashcode, WeakReference symbol)> + { + public static readonly WeakSymbolComparer Instance = new WeakSymbolComparer(); + + private WeakSymbolComparer() + { + } + + public int Compare((int hashcode, WeakReference symbol) x, (int hashcode, WeakReference symbol) y) + => x.hashcode - y.hashcode; + } } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 7992315c9fefc..fcd5eab374c23 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -47,7 +47,7 @@ internal partial class SolutionState public readonly IReadOnlyList AnalyzerReferences; // Values for all these are created on demand. - private ImmutableDictionary _projectIdToTrackerMap; + private ImmutableDictionary _projectIdToTrackerMap; // Checksums for this solution state private readonly ValueSource _lazyChecksums; @@ -63,6 +63,8 @@ internal partial class SolutionState private ConditionalWeakTable? _unrootedSymbolToProjectId; private static readonly Func> s_createTable = () => new ConditionalWeakTable(); + private readonly SourceGeneratedDocumentState? _frozenSourceGeneratedDocumentState; + private SolutionState( BranchId branchId, int workspaceVersion, @@ -72,10 +74,11 @@ private SolutionState( SerializableOptionSet options, IReadOnlyList analyzerReferences, ImmutableDictionary idToProjectStateMap, - ImmutableDictionary projectIdToTrackerMap, + ImmutableDictionary projectIdToTrackerMap, ImmutableDictionary> filePathToDocumentIdsMap, ProjectDependencyGraph dependencyGraph, - Lazy? lazyAnalyzers) + Lazy? lazyAnalyzers, + SourceGeneratedDocumentState? frozenSourceGeneratedDocument) { _branchId = branchId; _workspaceVersion = workspaceVersion; @@ -89,6 +92,7 @@ private SolutionState( _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; _lazyAnalyzers = lazyAnalyzers ?? CreateLazyHostDiagnosticAnalyzers(analyzerReferences); + _frozenSourceGeneratedDocumentState = frozenSourceGeneratedDocument; // when solution state is changed, we recalculate its checksum _lazyChecksums = new AsyncLazy(ComputeChecksumsAsync, cacheResult: true); @@ -115,10 +119,11 @@ public SolutionState( options, analyzerReferences, idToProjectStateMap: ImmutableDictionary.Empty, - projectIdToTrackerMap: ImmutableDictionary.Empty, + projectIdToTrackerMap: ImmutableDictionary.Empty, filePathToDocumentIdsMap: ImmutableDictionary.Create>(StringComparer.OrdinalIgnoreCase), dependencyGraph: ProjectDependencyGraph.Empty, - lazyAnalyzers: null) + lazyAnalyzers: null, + frozenSourceGeneratedDocument: null) { } @@ -137,6 +142,8 @@ public SolutionState WithNewWorkspace(Workspace workspace, int workspaceVersion) public SolutionInfo.SolutionAttributes SolutionAttributes => _solutionAttributes; + public SourceGeneratedDocumentState? FrozenSourceGeneratedDocumentState => _frozenSourceGeneratedDocumentState; + public ImmutableDictionary ProjectStates => _projectIdToProjectStateMap; public int WorkspaceVersion => _workspaceVersion; @@ -183,7 +190,8 @@ public SolutionState WithNewWorkspace(Workspace workspace, int workspaceVersion) /// public IReadOnlyList ProjectIds { get; } - // [Conditional("DEBUG")] + // Only run this in debug builds; even the .Any() call across all projects can be expensive when there's a lot of them. + [Conditional("DEBUG")] private void CheckInvariants() { Contract.ThrowIfFalse(_projectIdToProjectStateMap.Count == ProjectIds.Count); @@ -203,9 +211,10 @@ private SolutionState Branch( SerializableOptionSet? options = null, IReadOnlyList? analyzerReferences = null, ImmutableDictionary? idToProjectStateMap = null, - ImmutableDictionary? projectIdToTrackerMap = null, + ImmutableDictionary? projectIdToTrackerMap = null, ImmutableDictionary>? filePathToDocumentIdsMap = null, - ProjectDependencyGraph? dependencyGraph = null) + ProjectDependencyGraph? dependencyGraph = null, + Optional frozenSourceGeneratedDocument = default) { var branchId = GetBranchId(); @@ -217,6 +226,7 @@ private SolutionState Branch( projectIdToTrackerMap ??= _projectIdToTrackerMap; filePathToDocumentIdsMap ??= _filePathToDocumentIdsMap; dependencyGraph ??= _dependencyGraph; + var newFrozenSourceGeneratedDocumentState = frozenSourceGeneratedDocument.HasValue ? frozenSourceGeneratedDocument.Value : _frozenSourceGeneratedDocumentState; var analyzerReferencesEqual = AnalyzerReferences.SequenceEqual(analyzerReferences); @@ -228,7 +238,8 @@ private SolutionState Branch( idToProjectStateMap == _projectIdToProjectStateMap && projectIdToTrackerMap == _projectIdToTrackerMap && filePathToDocumentIdsMap == _filePathToDocumentIdsMap && - dependencyGraph == _dependencyGraph) + dependencyGraph == _dependencyGraph && + newFrozenSourceGeneratedDocumentState == _frozenSourceGeneratedDocumentState) { return this; } @@ -245,7 +256,8 @@ private SolutionState Branch( projectIdToTrackerMap, filePathToDocumentIdsMap, dependencyGraph, - analyzerReferencesEqual ? _lazyAnalyzers : null); + analyzerReferencesEqual ? _lazyAnalyzers : null, + newFrozenSourceGeneratedDocumentState); } private SolutionState CreatePrimarySolution( @@ -272,7 +284,8 @@ private SolutionState CreatePrimarySolution( _projectIdToTrackerMap, _filePathToDocumentIdsMap, _dependencyGraph, - _lazyAnalyzers); + _lazyAnalyzers, + frozenSourceGeneratedDocument: null); } private BranchId GetBranchId() @@ -419,7 +432,7 @@ public ProjectState GetRequiredProjectState(ProjectId projectId) return id == null ? null : this.GetProjectState(id); } - private bool TryGetCompilationTracker(ProjectId projectId, [NotNullWhen(returnValue: true)] out CompilationTracker? tracker) + private bool TryGetCompilationTracker(ProjectId projectId, [NotNullWhen(returnValue: true)] out ICompilationTracker? tracker) => _projectIdToTrackerMap.TryGetValue(projectId, out tracker); private static readonly Func s_createCompilationTrackerFunction = CreateCompilationTracker; @@ -431,7 +444,7 @@ private static CompilationTracker CreateCompilationTracker(ProjectId projectId, return new CompilationTracker(projectState); } - private CompilationTracker GetCompilationTracker(ProjectId projectId) + private ICompilationTracker GetCompilationTracker(ProjectId projectId) { if (!_projectIdToTrackerMap.TryGetValue(projectId, out var tracker)) { @@ -1504,9 +1517,9 @@ private static ProjectDependencyGraph CreateDependencyGraph( return new ProjectDependencyGraph(projectIds.ToImmutableHashSet(), map); } - private ImmutableDictionary CreateCompilationTrackerMap(ProjectId changedProjectId, ProjectDependencyGraph dependencyGraph) + private ImmutableDictionary CreateCompilationTrackerMap(ProjectId changedProjectId, ProjectDependencyGraph dependencyGraph) { - var builder = ImmutableDictionary.CreateBuilder(); + var builder = ImmutableDictionary.CreateBuilder(); IEnumerable? dependencies = null; foreach (var (id, tracker) in _projectIdToTrackerMap) @@ -1554,9 +1567,9 @@ bool CanReuse(ProjectId id) /// public SolutionState GetIsolatedSolution() { - var forkedMap = ImmutableDictionary.CreateRange( + var forkedMap = ImmutableDictionary.CreateRange( _projectIdToTrackerMap.Where(kvp => kvp.Value.HasCompilation) - .Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Clone()))); + .Select(kvp => KeyValuePairUtil.Create(kvp.Key, kvp.Value.Clone()))); return this.Branch(projectIdToTrackerMap: forkedMap); } @@ -1777,6 +1790,64 @@ public ValueTask> GetSourceGene return GetCompilationTracker(documentId.ProjectId).TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); } + /// + /// Returns a new SolutionState that will always produce a specific output for a generated file. This is used only in the + /// implementation of where if a user has a source + /// generated file open, we need to make sure everything lines up. + /// + public SolutionState WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdentity documentIdentity, SourceText sourceText) + { + // We won't support freezing multiple source generated documents at once. Although nothing in the implementation + // of this method would have problems, this simplifies the handling of serializing this solution to out-of-proc. + // Since we only produce these snapshots from an open document, there should be no way to observe this, so this assertion + // also serves as a good check on the system. If down the road we need to support this, we can remove this check and + // update the out-of-process serialization logic accordingly. + Contract.ThrowIfTrue(_frozenSourceGeneratedDocumentState != null, "We shouldn't be calling WithFrozenSourceGeneratedDocument on a solution with a frozen source generated document."); + + var existingGeneratedState = TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); + SourceGeneratedDocumentState newGeneratedState; + + if (existingGeneratedState != null) + { + newGeneratedState = existingGeneratedState.WithUpdatedGeneratedContent(sourceText, existingGeneratedState.ParseOptions); + + // If the content already matched, we can just reuse the existing state + if (newGeneratedState == existingGeneratedState) + { + return this; + } + } + else + { + var projectState = GetRequiredProjectState(documentIdentity.DocumentId.ProjectId); + newGeneratedState = SourceGeneratedDocumentState.Create( + documentIdentity, + sourceText, + projectState.ParseOptions!, + projectState.LanguageServices, + _solutionServices); + } + + var projectId = documentIdentity.DocumentId.ProjectId; + var newTrackerMap = CreateCompilationTrackerMap(projectId, _dependencyGraph); + + // We want to create a new snapshot with a new compilation tracker that will do this replacement. + // If we already have an existing tracker we'll just wrap that (so we also are reusing any underlying + // computations). If we don't have one, we'll create one and then wrap it. + if (!newTrackerMap.TryGetValue(projectId, out var existingTracker)) + { + existingTracker = CreateCompilationTracker(projectId, this); + } + + newTrackerMap = newTrackerMap.SetItem( + projectId, + new GeneratedFileReplacingCompilationTracker(existingTracker, newGeneratedState)); + + return this.Branch( + projectIdToTrackerMap: newTrackerMap, + frozenSourceGeneratedDocument: newGeneratedState); + } + /// /// Symbols need to be either or . /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index ac3f55c1d101a..46d105e4692fb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,7 +18,7 @@ namespace Microsoft.CodeAnalysis { internal partial class SolutionState { - public bool TryGetStateChecksums(out SolutionStateChecksums stateChecksums) + public bool TryGetStateChecksums([NotNullWhen(true)] out SolutionStateChecksums? stateChecksums) => _lazyChecksums.TryGetValue(out stateChecksums); public Task GetStateChecksumsAsync(CancellationToken cancellationToken) @@ -43,15 +42,24 @@ private async Task ComputeChecksumsAsync(CancellationTok .Where(s => RemoteSupportedLanguages.IsSupported(s.Language)) .Select(s => s.GetChecksumAsync(cancellationToken)); - var serializer = _solutionServices.Workspace.Services.GetService(); - var infoChecksum = serializer.CreateChecksum(SolutionAttributes, cancellationToken); + var serializer = _solutionServices.Workspace.Services.GetRequiredService(); + var attributesChecksum = serializer.CreateChecksum(SolutionAttributes, cancellationToken); var optionsChecksum = serializer.CreateChecksum(Options, cancellationToken); + var frozenSourceGeneratedDocumentIdentityChecksum = Checksum.Null; + var frozenSourceGeneratedDocumentTextChecksum = Checksum.Null; + + if (FrozenSourceGeneratedDocumentState != null) + { + frozenSourceGeneratedDocumentIdentityChecksum = serializer.CreateChecksum(FrozenSourceGeneratedDocumentState.Identity, cancellationToken); + frozenSourceGeneratedDocumentTextChecksum = (await FrozenSourceGeneratedDocumentState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false)).Text; + } + var analyzerReferenceChecksums = ChecksumCache.GetOrCreate(AnalyzerReferences, _ => new AnalyzerReferenceChecksumCollection(AnalyzerReferences.Select(r => serializer.CreateChecksum(r, cancellationToken)).ToArray())); var projectChecksums = await Task.WhenAll(projectChecksumTasks).ConfigureAwait(false); - return new SolutionStateChecksums(infoChecksum, optionsChecksum, new ProjectChecksumCollection(projectChecksums), analyzerReferenceChecksums); + return new SolutionStateChecksums(attributesChecksum, optionsChecksum, new ProjectChecksumCollection(projectChecksums), analyzerReferenceChecksums, frozenSourceGeneratedDocumentIdentityChecksum, frozenSourceGeneratedDocumentTextChecksum); } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs index 8f96171918d9e..a9be4d8f1c6f5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Threading; + namespace Microsoft.CodeAnalysis { /// @@ -17,7 +19,18 @@ internal SourceGeneratedDocument(Project project, SourceGeneratedDocumentState s private new SourceGeneratedDocumentState State => (SourceGeneratedDocumentState)base.State; // TODO: make this public. Tracked by https://github.com/dotnet/roslyn/issues/50546 - internal ISourceGenerator SourceGenerator => State.SourceGenerator; + internal string SourceGeneratorAssemblyName => Identity.GeneratorAssemblyName; + internal string SourceGeneratorTypeName => Identity.GeneratorTypeName; public string HintName => State.HintName; + internal SourceGeneratedDocumentIdentity Identity => State.Identity; + + internal override Document WithFrozenPartialSemantics(CancellationToken cancellationToken) + { + // For us to implement frozen partial semantics here with a source generated document, + // we'd need to potentially deal with the combination where that happens on a snapshot that was already + // forked; rather than trying to deal with that combo we'll just fall back to not doing anything special + // which is allowed. + return this; + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs new file mode 100644 index 0000000000000..85bdbd828f310 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + /// + /// A small struct that holds the values that define the identity of a source generated document, and don't change + /// as new generations happen. This is mostly for convenience as we are reguarly working with this combination of values. + /// + internal readonly struct SourceGeneratedDocumentIdentity : IObjectWritable, IEquatable + { + public DocumentId DocumentId { get; } + public string HintName { get; } + public string GeneratorAssemblyName { get; } + public string GeneratorTypeName { get; } + public string FilePath { get; } + + public bool ShouldReuseInSerialization => true; + + public SourceGeneratedDocumentIdentity(DocumentId documentId, string hintName, string generatorAssemblyName, string generatorTypeName, string filePath) + { + DocumentId = documentId; + HintName = hintName; + GeneratorAssemblyName = generatorAssemblyName; + GeneratorTypeName = generatorTypeName; + FilePath = filePath; + } + + public static string GetGeneratorTypeName(ISourceGenerator generator) + { + return generator.GetType().FullName!; + } + + public static string GetGeneratorAssemblyName(ISourceGenerator generator) + { + return generator.GetType().Assembly.FullName!; + } + + public static SourceGeneratedDocumentIdentity Generate(ProjectId projectId, string hintName, ISourceGenerator generator, string filePath) + { + // We want the DocumentId generated for a generated output to be stable between Compilations; this is so features that track + // a document by DocumentId can find it after some change has happened that requires generators to run again. + // To achieve this we'll just do a crytographic hash of the generator name and hint name; the choice of a cryptographic hash + // as opposed to a more generic string hash is we actually want to ensure we don't have collisions. + var generatorAssemblyName = GetGeneratorAssemblyName(generator); + var generatorTypeName = GetGeneratorTypeName(generator); + + // Combine the strings together; we'll use Encoding.Unicode since that'll match the underlying format; this can be made much + // faster once we're on .NET Core since we could directly treat the strings as ReadOnlySpan. + var projectIdBytes = projectId.Id.ToByteArray(); + using var _ = ArrayBuilder.GetInstance(capacity: (generatorAssemblyName.Length + 1 + generatorTypeName.Length + 1 + hintName.Length) * 2 + projectIdBytes.Length, out var hashInput); + hashInput.AddRange(projectIdBytes); + + // Add a null to separate the generator name and hint name; since this is effectively a joining of UTF-16 bytes + // we'll use a UTF-16 null just to make sure there's absolutely no risk of collision. + hashInput.AddRange(Encoding.Unicode.GetBytes(generatorAssemblyName)); + hashInput.AddRange(0, 0); + hashInput.AddRange(Encoding.Unicode.GetBytes(generatorTypeName)); + hashInput.AddRange(0, 0); + hashInput.AddRange(Encoding.Unicode.GetBytes(hintName)); + + // The particular choice of crypto algorithm here is arbitrary and can be always changed as necessary. The only requirement + // is it must be collision resistant, and provide enough bits to fill a GUID. + using var crytpoAlgorithm = System.Security.Cryptography.SHA256.Create(); + var hash = crytpoAlgorithm.ComputeHash(hashInput.ToArray()); + Array.Resize(ref hash, 16); + var guid = new Guid(hash); + + var documentId = DocumentId.CreateFromSerialized(projectId, guid, hintName); + + return new SourceGeneratedDocumentIdentity(documentId, hintName, generatorAssemblyName, generatorTypeName, filePath); + } + + public void WriteTo(ObjectWriter writer) + { + DocumentId.WriteTo(writer); + + writer.WriteString(HintName); + writer.WriteString(GeneratorAssemblyName); + writer.WriteString(GeneratorTypeName); + writer.WriteString(FilePath); + } + + internal static SourceGeneratedDocumentIdentity ReadFrom(ObjectReader reader) + { + var documentId = DocumentId.ReadFrom(reader); + + var hintName = reader.ReadString(); + var generatorAssemblyName = reader.ReadString(); + var generatorTypeName = reader.ReadString(); + var filePath = reader.ReadString(); + + return new SourceGeneratedDocumentIdentity(documentId, hintName, generatorAssemblyName, generatorTypeName, filePath); + } + + public override bool Equals(object? obj) + { + return obj is SourceGeneratedDocumentIdentity identity && Equals(identity); + } + + public bool Equals(SourceGeneratedDocumentIdentity other) + { + return EqualityComparer.Default.Equals(DocumentId, other.DocumentId) && + HintName == other.HintName && + GeneratorAssemblyName == other.GeneratorAssemblyName && + GeneratorTypeName == other.GeneratorTypeName && + FilePath == other.FilePath; + } + + public override int GetHashCode() + { + return Hash.Combine(DocumentId, + Hash.Combine(HintName, + Hash.Combine(GeneratorAssemblyName, + Hash.Combine(GeneratorTypeName, + Hash.Combine(FilePath, 0))))); + } + + public static bool operator ==(SourceGeneratedDocumentIdentity left, SourceGeneratedDocumentIdentity right) + { + return left.Equals(right); + } + + public static bool operator !=(SourceGeneratedDocumentIdentity left, SourceGeneratedDocumentIdentity right) + { + return !(left == right); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs index a25f88586c67c..df953f274e056 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Threading; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -12,126 +11,83 @@ namespace Microsoft.CodeAnalysis { internal sealed class SourceGeneratedDocumentState : DocumentState { - public string HintName { get; } - public ISourceGenerator SourceGenerator { get; } + public SourceGeneratedDocumentIdentity Identity { get; } + public string HintName => Identity.HintName; + public string SourceGeneratorAssemblyName => Identity.GeneratorAssemblyName; + public string SourceGeneratorTypeName => Identity.GeneratorTypeName; public static SourceGeneratedDocumentState Create( - string hintName, + SourceGeneratedDocumentIdentity documentIdentity, SourceText generatedSourceText, - SyntaxTree generatedSyntaxTree, - DocumentId documentId, - ISourceGenerator sourceGenerator, + ParseOptions parseOptions, HostLanguageServices languageServices, - SolutionServices solutionServices, - CancellationToken cancellationToken) + SolutionServices solutionServices) { - var options = generatedSyntaxTree.Options; - var filePath = generatedSyntaxTree.FilePath; - var textAndVersion = TextAndVersion.Create(generatedSourceText, VersionStamp.Create()); - ValueSource textSource = new ConstantValueSource(textAndVersion); - - var root = generatedSyntaxTree.GetRoot(cancellationToken); - Contract.ThrowIfNull(languageServices.SyntaxTreeFactory, "We should not have a generated syntax tree for a language that doesn't support trees."); - - if (languageServices.SyntaxTreeFactory.CanCreateRecoverableTree(root)) - { - // We will only create recoverable text if we can create a recoverable tree; if we created a - // recoverable text but not a new tree, it would mean tree.GetText() could still potentially return - // the non-recoverable text, but asking the document directly for it's text would give a recoverable - // text with a different object identity. - textSource = CreateRecoverableText(textAndVersion, solutionServices); - - generatedSyntaxTree = languageServices.SyntaxTreeFactory.CreateRecoverableTree( - documentId.ProjectId, - filePath: generatedSyntaxTree.FilePath, - options, - textSource, - generatedSourceText.Encoding, - root); - } - - var treeAndVersion = TreeAndVersion.Create(generatedSyntaxTree, textAndVersion.Version); + var textSource = new ConstantValueSource(textAndVersion); + var treeSource = CreateLazyFullyParsedTree( + textSource, + documentIdentity.DocumentId.ProjectId, + documentIdentity.FilePath, + parseOptions, + languageServices); return new SourceGeneratedDocumentState( + documentIdentity, languageServices, solutionServices, documentServiceProvider: null, new DocumentInfo.DocumentAttributes( - documentId, - name: hintName, + documentIdentity.DocumentId, + name: documentIdentity.HintName, folders: SpecializedCollections.EmptyReadOnlyList(), - options.Kind, - filePath: filePath, + parseOptions.Kind, + filePath: documentIdentity.FilePath, isGenerated: true, designTimeOnly: false), - options, - sourceText: null, // don't strongly hold the text + parseOptions, textSource, - treeAndVersion, - sourceGenerator, - hintName); + treeSource); } private SourceGeneratedDocumentState( + SourceGeneratedDocumentIdentity documentIdentity, HostLanguageServices languageServices, SolutionServices solutionServices, IDocumentServiceProvider? documentServiceProvider, DocumentInfo.DocumentAttributes attributes, - ParseOptions? options, - SourceText? sourceText, + ParseOptions options, ValueSource textSource, - TreeAndVersion treeAndVersion, - ISourceGenerator sourceGenerator, - string hintName) - : base(languageServices, solutionServices, documentServiceProvider, attributes, options, sourceText, textSource, new ConstantValueSource(treeAndVersion)) + ValueSource treeSource) + : base(languageServices, solutionServices, documentServiceProvider, attributes, options, sourceText: null, textSource, treeSource) { - SourceGenerator = sourceGenerator; - HintName = hintName; + Identity = documentIdentity; } - /// - /// Equivalent to calling , but avoids the implicit requirement of a cancellation token since - /// we can always get the tree right away. - /// - /// - /// We won't expose this through in case the implementation changes. - /// - public SyntaxTree SyntaxTree - { - get - { - // We are always holding onto the SyntaxTree object with a ConstantValueSource, so we can just fetch this - // without any extra work. Unlike normal documents where we don't even have a tree object until we've fetched text and - // the tree the first time, the generated case we start with a tree and text and then wrap it. - return GetSyntaxTree(CancellationToken.None); - } - } + // The base allows for parse options to be null for non-C#/VB languages, but we'll always have parse options + public new ParseOptions ParseOptions => base.ParseOptions!; protected override TextDocumentState UpdateText(ValueSource newTextSource, PreservationMode mode, bool incremental) { throw new NotSupportedException(WorkspacesResources.The_contents_of_a_SourceGeneratedDocument_may_not_be_changed); } - public SourceGeneratedDocumentState WithUpdatedGeneratedContent(SourceText sourceText, SyntaxTree lazySyntaxTree, ParseOptions parseOptions, CancellationToken cancellationToken) + public SourceGeneratedDocumentState WithUpdatedGeneratedContent(SourceText sourceText, ParseOptions parseOptions) { if (TryGetText(out var existingText) && Checksum.From(existingText.GetChecksum()) == Checksum.From(sourceText.GetChecksum()) && - SyntaxTree.Options.Equals(parseOptions)) + ParseOptions.Equals(parseOptions)) { // We can reuse this instance directly return this; } - return SourceGeneratedDocumentState.Create( - this.HintName, + return Create( + Identity, sourceText, - lazySyntaxTree, - this.Id, - this.SourceGenerator, + ParseOptions, this.LanguageServices, - this.solutionServices, - cancellationToken); + this.solutionServices); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index cff827be27a0d..cac5a9caf03f9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -16,8 +15,8 @@ namespace Microsoft.CodeAnalysis.Serialization { internal sealed class SolutionStateChecksums : ChecksumWithChildren { - public SolutionStateChecksums(Checksum infoChecksum, Checksum optionsChecksum, ProjectChecksumCollection projectChecksums, AnalyzerReferenceChecksumCollection analyzerReferenceChecksums) - : this(new object[] { infoChecksum, optionsChecksum, projectChecksums, analyzerReferenceChecksums }) + public SolutionStateChecksums(Checksum attributesChecksum, Checksum optionsChecksum, ProjectChecksumCollection projectChecksums, AnalyzerReferenceChecksumCollection analyzerReferenceChecksums, Checksum frozenSourceGeneratedDocumentIdentity, Checksum frozenSourceGeneratedDocumentText) + : this(new object[] { attributesChecksum, optionsChecksum, projectChecksums, analyzerReferenceChecksums, frozenSourceGeneratedDocumentIdentity, frozenSourceGeneratedDocumentText }) { } @@ -29,6 +28,8 @@ public SolutionStateChecksums(object[] children) : base(WellKnownSynchronization public Checksum Options => (Checksum)Children[1]; public ProjectChecksumCollection Projects => (ProjectChecksumCollection)Children[2]; public AnalyzerReferenceChecksumCollection AnalyzerReferences => (AnalyzerReferenceChecksumCollection)Children[3]; + public Checksum FrozenSourceGeneratedDocumentIdentity => (Checksum)Children[4]; + public Checksum FrozenSourceGeneratedDocumentText => (Checksum)Children[5]; public async Task FindAsync( SolutionState state, @@ -57,6 +58,18 @@ public async Task FindAsync( result[Options] = state.Options; } + if (searchingChecksumsLeft.Remove(FrozenSourceGeneratedDocumentIdentity)) + { + Contract.ThrowIfNull(state.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentIdentity checksum if we didn't have a text in the first place."); + result[FrozenSourceGeneratedDocumentIdentity] = state.FrozenSourceGeneratedDocumentState.Identity; + } + + if (searchingChecksumsLeft.Remove(FrozenSourceGeneratedDocumentText)) + { + Contract.ThrowIfNull(state.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentState checksum if we didn't have a text in the first place."); + result[FrozenSourceGeneratedDocumentText] = await SerializableSourceText.FromTextDocumentStateAsync(state.FrozenSourceGeneratedDocumentState, cancellationToken).ConfigureAwait(false); + } + if (searchingChecksumsLeft.Remove(Projects.Checksum)) { result[Projects.Checksum] = Projects; @@ -153,11 +166,13 @@ public async Task FindAsync( if (searchingChecksumsLeft.Remove(CompilationOptions)) { + Contract.ThrowIfNull(state.CompilationOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); result[CompilationOptions] = state.CompilationOptions; } if (searchingChecksumsLeft.Remove(ParseOptions)) { + Contract.ThrowIfNull(state.ParseOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); result[ParseOptions] = state.ParseOptions; } @@ -252,7 +267,7 @@ internal static class ChecksumCache public static IReadOnlyList GetOrCreate(IReadOnlyList unorderedList, ConditionalWeakTable.CreateValueCallback orderedListGetter) => (IReadOnlyList)s_cache.GetValue(unorderedList, orderedListGetter); - public static bool TryGetValue(object value, out Checksum checksum) + public static bool TryGetValue(object value, [NotNullWhen(true)] out Checksum? checksum) { // same key should always return same checksum if (!s_cache.TryGetValue(value, out var result)) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 904f6825d3528..371f5f47389a3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -12,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -24,7 +22,8 @@ namespace Microsoft.CodeAnalysis internal readonly struct TextDocumentStates where TState : TextDocumentState { - public static readonly TextDocumentStates Empty = new(ImmutableList.Empty, ImmutableSortedDictionary.Empty); + public static readonly TextDocumentStates Empty = + new(ImmutableList.Empty, ImmutableSortedDictionary.Create(DocumentIdComparer.Instance)); private readonly ImmutableList _ids; @@ -37,6 +36,8 @@ internal readonly struct TextDocumentStates private TextDocumentStates(ImmutableList ids, ImmutableSortedDictionary map) { + Debug.Assert(map.KeyComparer == DocumentIdComparer.Instance); + _ids = ids; _map = map; } diff --git a/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs b/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs index 5b017c4bdf750..a600cfbe63060 100644 --- a/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs +++ b/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs @@ -27,6 +27,14 @@ public static ImmutableArray GetRelatedDocumentsWithChanges(this Sourc var solution = workspace.CurrentSolution; + if (workspace.TryGetOpenSourceGeneratedDocumentIdentity(documentId, out var documentIdentity)) + { + // For source generated documents, we won't count them as linked across multiple projects; this is because + // the generated documents in each target may have different source so other features might be surprised if we + // return the same documents but with different text. So in this case, we'll just return a single document. + return ImmutableArray.Create(solution.WithFrozenSourceGeneratedDocument(documentIdentity, text)); + } + var relatedIds = solution.GetRelatedDocumentIds(documentId); solution = solution.WithDocumentText(relatedIds, text, PreservationMode.PreserveIdentity); return relatedIds.SelectAsArray((id, solution) => solution.GetRequiredDocument(id), solution); @@ -45,11 +53,16 @@ public static ImmutableArray GetRelatedDocumentsWithChanges(this Sourc { var solution = workspace.CurrentSolution; var id = workspace.GetDocumentIdInCurrentContext(text.Container); - if (id == null || !solution.ContainsDocument(id)) + if (id == null) { return null; } + if (workspace.TryGetOpenSourceGeneratedDocumentIdentity(id, out var documentIdentity)) + { + return solution.WithFrozenSourceGeneratedDocument(documentIdentity, text); + } + // We update all linked files to ensure they are all in sync. Otherwise code might try to jump from // one linked file to another and be surprised if the text is entirely different. var allIds = solution.GetRelatedDocumentIds(id); diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs index b148d02f50d31..4811497b7ebe7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs @@ -37,6 +37,8 @@ public abstract partial class Workspace private readonly Dictionary> _bufferToAssociatedDocumentsMap = new(); private readonly Dictionary _textTrackers = new(); + private readonly Dictionary _documentToAssociatedBufferMap = new(); + private readonly Dictionary _openSourceGeneratedDocumentIdentities = new(); /// /// True if this workspace supports manually opening and closing documents. @@ -88,16 +90,21 @@ protected void ClearOpenDocument(DocumentId documentId) _projectToOpenDocumentsMap.MultiRemove(documentId.ProjectId, documentId); // Stop tracking the buffer or update the documentId associated with the buffer. - if (_textTrackers.TryGetValue(documentId, out var tracker)) + if (_documentToAssociatedBufferMap.TryGetValue(documentId, out var textContainer)) { - tracker.Disconnect(); - _textTrackers.Remove(documentId); + _documentToAssociatedBufferMap.Remove(documentId); - var currentContextDocumentId = UpdateCurrentContextMapping_NoLock(tracker.TextContainer, documentId); + if (_textTrackers.TryGetValue(documentId, out var tracker)) + { + tracker.Disconnect(); + _textTrackers.Remove(documentId); + } + + var currentContextDocumentId = RemoveDocumentFromCurrentContextMapping_NoLock(textContainer, documentId); if (currentContextDocumentId == null) { // No documentIds are attached to this buffer, so stop tracking it. - this.UnregisterText(tracker.TextContainer); + this.UnregisterText(textContainer); } } } @@ -279,6 +286,14 @@ internal DocumentId GetDocumentIdInCurrentContext(DocumentId documentId) return _bufferToAssociatedDocumentsMap.Where(kvp => kvp.Value.Contains(documentId)).Select(kvp => kvp.Key).FirstOrDefault(); } + internal bool TryGetOpenSourceGeneratedDocumentIdentity(DocumentId id, out SourceGeneratedDocumentIdentity documentIdentity) + { + using (_serializationLock.DisposableWait()) + { + return _openSourceGeneratedDocumentIdentities.TryGetValue(id, out documentIdentity); + } + } + /// /// Call this method to tell the host environment to change the current active context to this document. Only supported if /// returns true. @@ -393,6 +408,42 @@ protected internal void OnDocumentOpened( this.RegisterText(textContainer); } + /// + /// Registers a SourceTextContainer to a source generated document. Unlike , + /// this doesn't result in the workspace being updated any time the contents of the container is changed; instead + /// this ensures that features going from the text container to the buffer back to a document get a usable document. + /// + // TODO: switch this protected once we have confidence in API shape + internal void OnSourceGeneratedDocumentOpened( + SourceGeneratedDocumentIdentity documentIdentity, + SourceTextContainer textContainer) + { + using (_serializationLock.DisposableWait()) + { + var documentId = documentIdentity.DocumentId; + CheckDocumentIsClosed(documentId); + AddToOpenDocumentMap(documentId); + + _documentToAssociatedBufferMap.Add(documentId, textContainer); + _openSourceGeneratedDocumentIdentities.Add(documentId, documentIdentity); + + UpdateCurrentContextMapping_NoLock(textContainer, documentId, isCurrentContext: true); + } + + this.RegisterText(textContainer); + } + + internal void OnSourceGeneratedDocumentClosed(DocumentId documentId) + { + using (_serializationLock.DisposableWait()) + { + CheckDocumentIsOpen(documentId); + + Contract.ThrowIfFalse(_openSourceGeneratedDocumentIdentities.Remove(documentId)); + ClearOpenDocument(documentId); + } + } + private class ReuseVersionLoader : TextLoader { // Capture DocumentState instead of Document so that we don't hold onto the old solution. @@ -436,6 +487,7 @@ private void SignupForTextChanges(DocumentId documentId, SourceTextContainer tex { var tracker = new TextTracker(this, documentId, textContainer, onChangedHandler); _textTrackers.Add(documentId, tracker); + _documentToAssociatedBufferMap.Add(documentId, textContainer); this.UpdateCurrentContextMapping_NoLock(textContainer, documentId, isCurrentContext); tracker.Connect(); } @@ -640,7 +692,7 @@ private void UpdateCurrentContextMapping_NoLock(SourceTextContainer textContaine } /// The DocumentId of the current context document attached to the textContainer, if any. - private DocumentId? UpdateCurrentContextMapping_NoLock(SourceTextContainer textContainer, DocumentId closedDocumentId) + private DocumentId? RemoveDocumentFromCurrentContextMapping_NoLock(SourceTextContainer textContainer, DocumentId closedDocumentId) { // Check if we are tracking this textContainer. if (!_bufferToAssociatedDocumentsMap.TryGetValue(textContainer, out var docIds)) diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 46840d9828795..75e553abdcd37 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Projekt nemůže odkazovat sám na sebe. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Předdefinovaný převod z {0} na {1} diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index ae53b07fff3d4..ef869e3da868b 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Ein Projekt darf nicht auf sich selbst verweisen. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Vordefinierte Konvertierung von "{0}" in "{1}". diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index bd08dd57c9822..08eedb183c468 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Un proyecto no puede hacer referencia a sí mismo. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Conversión predefinida de {0} a {1} diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 5cff98addf71d..661701ae48464 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Un projet ne peut pas se référencer lui-même. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Conversion prédéfinie de {0} en {1}. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index 7652ba9665636..5fd3170fc9cc4 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Un progetto non può fare riferimento a se stesso. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Conversione predefinita da {0} a {1}. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index 06f41b694c916..35aa83c5b69ab 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + プロジェクトで自己参照はできません。 @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + {0} から {1} への定義済みの変換 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index cd3e830449a9a..f08efd24ea346 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + 프로젝트는 자신을 참조할 수 없습니다. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + {0}에서 {1}(으)로의 미리 정의된 변환입니다. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index 483af35ac98b0..e1ac7484b8f45 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Projekt może nie odwoływać się do siebie. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Wstępnie zdefiniowana konwersja z {0} na {1}. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index b7268419f091e..910e9a407e34e 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Um projeto não pode fazer referência a si mesmo. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Conversão predefinida de {0} em {1}. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index f879d50f33094..d57d28c49fe4e 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Проект не может ссылаться на себя. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + Предварительно определенное преобразование из {0} в {1}. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index 22549d4d09807..07e618c7e894a 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + Bir proje kendisine başvuru içeremez. @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + {0} öğesinden {1} öğesine dönüştürme önceden tanımlandı. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index 46b16d8017f24..bedfdb9b78096 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + 项目不能引用项目本身。 @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + 从 {0} 到 {1} 的预定义转换。 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index 2e3d38345c237..5c24815ee5bb0 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -1,10 +1,10 @@ - + A project may not reference itself. - A project may not reference itself. + 專案不可參考自己本身。 @@ -89,7 +89,7 @@ Predefined conversion from {0} to {1}. - Predefined conversion from {0} to {1}. + 從 {0} 到 {1} 預先定義的轉換。 diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs index 5d4971387b802..c5b92c0c957f1 100644 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs +++ b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestPersistenceService.cs @@ -21,9 +21,9 @@ public TestPersistenceService() } public IPersistentStorage GetStorage(Solution solution) - => NoOpPersistentStorage.Instance; + => NoOpPersistentStorage.GetOrThrow(solution.Options); public ValueTask GetStorageAsync(Solution solution, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.Instance); + => new(NoOpPersistentStorage.GetOrThrow(solution.Options)); } } diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 290708dbc1d0e..5978c4fc4b511 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -3219,5 +3219,40 @@ public async Task TestUpdatedDocumentTextIsObservablyConstantAsync(bool recovera var treeText = newDocTree.GetText(); Assert.Same(newDocText, treeText); } + + [Fact] + public async Task ReplacingTextMultipleTimesDoesNotRootIntermediateCopiesIfCompilationNotAskedFor() + { + // This test replicates the pattern of some operation changing a bunch of files, but the files aren't kept open. + // In Visual Studio we do large refactorings by opening files with an invisible editor, making changes, and closing + // again. This process means we'll queue up intermediate changes to those files, but we don't want to hold onto + // the intermediate edits when we don't really need to since the final version will be all that matters. + + using var workspace = CreateWorkspaceWithProjectAndDocuments(); + + var solution = workspace.CurrentSolution; + var documentId = solution.Projects.Single().DocumentIds.Single(); + + // Fetch the compilation, so further edits are going to be incremental updates of this one + var originalCompilation = await solution.Projects.Single().GetCompilationAsync(); + + // Create a source text we'll release and ensure it disappears. We'll also make sure we don't accidentally root + // that solution in the middle. + var sourceTextToRelease = ObjectReference.CreateFromFactory(static () => SourceText.From(Guid.NewGuid().ToString())); + var solutionWithSourceTextToRelease = sourceTextToRelease.GetObjectReference( + static (sourceText, document) => document.Project.Solution.WithDocumentText(document.Id, sourceText, PreservationMode.PreserveIdentity), + solution.GetDocument(documentId)); + + // Change it again, this time by editing the text loader; this replicates us closing a file, and we don't want to pin the changes from the + // prior change. + var finalSolution = solutionWithSourceTextToRelease.GetObjectReference( + static (s, documentId) => s.WithDocumentTextLoader(documentId, new TestTextLoader(Guid.NewGuid().ToString()), PreservationMode.PreserveValue), documentId).GetReference(); + + // The text in the middle shouldn't be held at all, since we replaced it. + solutionWithSourceTextToRelease.ReleaseStrongReference(); + sourceTextToRelease.AssertReleased(); + + GC.KeepAlive(finalSolution); + } } } diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index e67569ceb19a0..5e515eaf59705 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -310,5 +311,199 @@ public async Task TreeNotReusedIfParseOptionsChangeChangeBetweenRuns() Assert.NotSame(generatedTreeBeforeChange, generatedTreeAfterChange); } + + [Theory, CombinatorialData] + public async Task ChangeToDocumentThatDoesNotImpactGeneratedDocumentReusesDeclarationTree(bool generatorProducesTree) + { + using var workspace = CreateWorkspace(); + + // We'll use either a generator that produces a single tree, or no tree, to ensure we efficiently handle both cases + ISourceGenerator generator = generatorProducesTree ? new SingleFileTestGenerator("// StaticContent") + : new CallbackGenerator(onInit: _ => { }, onExecute: _ => { }); + + var analyzerReference = new TestGeneratorReference(generator); + var project = AddEmptyProject(workspace.CurrentSolution) + .AddAnalyzerReference(analyzerReference) + .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project; + + // Ensure we already have a compilation created + _ = await project.GetCompilationAsync(); + + project = await MakeChangesToDocument(project); + + var compilationAfterFirstChange = await project.GetRequiredCompilationAsync(CancellationToken.None); + + project = await MakeChangesToDocument(project); + + var compilationAfterSecondChange = await project.GetRequiredCompilationAsync(CancellationToken.None); + + // When we produced compilationAfterSecondChange, what we would ideally like is that compilation was produced by taking + // compilationAfterFirstChange and simply updating the syntax tree that changed, since the generated documents didn't change. + // That allows the compiler to reuse the same declaration tree for the generated file. This is hard to observe directly, but if we reflect + // into the Compilation we can see if the declaration tree is untouched. We won't look at the original compilation, since + // that original one was produced by adding the generated file as the final step, so it's cache won't be reusable, since the + // compiler separates the "most recently changed tree" in the declaration table for efficiency. + + var cachedStateAfterFirstChange = GetDeclarationManagerCachedStateForUnchangingTrees(compilationAfterFirstChange); + var cachedStateAfterSecondChange = GetDeclarationManagerCachedStateForUnchangingTrees(compilationAfterSecondChange); + + Assert.Same(cachedStateAfterFirstChange, cachedStateAfterSecondChange); + + static object GetDeclarationManagerCachedStateForUnchangingTrees(Compilation compilation) + { + var syntaxAndDeclarationsManager = compilation.GetFieldValue("_syntaxAndDeclarations"); + var state = syntaxAndDeclarationsManager.GetType().GetMethod("GetLazyState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!.Invoke(syntaxAndDeclarationsManager, null); + var declarationTable = state.GetFieldValue("DeclarationTable"); + return declarationTable.GetFieldValue("_cache"); + } + + static async Task MakeChangesToDocument(Project project) + { + var existingText = await project.Documents.Single().GetTextAsync(); + var newText = existingText.WithChanges(new TextChange(new TextSpan(existingText.Length, length: 0), " With Change")); + project = project.Documents.Single().WithText(newText).Project; + return project; + } + } + + [Fact] + public async Task CompilationNotCreatedByFetchingGeneratedFilesIfNoGeneratorsPresent() + { + using var workspace = CreateWorkspace(); + var project = AddEmptyProject(workspace.CurrentSolution); + + Assert.Empty(await project.GetSourceGeneratedDocumentsAsync()); + + // We shouldn't have any compilation since we didn't have to run anything + Assert.False(project.TryGetCompilation(out _)); + } + + [Fact] + public async Task OpenSourceGeneratedUpdatedToBufferContentsWhenCallingGetOpenDocumentInCurrentContextWithChanges() + { + using var workspace = CreateWorkspace(); + var analyzerReference = new TestGeneratorReference(new SingleFileTestGenerator("// StaticContent")); + var project = AddEmptyProject(workspace.CurrentSolution) + .AddAnalyzerReference(analyzerReference); + + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + + var generatedDocumentIdentity = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()).Identity; + var differentOpenTextContainer = SourceText.From("// Open Text").Container; + + workspace.OnSourceGeneratedDocumentOpened(generatedDocumentIdentity, differentOpenTextContainer); + + var generatedDocument = differentOpenTextContainer.CurrentText.GetOpenDocumentInCurrentContextWithChanges(); + Assert.IsType(generatedDocument); + Assert.Same(differentOpenTextContainer.CurrentText, await generatedDocument!.GetTextAsync()); + Assert.NotSame(workspace.CurrentSolution, generatedDocument.Project.Solution); + + var generatedTree = await generatedDocument.GetSyntaxTreeAsync(); + var compilation = await generatedDocument.Project.GetRequiredCompilationAsync(CancellationToken.None); + Assert.Contains(generatedTree, compilation.SyntaxTrees); + } + + [Fact] + public async Task OpenSourceGeneratedFileDoesNotCreateNewSnapshotIfContentsKnownToMatch() + { + using var workspace = CreateWorkspace(); + var analyzerReference = new TestGeneratorReference(new SingleFileTestGenerator("// StaticContent")); + var project = AddEmptyProject(workspace.CurrentSolution) + .AddAnalyzerReference(analyzerReference); + + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + + var generatedDocumentIdentity = Assert.Single(await workspace.CurrentSolution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Identity; + var differentOpenTextContainer = SourceText.From("// StaticContent", Encoding.UTF8).Container; + + workspace.OnSourceGeneratedDocumentOpened(generatedDocumentIdentity, differentOpenTextContainer); + + var generatedDocument = differentOpenTextContainer.CurrentText.GetOpenDocumentInCurrentContextWithChanges(); + Assert.Same(workspace.CurrentSolution, generatedDocument!.Project.Solution); + } + + [Fact] + public async Task OpenSourceGeneratedFileMatchesBufferContentsEvenIfGeneratedFileIsMissingIsRemoved() + { + using var workspace = CreateWorkspace(); + var analyzerReference = new TestGeneratorReference(new GenerateFileForEachAdditionalFileWithContentsCommented()); + var originalAdditionalFile = AddEmptyProject(workspace.CurrentSolution) + .AddAnalyzerReference(analyzerReference) + .AddAdditionalDocument("Test.txt", SourceText.From("")); + + Assert.True(workspace.SetCurrentSolution(_ => originalAdditionalFile.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + + var generatedDocumentIdentity = Assert.Single(await originalAdditionalFile.Project.GetSourceGeneratedDocumentsAsync()).Identity; + var differentOpenTextContainer = SourceText.From("// Open Text").Container; + + workspace.OnSourceGeneratedDocumentOpened(generatedDocumentIdentity, differentOpenTextContainer); + workspace.OnAdditionalDocumentRemoved(originalAdditionalFile.Id); + + // At this point there should be no generated documents, even though our file is still open + Assert.Empty(await workspace.CurrentSolution.Projects.Single().GetSourceGeneratedDocumentsAsync()); + + var generatedDocument = differentOpenTextContainer.CurrentText.GetOpenDocumentInCurrentContextWithChanges(); + Assert.IsType(generatedDocument); + Assert.Same(differentOpenTextContainer.CurrentText, await generatedDocument!.GetTextAsync()); + + var generatedTree = await generatedDocument.GetSyntaxTreeAsync(); + var compilation = await generatedDocument.Project.GetRequiredCompilationAsync(CancellationToken.None); + Assert.Contains(generatedTree, compilation.SyntaxTrees); + } + + [Fact] + public async Task OpenSourceGeneratedDocumentUpdatedAndVisibleInProjectReference() + { + using var workspace = CreateWorkspace(); + var analyzerReference = new TestGeneratorReference(new SingleFileTestGenerator("// StaticContent")); + var solution = AddEmptyProject(workspace.CurrentSolution) + .AddAnalyzerReference(analyzerReference).Solution; + var projectIdWithGenerator = solution.ProjectIds.Single(); + + solution = AddEmptyProject(solution).AddProjectReference( + new ProjectReference(projectIdWithGenerator)).Solution; + + Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionChanged)); + + var generatedDocumentIdentity = Assert.Single(await workspace.CurrentSolution.GetRequiredProject(projectIdWithGenerator).GetSourceGeneratedDocumentsAsync()).Identity; + var differentOpenTextContainer = SourceText.From("// Open Text").Container; + + workspace.OnSourceGeneratedDocumentOpened(generatedDocumentIdentity, differentOpenTextContainer); + + var generatedDocument = differentOpenTextContainer.CurrentText.GetOpenDocumentInCurrentContextWithChanges(); + AssertEx.NotNull(generatedDocument); + var generatedTree = await generatedDocument.GetSyntaxTreeAsync(); + + // Fetch the compilation from the other project, it should have a compilation reference that + // contains the generated tree + var projectWithReference = generatedDocument.Project.Solution.Projects.Single(p => p.Id != projectIdWithGenerator); + var compilationWithReference = await projectWithReference.GetRequiredCompilationAsync(CancellationToken.None); + var compilationReference = Assert.Single(compilationWithReference.References.OfType()); + + Assert.Contains(generatedTree, compilationReference.Compilation.SyntaxTrees); + } + + [Fact] + public async Task OpenSourceGeneratedDocumentsUpdateIsDocumentOpenAndCloseWorks() + { + using var workspace = CreateWorkspace(); + var analyzerReference = new TestGeneratorReference(new SingleFileTestGenerator("// StaticContent")); + var project = AddEmptyProject(workspace.CurrentSolution) + .AddAnalyzerReference(analyzerReference); + + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + + var generatedDocumentIdentity = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()).Identity; + var differentOpenTextContainer = SourceText.From("// Open Text").Container; + + workspace.OnSourceGeneratedDocumentOpened(generatedDocumentIdentity, differentOpenTextContainer); + + Assert.True(workspace.IsDocumentOpen(generatedDocumentIdentity.DocumentId)); + + workspace.OnSourceGeneratedDocumentClosed(generatedDocumentIdentity.DocumentId); + + Assert.False(workspace.IsDocumentOpen(generatedDocumentIdentity.DocumentId)); + Assert.Null(differentOpenTextContainer.CurrentText.GetOpenDocumentInCurrentContextWithChanges()); + } } } diff --git a/src/Workspaces/CoreTestUtilities/Fakes/StubStreamingFindUsagesPresenter.cs b/src/Workspaces/CoreTestUtilities/Fakes/StubStreamingFindUsagesPresenter.cs index bb43955a7ce8e..31e09683ef8f9 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/StubStreamingFindUsagesPresenter.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/StubStreamingFindUsagesPresenter.cs @@ -27,10 +27,10 @@ public virtual void ClearAll() { } - public virtual FindUsagesContext StartSearch(string title, bool supportsReferences, CancellationToken cancellationToken) - => new SimpleFindUsagesContext(cancellationToken); + public virtual (FindUsagesContext, CancellationToken) StartSearch(string title, bool supportsReferences) + => (new SimpleFindUsagesContext(), CancellationToken.None); - public virtual FindUsagesContext StartSearchWithCustomColumns(string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn, CancellationToken cancellationToken) - => new SimpleFindUsagesContext(cancellationToken); + public virtual (FindUsagesContext, CancellationToken) StartSearchWithCustomColumns(string title, bool supportsReferences, bool includeContainingTypeAndMemberColumns, bool includeKindColumn) + => (new SimpleFindUsagesContext(), CancellationToken.None); } } diff --git a/src/Workspaces/CoreTestUtilities/MEF/UseExportProviderAttribute.cs b/src/Workspaces/CoreTestUtilities/MEF/UseExportProviderAttribute.cs index efc21f82745a2..456a7bbddfcd0 100644 --- a/src/Workspaces/CoreTestUtilities/MEF/UseExportProviderAttribute.cs +++ b/src/Workspaces/CoreTestUtilities/MEF/UseExportProviderAttribute.cs @@ -11,15 +11,11 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; -using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Editor.Implementation.ForegroundNotification; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Composition; -using Microsoft.VisualStudio.LanguageServices; using Roslyn.Test.Utilities; using Xunit.Sdk; @@ -122,17 +118,6 @@ private static void DisposeExportProvider(ExportProvider? exportProvider) if (exportProvider.GetExportedValues().SingleOrDefault() is { } listenerProvider) { - if (exportProvider.GetExportedValues().SingleOrDefault()?.HasMainThread ?? false) - { - // Immediately clear items from the foreground notification service for which cancellation is - // requested. This service maintains a queue separately from Tasks, and work items scheduled for - // execution after a delay are not immediately purged when cancellation is requested. This code - // instructs the service to walk the list of queued work items and immediately cancel and purge any - // which are already cancelled. - var foregroundNotificationService = exportProvider.GetExportedValues().SingleOrDefault() as ForegroundNotificationService; - foregroundNotificationService?.ReleaseCancelledItems(); - } - // Verify the synchronization context was not used incorrectly var testExportJoinableTaskContext = exportProvider.GetExportedValues().SingleOrDefault(); var denyExecutionSynchronizationContext = testExportJoinableTaskContext?.SynchronizationContext as TestExportJoinableTaskContext.DenyExecutionSynchronizationContext; diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ReflectionExtensions/ObjectExtensions.cs b/src/Workspaces/CoreTestUtilities/ObjectExtensions.cs similarity index 93% rename from src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ReflectionExtensions/ObjectExtensions.cs rename to src/Workspaces/CoreTestUtilities/ObjectExtensions.cs index 04c8b98aab2c3..b3c8d65a8b58c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/ReflectionExtensions/ObjectExtensions.cs +++ b/src/Workspaces/CoreTestUtilities/ObjectExtensions.cs @@ -7,9 +7,9 @@ using System; using System.Reflection; -namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess.ReflectionExtensions +namespace Microsoft.CodeAnalysis.UnitTests { - internal static class ObjectExtensions + public static class ObjectExtensions { public static PropertyType GetPropertyValue(this object instance, string propertyName) { diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 97d561ab1e5fb..bfba9ace063f2 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -270,6 +270,7 @@ public InProcRemoteServices(HostWorkspaceServices workspaceServices, TraceListen RegisterRemoteBrokeredService(new RemoteGlobalNotificationDeliveryService.Factory()); RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); RegisterRemoteBrokeredService(new RemoteEditAndContinueService.Factory()); + RegisterRemoteBrokeredService(new RemoteInheritanceMarginService.Factory()); } public void Dispose() diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs index a1a47535ce330..900ddbdade5ed 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -11,7 +12,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.UnitTests.Remote; +using Roslyn.Test.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote.Testing @@ -34,11 +38,19 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private sealed class WorkspaceManager : RemoteWorkspaceManager { - public WorkspaceManager(SolutionAssetCache assetStorage, Type[]? additionalRemoteParts) + public WorkspaceManager(SolutionAssetCache assetStorage, ConcurrentDictionary sharedTestGeneratorReferences, Type[]? additionalRemoteParts) : base(assetStorage) { LazyWorkspace = new Lazy( - () => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.AddParts(additionalRemoteParts).GetHostServices(), WorkspaceKind.RemoteWorkspace)); + () => + { + var hostServices = FeaturesTestCompositions.RemoteHost.AddParts(additionalRemoteParts).GetHostServices(); + + // We want to allow references to source generators to be shared between the "in proc" and "remote" workspaces and + // MEF compositions, so tell the serializer service to use the same map for this "remote" workspace as the in-proc one. + ((IMefHostExportProvider)hostServices).GetExportedValue().SharedTestGeneratorReferences = sharedTestGeneratorReferences; + return new RemoteWorkspace(hostServices, WorkspaceKind.RemoteWorkspace); + }); } public Lazy LazyWorkspace { get; } @@ -59,7 +71,13 @@ public InProcRemoteHostClientProvider(HostWorkspaceServices services, RemoteServ { _services = services; - _lazyManager = new Lazy(() => new WorkspaceManager(RemoteAssetStorage ?? new SolutionAssetCache(), AdditionalRemoteParts)); + var testSerializerServiceFactory = ((IMefHostExportProvider)services.HostServices).GetExportedValue(); + + _lazyManager = new Lazy( + () => new WorkspaceManager( + RemoteAssetStorage ?? new SolutionAssetCache(), + testSerializerServiceFactory.SharedTestGeneratorReferences, + AdditionalRemoteParts)); _lazyClient = new AsyncLazy( cancellationToken => InProcRemoteHostClient.CreateAsync( _services, diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index 9f324d823e811..61758abb198b2 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -5,11 +5,13 @@ #nullable disable using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Serialization; @@ -30,10 +32,13 @@ internal sealed class TestSerializerService : SerializerService private static readonly ImmutableDictionary s_wellKnownReferences = ImmutableDictionary.Create() .AddRange(s_wellKnownReferenceNames.Select(pair => KeyValuePairUtil.Create(pair.Value, pair.Key))); + private readonly ConcurrentDictionary _sharedTestGeneratorReferences; + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - public TestSerializerService(HostWorkspaceServices workspaceServices) + public TestSerializerService(ConcurrentDictionary sharedTestGeneratorReferences, HostWorkspaceServices workspaceServices) : base(workspaceServices) { + _sharedTestGeneratorReferences = sharedTestGeneratorReferences; } public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) @@ -64,9 +69,82 @@ public override MetadataReference ReadMetadataReferenceFrom(ObjectReader reader, } } + public override void WriteAnalyzerReferenceTo(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken) + { + if (reference is TestGeneratorReference generatorReference) + { + // It's a test reference, we'll just store it in a map and then just write out our GUID + _sharedTestGeneratorReferences.TryAdd(generatorReference.Guid, generatorReference); + writer.WriteGuid(generatorReference.Guid); + } + else + { + writer.WriteGuid(Guid.Empty); + base.WriteAnalyzerReferenceTo(reference, writer, cancellationToken); + } + } + + public override AnalyzerReference ReadAnalyzerReferenceFrom(ObjectReader reader, CancellationToken cancellationToken) + { + var testGeneratorReferenceGuid = reader.ReadGuid(); + + if (testGeneratorReferenceGuid != Guid.Empty) + { + Contract.ThrowIfFalse(_sharedTestGeneratorReferences.TryGetValue(testGeneratorReferenceGuid, out var generatorReference)); + return generatorReference; + } + else + { + return base.ReadAnalyzerReferenceFrom(reader, cancellationToken); + } + } + [ExportWorkspaceServiceFactory(typeof(ISerializerService), layer: ServiceLayer.Test), Shared, PartNotDiscoverable] + [Export(typeof(Factory))] internal new sealed class Factory : IWorkspaceServiceFactory { + private ConcurrentDictionary _sharedTestGeneratorReferences; + + /// + /// Gate to serialize reads/writes to . + /// + private readonly object _gate = new object(); + + /// + /// In unit tests that are testing OOP, we want to be able to share test generator references directly + /// from one side to another. We'll create multiple instances of the TestSerializerService -- one for each + /// workspace and in different MEF compositions, but we'll share the generator references across them. This + /// property allows the shared dictionary to be read/set. + /// + public ConcurrentDictionary SharedTestGeneratorReferences + { + get + { + lock (_gate) + { + if (_sharedTestGeneratorReferences == null) + _sharedTestGeneratorReferences = new ConcurrentDictionary(); + + return _sharedTestGeneratorReferences; + } + } + + set + { + lock (_gate) + { + // If we're already being assigned the same set of references as before, we're fine as that won't change anything. + // Ideally, every time we created a new RemoteWorkspace we'd have a new MEF container; this would ensure that + // the assignment earlier before we create the RemoteWorkspace was always the first assignment. However the + // ExportProviderCache.cs in our unit tests hands out the same MEF container multpile times instead of implementing the expected + // contract. See https://github.com/dotnet/roslyn/issues/25863 for further details. + Contract.ThrowIfFalse(_sharedTestGeneratorReferences == null || + _sharedTestGeneratorReferences == value, "We already have a shared set of references, we shouldn't be getting another one."); + _sharedTestGeneratorReferences = value; + } + } + } + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public Factory() @@ -75,7 +153,7 @@ public Factory() [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new TestSerializerService(workspaceServices); + => new TestSerializerService(SharedTestGeneratorReferences, workspaceServices); } } } diff --git a/src/Workspaces/CoreTestUtilities/TestDocumentServiceProvider.cs b/src/Workspaces/CoreTestUtilities/TestDocumentServiceProvider.cs index 67aafba5cd896..55f12bacca1c7 100644 --- a/src/Workspaces/CoreTestUtilities/TestDocumentServiceProvider.cs +++ b/src/Workspaces/CoreTestUtilities/TestDocumentServiceProvider.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -61,9 +62,17 @@ public TestSpanMappingService(bool supportsMappingImportDirectives) public bool SupportsMappingImportDirectives { get; } + public Task> GetMappedTextChangesAsync( + Document oldDocument, + Document newDocument, + CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public Task> MapSpansAsync(Document document, IEnumerable spans, CancellationToken cancellationToken) { - return Task.FromResult(ImmutableArray.Empty); + throw new NotImplementedException(); } } } diff --git a/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs b/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs index 657e1d805378d..301521aa7175d 100644 --- a/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs +++ b/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -12,17 +13,31 @@ namespace Roslyn.Test.Utilities /// A simple deriviation of that returns the source generator /// passed, for ease in unit tests. /// - public sealed class TestGeneratorReference : AnalyzerReference + public sealed class TestGeneratorReference : AnalyzerReference, IChecksummedObject { private readonly ISourceGenerator _generator; + private readonly Checksum _checksum; public TestGeneratorReference(ISourceGenerator generator) { _generator = generator; + Guid = Guid.NewGuid(); + + // In unit tests, we often simulate OOP interactions by interacting with a in-process remote host, but + // still going through our serialization pathways. In real OOP cases we only support serializing + // AnalyzerFileReferences, since we load the file in both places. In unit tests however we often directly + // create instances of ISourceGenerators so we can test generator support for various features. + // We'll make up a checksum here so we can "serialize" it to our unit test in-proc "remote" host. + var checksumArray = Guid.ToByteArray(); + Array.Resize(ref checksumArray, Checksum.HashSize); + _checksum = Checksum.From(checksumArray); } public override string? FullPath => null; public override object Id => this; + public Guid Guid { get; } + + Checksum IChecksummedObject.Checksum => _checksum; public override ImmutableArray GetAnalyzers(string language) => ImmutableArray.Empty; public override ImmutableArray GetAnalyzersForAllLanguages() => ImmutableArray.Empty; diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs index 0e7713f1b7e99..2e88834903b65 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs @@ -2108,6 +2108,33 @@ public async Task TestApplyChanges_UpdateDocumentText() Assert.Equal(newText.ToString(), textOnDisk); } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] + public async Task TestApplyChanges_UpdateAdditionalDocumentText() + { + CreateFiles(GetSimpleCSharpSolutionWithAdditionaFile()); + var solutionFilePath = GetSolutionFileName("TestSolution.sln"); + using var workspace = CreateMSBuildWorkspace(); + var solution = await workspace.OpenSolutionAsync(solutionFilePath); + + var documents = solution.GetProjectsByName("CSharpProject").FirstOrDefault().AdditionalDocuments.ToList(); + var document = documents.Single(d => d.Name.Contains("ValidAdditionalFile")); + var text = await document.GetTextAsync(); + var newText = SourceText.From("New Text In Additional File.\r\n" + text.ToString()); + var newSolution = solution.WithAdditionalDocumentText(document.Id, newText); + + workspace.TryApplyChanges(newSolution); + + // check workspace current solution + var solution2 = workspace.CurrentSolution; + var document2 = solution2.GetAdditionalDocument(document.Id); + var text2 = await document2.GetTextAsync(); + Assert.Equal(newText.ToString(), text2.ToString()); + + // check actual file on disk... + var textOnDisk = File.ReadAllText(document.FilePath); + Assert.Equal(newText.ToString(), textOnDisk); + } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] public async Task TestApplyChanges_AddDocument() { diff --git a/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj b/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj new file mode 100644 index 0000000000000..ee1cd1c90968e --- /dev/null +++ b/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + AnyCPU + 8.0.30703 + 2.0 + {686DD036-86AA-443E-8A10-DDB43266A8C4} + Library + Properties + CSharpProject + CSharpProject + v4.0 + 512 + snKey.snk + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Workspaces/MSBuildTest/Resources/global.json b/src/Workspaces/MSBuildTest/Resources/global.json index c3c7f97f6e2a8..d1f137fd6c814 100644 --- a/src/Workspaces/MSBuildTest/Resources/global.json +++ b/src/Workspaces/MSBuildTest/Resources/global.json @@ -1,4 +1,3 @@ { - // this file is empty to ensure we get the "standard" behavior as if - // no global.json was specified in the first place -} + "comment": "this file is empty to ensure we get the 'standard' behavior as if no global.json was specified in the first place" +} \ No newline at end of file diff --git a/src/Workspaces/MSBuildTest/TestFiles/Resources.cs b/src/Workspaces/MSBuildTest/TestFiles/Resources.cs index e4ede67a90d65..4fae1df0e4b66 100644 --- a/src/Workspaces/MSBuildTest/TestFiles/Resources.cs +++ b/src/Workspaces/MSBuildTest/TestFiles/Resources.cs @@ -124,6 +124,7 @@ public static class CSharp public static string CircularProjectReferences_CircularCSharpProject1 => GetText("CircularProjectReferences.CircularCSharpProject1.csproj"); public static string CircularProjectReferences_CircularCSharpProject2 => GetText("CircularProjectReferences.CircularCSharpProject2.csproj"); public static string CSharpProject => GetText("ProjectFiles.CSharp.CSharpProject.csproj"); + public static string AdditionalFile => GetText("ProjectFiles.CSharp.AdditionalFile.csproj"); public static string DuplicateFile => GetText("ProjectFiles.CSharp.DuplicateFile.csproj"); public static string DuplicateReferences => GetText("ProjectFiles.CSharp.DuplicateReferences.csproj"); public static string DuplicatedGuidLibrary1 => GetText("ProjectFiles.CSharp.DuplicatedGuidLibrary1.csproj"); diff --git a/src/Workspaces/MSBuildTest/WorkspaceTestBase.cs b/src/Workspaces/MSBuildTest/WorkspaceTestBase.cs index 83bac93ff5ed2..1e91df76c01ee 100644 --- a/src/Workspaces/MSBuildTest/WorkspaceTestBase.cs +++ b/src/Workspaces/MSBuildTest/WorkspaceTestBase.cs @@ -104,6 +104,19 @@ protected static FileSet GetSimpleCSharpSolutionFiles() (@"CSharpProject\Properties\AssemblyInfo.cs", Resources.SourceFiles.CSharp.AssemblyInfo)); } + protected static FileSet GetSimpleCSharpSolutionWithAdditionaFile() + { + return new FileSet( + (@"NuGet.Config", Resources.NuGet_Config), + (@"Directory.Build.props", Resources.Directory_Build_props), + (@"Directory.Build.targets", Resources.Directory_Build_targets), + (@"TestSolution.sln", Resources.SolutionFiles.CSharp), + (@"CSharpProject\CSharpProject.csproj", Resources.ProjectFiles.CSharp.AdditionalFile), + (@"CSharpProject\CSharpClass.cs", Resources.SourceFiles.CSharp.CSharpClass), + (@"CSharpProject\Properties\AssemblyInfo.cs", Resources.SourceFiles.CSharp.AssemblyInfo), + (@"CSharpProject\ValidAdditionalFile.txt", Resources.SourceFiles.Text.ValidAdditionalFile)); + } + protected static FileSet GetNetCoreApp2Files() { return new FileSet( diff --git a/src/Workspaces/Remote/Core/RemoteEndPoint.cs b/src/Workspaces/Remote/Core/RemoteEndPoint.cs index 83fc6538e543b..824b62a96c2ba 100644 --- a/src/Workspaces/Remote/Core/RemoteEndPoint.cs +++ b/src/Workspaces/Remote/Core/RemoteEndPoint.cs @@ -66,7 +66,8 @@ public RemoteEndPoint(Stream stream, TraceSource logger, object? incomingCallTar _rpc = new JsonRpc(new HeaderDelimitedMessageHandler(stream, jsonFormatter)) { CancelLocallyInvokedMethodsWhenConnectionIsClosed = true, - TraceSource = logger + TraceSource = logger, + ExceptionStrategy = ExceptionProcessing.ISerializable, }; if (incomingCallTarget != null) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 2b52cd705d6d4..1948dee5f0c27 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -2,18 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; +using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; -using System; -using System.IO.Pipelines; -using Microsoft.VisualStudio.Threading; -using System.Diagnostics; +using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Remote { @@ -75,6 +75,7 @@ public static void WriteData( static void WriteAsset(ObjectWriter writer, ISerializerService serializer, SolutionReplicationContext context, Checksum checksum, SolutionAsset asset, CancellationToken cancellationToken) { + Debug.Assert(asset.Kind != WellKnownSynchronizationKind.Null, "We should not be sending null assets"); checksum.WriteTo(writer); writer.WriteInt32((int)asset.Kind); @@ -186,8 +187,7 @@ static bool IsEndOfStreamExceptionExpected(Exception? copyException, Cancellatio // in service hub, cancellation means simply closed stream var result = serializerService.Deserialize(kind, reader, cancellationToken); - // we should not request null assets: - Debug.Assert(result != null); + Debug.Assert(result != null, "We should not be requesting null assets"); results.Add((responseChecksum, result)); } diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index cba534f26f993..1b2e8b945489a 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -198,4 +198,7 @@ Navigation bar + + Inheritance margin + \ No newline at end of file diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index d252dbbc101cf..b4231bbce3995 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.EncapsulateField; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.InheritanceMargin; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.NavigationBar; using Microsoft.CodeAnalysis.ProjectTelemetry; @@ -69,6 +70,7 @@ internal sealed class ServiceDescriptors (typeof(IRemoteGlobalNotificationDeliveryService), null), (typeof(IRemoteCodeLensReferencesService), null), (typeof(IRemoteEditAndContinueService), typeof(IRemoteEditAndContinueService.ICallback)), + (typeof(IRemoteInheritanceMarginService), null), }); internal readonly RemoteSerializationOptions Options; diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs index 45b33544e51e7..6eb9c2f441675 100644 --- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs @@ -88,14 +88,6 @@ public static async Task CreateAsync( { using (Logger.LogBlock(FunctionId.ServiceHubRemoteHostClient_CreateAsync, KeyValueLogMessage.NoProperty, cancellationToken)) { - Logger.Log(FunctionId.RemoteHost_Bitness, KeyValueLogMessage.Create( - LogType.Trace, - m => - { - m["64bit"] = RemoteHostOptions.IsServiceHubProcess64Bit(services); - m["ServerGC"] = RemoteHostOptions.IsServiceHubProcessServerGC(services); - })); - #pragma warning disable ISB001 // Dispose of proxies #pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed var serviceBrokerClient = new ServiceBrokerClient(serviceBroker); diff --git a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs b/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs index e36b7331b3b86..0878c25167aac 100644 --- a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs +++ b/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs @@ -15,6 +15,10 @@ namespace Microsoft.CodeAnalysis.Remote { + /// + /// This class runs against the in-process workspace, and when it sees changes proactively pushes them to + /// the out-of-process workspace through the . + /// internal sealed class SolutionChecksumUpdater : GlobalOperationAwareIdleProcessor { private readonly Workspace _workspace; diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index e5c3c82490009..bd5a5c8704996 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Hlavní body dokumentu + Zvýraznění dokumentu Edit and Continue - Edit and Continue + Upravit a pokračovat @@ -67,6 +67,11 @@ Doručování globálních oznámení + + Inheritance margin + Míra dědičnosti + + Missing import discovery Chybí zjišťování importů @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Navigační panel diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index f0a0e87181d5e..fc7996507456d 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Dokumenthighlights + Dokumenthighlights Edit and Continue - Edit and Continue + Bearbeiten und fortfahren @@ -67,6 +67,11 @@ Globale Benachrichtigungsübermittlung + + Inheritance margin + Vererbungsrand + + Missing import discovery Fehlende Importermittlung @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Navigationsleiste diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 9d338ee5c0ceb..99b8c4416bd8a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Contenidos destacados del documento + Elementos destacados del documento Edit and Continue - Edit and Continue + Editar y continuar @@ -67,6 +67,11 @@ Entrega de notificaciones globales + + Inheritance margin + Margen de herencia + + Missing import discovery Detección de importaciones que faltan @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Barra de navegación diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 71fdb37d25ed6..115fbd2523ec4 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Colorations de documents + Grandes lignes du document Edit and Continue - Edit and Continue + Modifier et continuer @@ -67,6 +67,11 @@ Remise de notification globale + + Inheritance margin + Marge d’héritage + + Missing import discovery Détection d'importation manquante @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Barre de navigation diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index 2b4cc09c11f96..675038078c2f8 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Evidenziazioni del documento + Evidenziazioni del documento Edit and Continue - Edit and Continue + Modifica e continuazione @@ -67,6 +67,11 @@ Recapito delle notifiche globali + + Inheritance margin + Margine di ereditarietà + + Missing import discovery Individuazione delle importazioni mancanti @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Barra di spostamento diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index a62d85d722363..506710741c89b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - ドキュメント強調表示 + ドキュメント強調表示 Edit and Continue - Edit and Continue + エディット コンティニュ @@ -67,6 +67,11 @@ グローバル通知の配信 + + Inheritance margin + 継承の余白 + + Missing import discovery 不足しているインポートの検出 @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + ナビゲーション バー diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index 05bb722b52a19..ef559a20f17ce 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - 문서 하이라이트 + 문서 하이라이트 Edit and Continue - Edit and Continue + 편집하며 계속하기 @@ -67,6 +67,11 @@ 전역 알림 배달 + + Inheritance margin + 상속 여백 + + Missing import discovery 가져오기 검색 없음 @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + 탐색 모음 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index a191595d2c5a9..9c6df3ce48c97 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Wyróżnienia dokumentów + Wyróżnienia dokumentów Edit and Continue - Edit and Continue + Edytuj i kontynuuj @@ -67,6 +67,11 @@ Dostarczanie powiadomień globalnych + + Inheritance margin + Margines dziedziczenia + + Missing import discovery Odnajdywanie brakujących importów @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Pasek nawigacyjny diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index cd7c71c1f2758..3632718305b99 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Destaques do documento + Documentos em destaques Edit and Continue - Edit and Continue + Editar e Continuar @@ -67,6 +67,11 @@ Entrega de notificação global + + Inheritance margin + Margem de herança + + Missing import discovery Descoberta de importação ausente @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Barra de navegação diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index 144daa69fcb43..14a16e0069e9b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Выделения в документе + Основные моменты документа Edit and Continue - Edit and Continue + Изменить и продолжить @@ -67,6 +67,11 @@ Доставка глобальных уведомлений + + Inheritance margin + Граница наследования + + Missing import discovery Отсутствует обнаружение импорта @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Панель навигации diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index 374b482966763..67ceac7e373aa 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - Belgedeki önemli noktalar + Belgedeki önemli noktalar Edit and Continue - Edit and Continue + Düzenle ve Devam Et @@ -67,6 +67,11 @@ Genel bildirim teslimi + + Inheritance margin + Devralma boşluğu + + Missing import discovery İçeri aktarma bulma eksik @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + Gezinti çubuğu diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index b0458cf70ecbb..a5d85966661c9 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - 文档亮点 + 文档亮点 Edit and Continue - Edit and Continue + 编辑并继续 @@ -67,6 +67,11 @@ 全局通知传送 + + Inheritance margin + 继承边距 + + Missing import discovery 缺少导入发现 @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + 导航栏 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index 81b6b2b0818c0..3128d92209ca3 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -39,12 +39,12 @@ Document highlights - 文件重點 + 文件重點 Edit and Continue - Edit and Continue + 編輯並繼續 @@ -67,6 +67,11 @@ 全域通知傳遞 + + Inheritance margin + 繼承邊界 + + Missing import discovery 缺少匯入探索 @@ -79,7 +84,7 @@ Navigation bar - Navigation bar + 導覽列 diff --git a/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs b/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs index 5ac7fcddd4655..86e9314c836a2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs @@ -124,11 +124,12 @@ private void AddIfNeeded(HashSet checksums, IEnumerable checks private void AddIfNeeded(HashSet checksums, Checksum checksum) { - Debug.Assert(checksum != Checksum.Null); - - if (!_assetProvider.EnsureCacheEntryIfExists(checksum)) + if (checksum != Checksum.Null) { - checksums.Add(checksum); + if (!_assetProvider.EnsureCacheEntryIfExists(checksum)) + { + checksums.Add(checksum); + } } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/IGlobalServiceBroker.cs b/src/Workspaces/Remote/ServiceHub/Host/IGlobalServiceBroker.cs new file mode 100644 index 0000000000000..d75fc44e7567b --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/IGlobalServiceBroker.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.ServiceHub.Framework; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote.Host +{ + internal interface IGlobalServiceBroker + { + IServiceBroker Instance { get; } + } + + /// + /// Hacky way to expose a to workspace services that expect there to be a global + /// singleton (like in visual studio). Effectively the first service that gets called into will record its + /// broker here for these services to use. + /// + // Note: this Export is only so MEF picks up the exported member internally. + [Export(typeof(IGlobalServiceBroker)), Shared] + internal class GlobalServiceBroker : IGlobalServiceBroker + { + private static IServiceBroker? s_instance; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public GlobalServiceBroker() + { + } + + public static void RegisterServiceBroker(IServiceBroker serviceBroker) + { + Interlocked.CompareExchange(ref s_instance, serviceBroker, null); + } + + public IServiceBroker Instance + { + get + { + Contract.ThrowIfNull(s_instance, "Global service broker not registered"); + return s_instance; + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 7489bf7c39b02..b1e4b22cebb2a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -84,6 +84,18 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) newSolutionChecksums.AnalyzerReferences, _cancellationToken).ConfigureAwait(false)); } + // The old solution should never have any frozen source generated documents -- those are only created and forked off of + // a workspaces's CurrentSolution + Contract.ThrowIfFalse(solution.State.FrozenSourceGeneratedDocumentState == null); + + if (newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity != Checksum.Null && newSolutionChecksums.FrozenSourceGeneratedDocumentText != Checksum.Null) + { + var identity = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity, _cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentText, _cancellationToken).ConfigureAwait(false); + var sourceText = await serializableSourceText.GetTextAsync(_cancellationToken).ConfigureAwait(false); + solution = solution.WithFrozenSourceGeneratedDocument(identity, sourceText).Project.Solution; + } + #if DEBUG // make sure created solution has same checksum as given one await ValidateChecksumAsync(newSolutionChecksum, solution).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCacheStorageServiceFactory.cs b/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCacheStorageServiceFactory.cs new file mode 100644 index 0000000000000..8b0ae8a90cebe --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCacheStorageServiceFactory.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Remote.Host; +using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Storage.CloudCache; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.LanguageServices.Storage; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote.Storage +{ + [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), WorkspaceKind.RemoteWorkspace), Shared] + internal class RemoteCloudCacheStorageServiceFactory : ICloudCacheStorageServiceFactory + { + private readonly IGlobalServiceBroker _globalServiceBroker; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RemoteCloudCacheStorageServiceFactory(IGlobalServiceBroker globalServiceBroker) + { + _globalServiceBroker = globalServiceBroker; + } + + public AbstractPersistentStorageService Create(IPersistentStorageLocationService locationService) + => new RemoteCloudCachePersistentStorageService(_globalServiceBroker, locationService); + + private class RemoteCloudCachePersistentStorageService : AbstractCloudCachePersistentStorageService + { + private readonly IGlobalServiceBroker _globalServiceBroker; + + public RemoteCloudCachePersistentStorageService(IGlobalServiceBroker globalServiceBroker, IPersistentStorageLocationService locationService) + : base(locationService) + { + _globalServiceBroker = globalServiceBroker; + } + + protected override void DisposeCacheService(ICacheService cacheService) + { + if (cacheService is IAsyncDisposable asyncDisposable) + { + asyncDisposable.DisposeAsync().AsTask().Wait(); + } + else if (cacheService is IDisposable disposable) + { + disposable.Dispose(); + } + } + + protected override async ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken) + { + var serviceBroker = _globalServiceBroker.Instance; + +#pragma warning disable ISB001 // Dispose of proxies + // cache service will be disposed inside RemoteCloudCacheService.Dispose + var cacheService = await serviceBroker.GetProxyAsync(VisualStudioServices.VS2019_10.CacheService, cancellationToken: cancellationToken).ConfigureAwait(false); +#pragma warning restore ISB001 // Dispose of proxies + + Contract.ThrowIfNull(cacheService); + return cacheService; + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemotePersistentStorageLocationService.cs b/src/Workspaces/Remote/ServiceHub/Host/Storage/RemotePersistentStorageLocationService.cs similarity index 100% rename from src/Workspaces/Remote/ServiceHub/Host/RemotePersistentStorageLocationService.cs rename to src/Workspaces/Remote/ServiceHub/Host/Storage/RemotePersistentStorageLocationService.cs diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 981add13f352a..05d0cef80d809 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -203,7 +203,8 @@ public static void AppendChecksums(this HashSet set, ChecksumWithChild { if (child is Checksum checksum) { - set.Add(checksum); + if (checksum != Checksum.Null) + set.Add(checksum); } if (child is ChecksumCollection collection) diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj index 0c3504b298542..5ad1f662f2edb 100644 --- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj @@ -28,6 +28,8 @@ + + @@ -36,6 +38,10 @@ + + + + diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index debb4873d1d91..6f8a33d9e34ba 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -12,6 +12,11 @@ namespace Microsoft.CodeAnalysis.Remote { + /// + /// This service is used by the to proactively update the solution snapshot in + /// the out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after an edit + /// once a feature is asking for a snapshot. + /// internal sealed class RemoteAssetSynchronizationService : BrokeredServiceBase, IRemoteAssetSynchronizationService { internal sealed class Factory : FactoryBase diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs index e6abd6cb72e64..8829144873748 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs @@ -8,6 +8,7 @@ using System.IO.Pipelines; using System.Runtime; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Remote.Host; using Microsoft.ServiceHub.Framework; using Microsoft.ServiceHub.Framework.Services; using Nerdbank.Streams; @@ -68,6 +69,10 @@ internal TService Create( ServiceActivationOptions serviceActivationOptions, IServiceBroker serviceBroker) { + // Register this service broker globally (if it's the first we encounter) so it can be used by other + // global services that need it. + GlobalServiceBroker.RegisterServiceBroker(serviceBroker); + var descriptor = ServiceDescriptors.Instance.GetServiceDescriptorForServiceFactory(typeof(TService)); var serviceHubTraceSource = (TraceSource)hostProvidedServices.GetService(typeof(TraceSource)); var serverConnection = descriptor.WithTraceSource(serviceHubTraceSource).ConstructRpcConnection(pipe); diff --git a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs index 1f73c42542e8f..fe03f2ace3e92 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs @@ -32,6 +32,7 @@ public ValueTask ConvertToStructAsync( DocumentId documentId, TextSpan span, Scope scope, + bool isRecord, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => @@ -40,7 +41,7 @@ public ValueTask ConvertToStructAsync( var document = solution.GetDocument(documentId); var service = document.GetLanguageService(); - var updatedSolution = await service.ConvertToStructAsync(document, span, scope, cancellationToken).ConfigureAwait(false); + var updatedSolution = await service.ConvertToStructAsync(document, span, scope, isRecord, cancellationToken).ConfigureAwait(false); var cleanedSolution = await CleanupAsync(solution, updatedSolution, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttributeDiscovery/RemoteDesignerAttributeDiscoveryService.cs b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttributeDiscovery/RemoteDesignerAttributeDiscoveryService.cs index ec5978b81cc5b..a103ecfaeba1d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DesignerAttributeDiscovery/RemoteDesignerAttributeDiscoveryService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DesignerAttributeDiscovery/RemoteDesignerAttributeDiscoveryService.cs @@ -36,7 +36,7 @@ public ValueTask StartScanningForDesignerAttributesAsync(RemoteServiceCallbackId analyzerProvider, new IncrementalAnalyzerProviderMetadata( nameof(RemoteDesignerAttributeIncrementalAnalyzerProvider), - highPriorityForActiveFile: true, + highPriorityForActiveFile: false, workspaceKinds: WorkspaceKind.RemoteWorkspace)); return default; diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs index e8c49230d3a7c..b99ff55be839f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs @@ -43,13 +43,13 @@ private static readonly ConditionalWeakTable CalculateDiagnosticsAsyn var documentId = arguments.DocumentId; var projectId = arguments.ProjectId; var project = solution.GetProject(projectId); + var document = arguments.DocumentId != null + ? solution.GetTextDocument(arguments.DocumentId) ?? await solution.GetSourceGeneratedDocumentAsync(arguments.DocumentId, cancellationToken).ConfigureAwait(false) + : null; var documentSpan = arguments.DocumentSpan; var documentAnalysisKind = arguments.DocumentAnalysisKind; - var diagnosticComputer = new DiagnosticComputer(documentId, project, documentSpan, documentAnalysisKind, _analyzerInfoCache); + var diagnosticComputer = new DiagnosticComputer(document, project, documentSpan, documentAnalysisKind, _analyzerInfoCache); var result = await diagnosticComputer.GetDiagnosticsAsync( arguments.AnalyzerIds, diff --git a/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs b/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs index 968f3bf4ed4c8..d793379faf602 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs @@ -38,13 +38,13 @@ public ValueTask> GetDocumentHigh // (like the JS parts of a .cshtml file). Filter them out here. This will // need to be revisited if we someday support FAR between these languages. var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - var document = solution.GetDocument(documentId); - var documentsToSearch = ImmutableHashSet.CreateRange( - documentIdsToSearch.Select(solution.GetDocument).WhereNotNull()); + var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + var documentsToSearch = await documentIdsToSearch.SelectAsArrayAsync(id => solution.GetDocumentAsync(id, includeSourceGenerated: true, cancellationToken)).ConfigureAwait(false); + var documentsToSearchSet = ImmutableHashSet.CreateRange(documentsToSearch.WhereNotNull()); var service = document.GetLanguageService(); var result = await service.GetDocumentHighlightsAsync( - document, position, documentsToSearch, cancellationToken).ConfigureAwait(false); + document, position, documentsToSearchSet, cancellationToken).ConfigureAwait(false); return result.SelectAsArray(SerializableDocumentHighlights.Dehydrate); }, cancellationToken); diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index 6714587735c06..4d500a0bcd2b3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -43,6 +43,9 @@ Task> IManagedEditAndContinueDeb Task IManagedEditAndContinueDebuggerService.GetAvailabilityAsync(Guid moduleVersionId, CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.GetAvailabilityAsync(_callbackId, moduleVersionId, cancellationToken), cancellationToken).AsTask(); + Task> IManagedEditAndContinueDebuggerService.GetCapabilitiesAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.GetCapabilitiesAsync(_callbackId, cancellationToken), cancellationToken).AsTask(); + Task IManagedEditAndContinueDebuggerService.PrepareModuleForUpdateAsync(Guid moduleVersionId, CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.PrepareModuleForUpdateAsync(_callbackId, moduleVersionId, cancellationToken), cancellationToken).AsTask(); } @@ -58,11 +61,8 @@ public RemoteEditAndContinueService(in ServiceConstructionArguments arguments, R private IEditAndContinueWorkspaceService GetService() => GetWorkspace().Services.GetRequiredService(); - private SolutionActiveStatementSpanProvider CreateSolutionActiveStatementSpanProvider(RemoteServiceCallbackId callbackId) - => new((documentId, cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.GetSpansAsync(callbackId, documentId, cancellationToken), cancellationToken)); - - private DocumentActiveStatementSpanProvider CreateDocumentActiveStatementSpanProvider(RemoteServiceCallbackId callbackId) - => new(cancellationToken => _callback.InvokeAsync((callback, cancellationToken) => callback.GetSpansAsync(callbackId, cancellationToken), cancellationToken)); + private ActiveStatementSpanProvider CreateActiveStatementSpanProvider(RemoteServiceCallbackId callbackId) + => new((documentId, filePath, cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.GetSpansAsync(callbackId, documentId, filePath, cancellationToken), cancellationToken)); /// /// Remote API. @@ -109,9 +109,9 @@ public ValueTask> GetDocumentDiagnosticsAsync(Pin return RunServiceAsync(async cancellationToken => { var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - var document = solution.GetRequiredDocument(documentId); + var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - var diagnostics = await GetService().GetDocumentDiagnosticsAsync(document, CreateDocumentActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); + var diagnostics = await GetService().GetDocumentDiagnosticsAsync(document, CreateActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); return diagnostics.SelectAsArray(diagnostic => DiagnosticData.Create(diagnostic, document)); }, cancellationToken); } @@ -125,14 +125,14 @@ public ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteSe { var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - return await GetService().HasChangesAsync(solution, CreateSolutionActiveStatementSpanProvider(callbackId), sourceFilePath, cancellationToken).ConfigureAwait(false); + return await GetService().HasChangesAsync(solution, CreateActiveStatementSpanProvider(callbackId), sourceFilePath, cancellationToken).ConfigureAwait(false); }, cancellationToken); } /// /// Remote API. /// - public ValueTask<(ManagedModuleUpdates Updates, ImmutableArray Diagnostics, ImmutableArray DocumentsWithRudeEdits)> EmitSolutionUpdateAsync( + public ValueTask EmitSolutionUpdateAsync( PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => @@ -142,8 +142,8 @@ public ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteSe try { - var results = await service.EmitSolutionUpdateAsync(solution, CreateSolutionActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); - return (results.ModuleUpdates, results.GetDiagnosticData(solution), results.DocumentsWithRudeEdits.SelectAsArray(d => d.DocumentId)); + var results = await service.EmitSolutionUpdateAsync(solution, CreateActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); + return results.Dehydrate(solution); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -152,7 +152,7 @@ public ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteSe var diagnostic = Diagnostic.Create(descriptor, Location.None, new[] { e.Message }); var diagnostics = ImmutableArray.Create(DiagnosticData.Create(diagnostic, solution.Options)); - return (updates, diagnostics, ImmutableArray.Empty); + return new EmitSolutionUpdateResults.Data(updates, diagnostics, ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)>.Empty); } }, cancellationToken); } @@ -184,7 +184,7 @@ public ValueTask DiscardSolutionUpdateAsync(CancellationToken cancellationToken) /// /// Remote API. /// - public ValueTask>> GetBaseActiveStatementSpansAsync(PinnedSolutionInfo solutionInfo, ImmutableArray documentIds, CancellationToken cancellationToken) + public ValueTask>> GetBaseActiveStatementSpansAsync(PinnedSolutionInfo solutionInfo, ImmutableArray documentIds, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => { @@ -196,13 +196,13 @@ public ValueTask DiscardSolutionUpdateAsync(CancellationToken cancellationToken) /// /// Remote API. /// - public ValueTask> GetAdjustedActiveStatementSpansAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) + public ValueTask> GetAdjustedActiveStatementSpansAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => { var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - var document = solution.GetRequiredDocument(documentId); - return await GetService().GetAdjustedActiveStatementSpansAsync(document, CreateDocumentActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); + var document = await solution.GetRequiredTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); + return await GetService().GetAdjustedActiveStatementSpansAsync(document, CreateActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -226,7 +226,7 @@ public ValueTask DiscardSolutionUpdateAsync(CancellationToken cancellationToken) return RunServiceAsync(async cancellationToken => { var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - return await GetService().GetCurrentActiveStatementPositionAsync(solution, CreateSolutionActiveStatementSpanProvider(callbackId), instructionId, cancellationToken).ConfigureAwait(false); + return await GetService().GetCurrentActiveStatementPositionAsync(solution, CreateActiveStatementSpanProvider(callbackId), instructionId, cancellationToken).ConfigureAwait(false); }, cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs index df48f54df8abd..8ec4923b98d2c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs @@ -4,7 +4,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -49,9 +48,9 @@ public ValueTask FindReferencesAsync( if (symbol == null) return; - var context = new RemoteFindUsageContext(_callback, callbackId, cancellationToken); + var context = new RemoteFindUsageContext(_callback, callbackId); await AbstractFindUsagesService.FindReferencesAsync( - context, symbol, project, options).ConfigureAwait(false); + context, symbol, project, options, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -71,9 +70,9 @@ public ValueTask FindImplementationsAsync( if (symbol == null) return; - var context = new RemoteFindUsageContext(_callback, callbackId, cancellationToken); + var context = new RemoteFindUsageContext(_callback, callbackId); await AbstractFindUsagesService.FindImplementationsAsync( - symbol, project, context).ConfigureAwait(false); + symbol, project, context, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -83,22 +82,19 @@ private sealed class RemoteFindUsageContext : IFindUsagesContext, IStreamingProg private readonly RemoteServiceCallbackId _callbackId; private readonly Dictionary _definitionItemToId = new(); - public CancellationToken CancellationToken { get; } - - public RemoteFindUsageContext(RemoteCallback callback, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + public RemoteFindUsageContext(RemoteCallback callback, RemoteServiceCallbackId callbackId) { _callback = callback; _callbackId = callbackId; - CancellationToken = cancellationToken; } #region IStreamingProgressTracker - public ValueTask AddItemsAsync(int count) - => _callback.InvokeAsync((callback, cancellationToken) => callback.AddItemsAsync(_callbackId, count), CancellationToken); + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.AddItemsAsync(_callbackId, count, cancellationToken), cancellationToken); - public ValueTask ItemCompletedAsync() - => _callback.InvokeAsync((callback, cancellationToken) => callback.ItemCompletedAsync(_callbackId), CancellationToken); + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.ItemCompletedAsync(_callbackId, cancellationToken), cancellationToken); #endregion @@ -106,21 +102,17 @@ public ValueTask ItemCompletedAsync() public IStreamingProgressTracker ProgressTracker => this; - public ValueTask ReportMessageAsync(string message) - => _callback.InvokeAsync((callback, cancellationToken) => callback.ReportMessageAsync(_callbackId, message), CancellationToken); - - [Obsolete] - public ValueTask ReportProgressAsync(int current, int maximum) - => _callback.InvokeAsync((callback, cancellationToken) => callback.ReportProgressAsync(_callbackId, current, maximum), CancellationToken); + public ValueTask ReportMessageAsync(string message, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.ReportMessageAsync(_callbackId, message, cancellationToken), cancellationToken); - public ValueTask SetSearchTitleAsync(string title) - => _callback.InvokeAsync((callback, cancellationToken) => callback.SetSearchTitleAsync(_callbackId, title), CancellationToken); + public ValueTask SetSearchTitleAsync(string title, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.SetSearchTitleAsync(_callbackId, title, cancellationToken), cancellationToken); - public ValueTask OnDefinitionFoundAsync(DefinitionItem definition) + public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) { var id = GetOrAddDefinitionItemId(definition); var dehydratedDefinition = SerializableDefinitionItem.Dehydrate(id, definition); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedDefinition), CancellationToken); + return _callback.InvokeAsync((callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedDefinition, cancellationToken), cancellationToken); } private int GetOrAddDefinitionItemId(DefinitionItem item) @@ -137,11 +129,11 @@ private int GetOrAddDefinitionItemId(DefinitionItem item) } } - public ValueTask OnReferenceFoundAsync(SourceReferenceItem reference) + public ValueTask OnReferenceFoundAsync(SourceReferenceItem reference, CancellationToken cancellationToken) { var definitionItem = GetOrAddDefinitionItemId(reference.Definition); var dehydratedReference = SerializableSourceReferenceItem.Dehydrate(definitionItem, reference); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnReferenceFoundAsync(_callbackId, dehydratedReference), CancellationToken); + return _callback.InvokeAsync((callback, cancellationToken) => callback.OnReferenceFoundAsync(_callbackId, dehydratedReference, cancellationToken), cancellationToken); } #endregion diff --git a/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs b/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs new file mode 100644 index 0000000000000..3ae3b5404c9af --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.InheritanceMargin; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal sealed class RemoteInheritanceMarginService : BrokeredServiceBase, IRemoteInheritanceMarginService + { + internal sealed class Factory : FactoryBase + { + protected override IRemoteInheritanceMarginService CreateService(in ServiceConstructionArguments arguments) + { + return new RemoteInheritanceMarginService(arguments); + } + } + + public RemoteInheritanceMarginService(in ServiceConstructionArguments arguments) : base(in arguments) + { + } + + public ValueTask> GetInheritanceMarginItemsAsync( + PinnedSolutionInfo pinnedSolutionInfo, + ProjectId projectId, + ImmutableArray<(SymbolKey symbolKey, int lineNumber)> symbolKeyAndLineNumbers, + CancellationToken cancellationToken) + => RunServiceAsync(async cancellationToken => + { + var solution = await GetSolutionAsync(pinnedSolutionInfo, cancellationToken).ConfigureAwait(false); + return await InheritanceMarginServiceHelper + .GetInheritanceMemberItemAsync(solution, projectId, symbolKeyAndLineNumbers, cancellationToken) + .ConfigureAwait(false); + }, cancellationToken); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs index 555f1fd38d4c1..ad93ca5162926 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.NavigationBar; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote { @@ -30,7 +31,8 @@ public ValueTask> GetItemsAsync( { var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - var document = solution.GetRequiredDocument(documentId); + var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(document); var navigationBarService = document.GetRequiredLanguageService(); var result = await navigationBarService.GetItemsAsync(document, supportsCodeGeneration, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs index f9af7086df322..9a609abf47435 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs @@ -95,7 +95,7 @@ private static async Task CacheSemanticClassificationsAsync(Document document, C return; var storage = await persistenceService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); + await using var _1 = storage.ConfigureAwait(false); if (storage == null) return; @@ -114,29 +114,23 @@ private static async Task CacheSemanticClassificationsAsync(Document document, C if (matches) return; - var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList(); - try - { - // Compute classifications for the full span. - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - await classificationService.AddSemanticClassificationsAsync(document, new TextSpan(0, text.Length), classifiedSpans, cancellationToken).ConfigureAwait(false); + using var _2 = ArrayBuilder.GetInstance(out var classifiedSpans); - using var stream = SerializableBytes.CreateWritableStream(); - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) - { - WriteTo(classifiedSpans, writer); - } + // Compute classifications for the full span. + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + await classificationService.AddSemanticClassificationsAsync(document, new TextSpan(0, text.Length), classifiedSpans, cancellationToken).ConfigureAwait(false); - stream.Position = 0; - await storage.WriteStreamAsync(documentKey, PersistenceName, stream, checksum, cancellationToken).ConfigureAwait(false); - } - finally + using var stream = SerializableBytes.CreateWritableStream(); + using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) { - ClassificationUtilities.ReturnClassifiedSpanList(classifiedSpans); + WriteTo(classifiedSpans, writer); } + + stream.Position = 0; + await storage.WriteStreamAsync(documentKey, PersistenceName, stream, checksum, cancellationToken).ConfigureAwait(false); } - private static void WriteTo(List classifiedSpans, ObjectWriter writer) + private static void WriteTo(ArrayBuilder classifiedSpans, ObjectWriter writer) { writer.WriteInt32(ClassificationFormat); diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs new file mode 100644 index 0000000000000..dfa97c1d7fd3c --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Remote +{ + [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Host)] + [Shared] + internal sealed class ServiceHubDocumentTrackingService : IDocumentTrackingService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ServiceHubDocumentTrackingService() + { + } + + public bool SupportsDocumentTracking => false; + + public event EventHandler ActiveDocumentChanged { add { } remove { } } + public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } + + public ImmutableArray GetVisibleDocuments() + { + Fail("Code should not be attempting to obtain visible documents from a stateless remote invocation."); + return ImmutableArray.Empty; + } + + public DocumentId? TryGetActiveDocument() + { + Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); + return null; + } + + private static void Fail(string message) + { + // assert in debug builds to hopefully catch problems in CI + Debug.Fail(message); + + // record NFW to see who violates contract. + WatsonReporter.ReportNonFatal(new InvalidOperationException(message)); + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index c51648acb00ea..d1fe987bbfb55 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -48,12 +48,12 @@ public ValueTask FindReferencesAsync( var symbol = await symbolAndProjectIdArg.TryRehydrateAsync( solution, cancellationToken).ConfigureAwait(false); - var progressCallback = new FindReferencesProgressCallback(solution, _callback, callbackId, cancellationToken); + var progressCallback = new FindReferencesProgressCallback(solution, _callback, callbackId); if (symbol == null) { - await progressCallback.OnStartedAsync().ConfigureAwait(false); - await progressCallback.OnCompletedAsync().ConfigureAwait(false); + await progressCallback.OnStartedAsync(cancellationToken).ConfigureAwait(false); + await progressCallback.OnCompletedAsync(cancellationToken).ConfigureAwait(false); return; } @@ -77,7 +77,7 @@ public ValueTask FindLiteralReferencesAsync(PinnedSolutionInfo solutionInfo, Rem var convertedType = System.Convert.ChangeType(value, typeCode); var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - var progressCallback = new FindLiteralReferencesProgressCallback(_callback, callbackId, cancellationToken); + var progressCallback = new FindLiteralReferencesProgressCallback(_callback, callbackId); await SymbolFinder.FindLiteralReferencesInCurrentProcessAsync( convertedType, solution, progressCallback, cancellationToken).ConfigureAwait(false); }, cancellationToken); @@ -185,26 +185,24 @@ private sealed class FindLiteralReferencesProgressCallback : IStreamingFindLiter { private readonly RemoteCallback _callback; private readonly RemoteServiceCallbackId _callbackId; - private readonly CancellationToken _cancellationToken; public IStreamingProgressTracker ProgressTracker { get; } - public FindLiteralReferencesProgressCallback(RemoteCallback callback, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + public FindLiteralReferencesProgressCallback(RemoteCallback callback, RemoteServiceCallbackId callbackId) { _callback = callback; _callbackId = callbackId; - _cancellationToken = cancellationToken; ProgressTracker = this; } - public ValueTask OnReferenceFoundAsync(Document document, TextSpan span) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnLiteralReferenceFoundAsync(_callbackId, document.Id, span), _cancellationToken); + public ValueTask OnReferenceFoundAsync(Document document, TextSpan span, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.OnLiteralReferenceFoundAsync(_callbackId, document.Id, span, cancellationToken), cancellationToken); - public ValueTask AddItemsAsync(int count) - => _callback.InvokeAsync((callback, cancellationToken) => callback.AddLiteralItemsAsync(_callbackId, count), _cancellationToken); + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.AddLiteralItemsAsync(_callbackId, count, cancellationToken), cancellationToken); - public ValueTask ItemCompletedAsync() - => _callback.InvokeAsync((callback, cancellationToken) => callback.LiteralItemCompletedAsync(_callbackId), _cancellationToken); + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.LiteralItemCompletedAsync(_callbackId, cancellationToken), cancellationToken); } private sealed class FindReferencesProgressCallback : IStreamingFindReferencesProgress, IStreamingProgressTracker @@ -212,50 +210,52 @@ private sealed class FindReferencesProgressCallback : IStreamingFindReferencesPr private readonly Solution _solution; private readonly RemoteCallback _callback; private readonly RemoteServiceCallbackId _callbackId; - private readonly CancellationToken _cancellationToken; public IStreamingProgressTracker ProgressTracker { get; } - public FindReferencesProgressCallback(Solution solution, RemoteCallback callback, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) + public FindReferencesProgressCallback(Solution solution, RemoteCallback callback, RemoteServiceCallbackId callbackId) { _solution = solution; _callback = callback; _callbackId = callbackId; - _cancellationToken = cancellationToken; ProgressTracker = this; } - public ValueTask OnStartedAsync() - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnStartedAsync(_callbackId), _cancellationToken); + public ValueTask OnStartedAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.OnStartedAsync(_callbackId, cancellationToken), cancellationToken); - public ValueTask OnCompletedAsync() - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnCompletedAsync(_callbackId), _cancellationToken); + public ValueTask OnCompletedAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.OnCompletedAsync(_callbackId, cancellationToken), cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(Document document) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentStartedAsync(_callbackId, document.Id), _cancellationToken); + public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentStartedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(Document document) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentCompletedAsync(_callbackId, document.Id), _cancellationToken); + public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentCompletedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - public ValueTask OnDefinitionFoundAsync(ISymbol definition) + public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { - var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, _cancellationToken); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedDefinition), _cancellationToken); + var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); + return _callback.InvokeAsync( + (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup, cancellationToken), cancellationToken); } - public ValueTask OnReferenceFoundAsync(ISymbol definition, ReferenceLocation reference) + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation reference, CancellationToken cancellationToken) { - var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, _cancellationToken); - var dehydratedReference = SerializableReferenceLocation.Dehydrate(reference, _cancellationToken); + var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); + var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, cancellationToken); + var dehydratedReference = SerializableReferenceLocation.Dehydrate(reference, cancellationToken); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnReferenceFoundAsync(_callbackId, dehydratedDefinition, dehydratedReference), _cancellationToken); + return _callback.InvokeAsync( + (callback, cancellationToken) => callback.OnReferenceFoundAsync( + _callbackId, dehydratedGroup, dehydratedDefinition, dehydratedReference, cancellationToken), cancellationToken); } - public ValueTask AddItemsAsync(int count) - => _callback.InvokeAsync((callback, cancellationToken) => callback.AddReferenceItemsAsync(_callbackId, count), _cancellationToken); + public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.AddReferenceItemsAsync(_callbackId, count, cancellationToken), cancellationToken); - public ValueTask ItemCompletedAsync() - => _callback.InvokeAsync((callback, cancellationToken) => callback.ReferenceItemCompletedAsync(_callbackId), _cancellationToken); + public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.ReferenceItemCompletedAsync(_callbackId, cancellationToken), cancellationToken); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs index 430e349662eef..68a6c3deaea5a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.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.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -19,7 +17,7 @@ internal static partial class ExpressionSyntaxExtensions { public static ExpressionSyntax WalkUpParentheses(this ExpressionSyntax expression) { - while (expression.IsParentKind(SyntaxKind.ParenthesizedExpression, out ExpressionSyntax parentExpr)) + while (expression.IsParentKind(SyntaxKind.ParenthesizedExpression, out ExpressionSyntax? parentExpr)) expression = parentExpr; return expression; @@ -27,7 +25,7 @@ public static ExpressionSyntax WalkUpParentheses(this ExpressionSyntax expressio public static ExpressionSyntax WalkDownParentheses(this ExpressionSyntax expression) { - while (expression.IsKind(SyntaxKind.ParenthesizedExpression, out ParenthesizedExpressionSyntax parenExpression)) + while (expression.IsKind(SyntaxKind.ParenthesizedExpression, out ParenthesizedExpressionSyntax? parenExpression)) expression = parenExpression.Expression; return expression; @@ -36,8 +34,8 @@ public static ExpressionSyntax WalkDownParentheses(this ExpressionSyntax express public static bool IsQualifiedCrefName(this ExpressionSyntax expression) => expression.IsParentKind(SyntaxKind.NameMemberCref) && expression.Parent.IsParentKind(SyntaxKind.QualifiedCref); - public static bool IsSimpleMemberAccessExpressionName(this ExpressionSyntax expression) - => expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess) && memberAccess.Name == expression; + public static bool IsSimpleMemberAccessExpressionName([NotNullWhen(true)] this ExpressionSyntax? expression) + => expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax? memberAccess) && memberAccess.Name == expression; public static bool IsAnyMemberAccessExpressionName(this ExpressionSyntax expression) { @@ -50,15 +48,15 @@ public static bool IsAnyMemberAccessExpressionName(this ExpressionSyntax express expression.IsMemberBindingExpressionName(); } - public static bool IsMemberBindingExpressionName(this ExpressionSyntax expression) - => expression.IsParentKind(SyntaxKind.MemberBindingExpression, out MemberBindingExpressionSyntax memberBinding) && + public static bool IsMemberBindingExpressionName([NotNullWhen(true)] this ExpressionSyntax? expression) + => expression.IsParentKind(SyntaxKind.MemberBindingExpression, out MemberBindingExpressionSyntax? memberBinding) && memberBinding.Name == expression; - public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression) - => expression.IsParentKind(SyntaxKind.QualifiedName, out QualifiedNameSyntax qualifiedName) && qualifiedName.Right == expression; + public static bool IsRightSideOfQualifiedName([NotNullWhen(true)] this ExpressionSyntax? expression) + => expression.IsParentKind(SyntaxKind.QualifiedName, out QualifiedNameSyntax? qualifiedName) && qualifiedName.Right == expression; public static bool IsRightSideOfColonColon(this ExpressionSyntax expression) - => expression.IsParentKind(SyntaxKind.AliasQualifiedName, out AliasQualifiedNameSyntax aliasName) && aliasName.Name == expression; + => expression.IsParentKind(SyntaxKind.AliasQualifiedName, out AliasQualifiedNameSyntax? aliasName) && aliasName.Name == expression; public static bool IsRightSideOfDot(this ExpressionSyntax name) => IsSimpleMemberAccessExpressionName(name) || IsMemberBindingExpressionName(name) || IsRightSideOfQualifiedName(name) || IsQualifiedCrefName(name); @@ -79,19 +77,17 @@ public static bool IsRightOfCloseParen(this ExpressionSyntax expression) && firstToken.GetPreviousToken().Kind() == SyntaxKind.CloseParenToken; } - public static bool IsLeftSideOfDot(this ExpressionSyntax expression) + public static bool IsLeftSideOfDot([NotNullWhen(true)] this ExpressionSyntax? expression) { if (expression == null) - { return false; - } return IsLeftSideOfQualifiedName(expression) || IsLeftSideOfSimpleMemberAccessExpression(expression); } public static bool IsLeftSideOfSimpleMemberAccessExpression(this ExpressionSyntax expression) - => (expression?.Parent).IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess) && + => (expression?.Parent).IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax? memberAccess) && memberAccess.Expression == expression; public static bool IsLeftSideOfDotOrArrow(this ExpressionSyntax expression) @@ -99,16 +95,16 @@ public static bool IsLeftSideOfDotOrArrow(this ExpressionSyntax expression) (expression.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression == expression); public static bool IsLeftSideOfQualifiedName(this ExpressionSyntax expression) - => (expression?.Parent).IsKind(SyntaxKind.QualifiedName, out QualifiedNameSyntax qualifiedName) && qualifiedName.Left == expression; + => (expression?.Parent).IsKind(SyntaxKind.QualifiedName, out QualifiedNameSyntax? qualifiedName) && qualifiedName.Left == expression; - public static bool IsLeftSideOfExplicitInterfaceSpecifier(this NameSyntax name) + public static bool IsLeftSideOfExplicitInterfaceSpecifier([NotNullWhen(true)] this NameSyntax? name) => name.IsParentKind(SyntaxKind.ExplicitInterfaceSpecifier); public static bool IsExpressionOfInvocation(this ExpressionSyntax expression) - => expression.IsParentKind(SyntaxKind.InvocationExpression, out InvocationExpressionSyntax invocation) && + => expression.IsParentKind(SyntaxKind.InvocationExpression, out InvocationExpressionSyntax? invocation) && invocation.Expression == expression; - public static bool TryGetNameParts(this ExpressionSyntax expression, out IList parts) + public static bool TryGetNameParts(this ExpressionSyntax expression, [NotNullWhen(true)] out IList? parts) { var partsList = new List(); if (!TryGetNameParts(expression, partsList)) @@ -123,7 +119,7 @@ public static bool TryGetNameParts(this ExpressionSyntax expression, out IList parts) { - if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess)) + if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax? memberAccess)) { if (!TryGetNameParts(memberAccess.Expression, parts)) { @@ -132,7 +128,7 @@ public static bool TryGetNameParts(this ExpressionSyntax expression, List part public static bool IsAnyLiteralExpression(this ExpressionSyntax expression) => expression is LiteralExpressionSyntax; - public static bool IsInConstantContext(this ExpressionSyntax expression) + public static bool IsInConstantContext([NotNullWhen(true)] this ExpressionSyntax? expression) { + if (expression == null) + return false; + if (expression.GetAncestor() != null) - { return true; - } var attributeArgument = expression.GetAncestor(); if (attributeArgument != null) @@ -183,9 +180,7 @@ public static bool IsInConstantContext(this ExpressionSyntax expression) } if (expression.IsParentKind(SyntaxKind.ConstantPattern)) - { return true; - } // note: the above list is not intended to be exhaustive. If more cases // are discovered that should be considered 'constant' contexts in the @@ -212,7 +207,7 @@ private static ExpressionSyntax GetExpressionToAnalyzeForWrites(ExpressionSyntax { if (expression.IsRightSideOfDotOrArrow()) { - expression = expression.Parent as ExpressionSyntax; + expression = (ExpressionSyntax)expression.GetRequiredParent(); } expression = expression.WalkUpParentheses(); @@ -306,35 +301,59 @@ private static bool IsExpressionOfArgumentInDeconstruction(ExpressionSyntax expr } } - public static bool IsWrittenTo(this ExpressionSyntax expression) + public static bool IsWrittenTo(this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) { + if (expression == null) + return false; + expression = GetExpressionToAnalyzeForWrites(expression); if (expression.IsOnlyWrittenTo()) - { return true; - } if (expression.IsInRefContext()) { + // most cases of `ref x` will count as a potential write of `x`. An important exception is: + // `ref readonly y = ref x`. In that case, because 'y' can't be written to, this would not + // be a write of 'x'. + if (expression is { Parent: { RawKind: (int)SyntaxKind.RefExpression, Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: RefTypeSyntax refType } } } } } + && refType.ReadOnlyKeyword != default) + { + return false; + } + return true; } + // Similar to `ref x`, `&x` allows reads and write of the value, meaning `x` may be (but is not definitely) + // written to. + if (expression.Parent.IsKind(SyntaxKind.AddressOfExpression)) + return true; + // We're written if we're used in a ++, or -- expression. if (expression.IsOperandOfIncrementOrDecrementExpression()) - { return true; - } if (expression.IsLeftSideOfAnyAssignExpression()) - { return true; + + // An extension method invocation with a ref-this parameter can write to an expression. + if (expression.Parent is MemberAccessExpressionSyntax memberAccess && + expression == memberAccess.Expression) + { + var symbol = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol; + if (symbol is IMethodSymbol { MethodKind: MethodKind.ReducedExtension, ReducedFrom: IMethodSymbol reducedFrom } && + reducedFrom.Parameters.Length > 0 && + reducedFrom.Parameters.First().RefKind == RefKind.Ref) + { + return true; + } } return false; } - public static bool IsAttributeNamedArgumentIdentifier(this ExpressionSyntax expression) + public static bool IsAttributeNamedArgumentIdentifier([NotNullWhen(true)] this ExpressionSyntax? expression) { var nameEquals = expression?.Parent as NameEqualsSyntax; return nameEquals.IsParentKind(SyntaxKind.AttributeArgument); @@ -342,9 +361,9 @@ public static bool IsAttributeNamedArgumentIdentifier(this ExpressionSyntax expr public static bool IsOperandOfIncrementOrDecrementExpression(this ExpressionSyntax expression) { - if (expression != null) + if (expression?.Parent is SyntaxNode parent) { - switch (expression.Parent.Kind()) + switch (parent.Kind()) { case SyntaxKind.PostIncrementExpression: case SyntaxKind.PreIncrementExpression: @@ -397,7 +416,7 @@ public static bool CanReplaceWithRValue( // i.e. you can't replace "a" in "a = b" with "Goo() = b". return expression != null && - !expression.IsWrittenTo() && + !expression.IsWrittenTo(semanticModel, cancellationToken) && CanReplaceWithLValue(expression, semanticModel, cancellationToken); } @@ -453,9 +472,12 @@ public static bool CanReplaceWithLValue( // is some form of member binding expression and they cannot be replaced with an LValue. if (expression.IsKind(SyntaxKind.ConditionalAccessExpression)) { - return expression.Parent.Kind() != SyntaxKind.ConditionalAccessExpression; + return expression is { Parent: { RawKind: not (int)SyntaxKind.ConditionalAccessExpression } }; } + if (expression.Parent == null) + return false; + switch (expression.Parent.Kind()) { case SyntaxKind.InvocationExpression: @@ -523,6 +545,7 @@ public static bool CanReplaceWithLValue( case SyntaxKind.RefExpression: case SyntaxKind.LockStatement: case SyntaxKind.ElementAccessExpression: + case SyntaxKind.SwitchExpressionArm: // Direct parent kind checks. return true; } @@ -543,7 +566,7 @@ public static bool CanReplaceWithLValue( } if (parentNonExpression != null && - parentNonExpression.IsKind(SyntaxKind.FromClause, out FromClauseSyntax fromClause) && + parentNonExpression.IsKind(SyntaxKind.FromClause, out FromClauseSyntax? fromClause) && topExpression != null && fromClause.Type == topExpression) { @@ -614,10 +637,18 @@ public static bool CanAccessInstanceAndStaticMembersOffOf( public static bool IsNameOfArgumentExpression(this ExpressionSyntax expression) { - return expression.IsParentKind(SyntaxKind.Argument) && - expression.Parent.IsParentKind(SyntaxKind.ArgumentList) && - expression.Parent.Parent.Parent is InvocationExpressionSyntax invocation && - invocation.IsNameOfInvocation(); + return expression is + { + Parent: + { + RawKind: (int)SyntaxKind.Argument, + Parent: + { + RawKind: (int)SyntaxKind.ArgumentList, + Parent: InvocationExpressionSyntax invocation + } + } + } && invocation.IsNameOfInvocation(); } public static bool IsNameOfInvocation(this InvocationExpressionSyntax invocation) @@ -626,7 +657,7 @@ public static bool IsNameOfInvocation(this InvocationExpressionSyntax invocation identifierName.Identifier.IsKindOrHasMatchingText(SyntaxKind.NameOfKeyword); } - public static SimpleNameSyntax GetRightmostName(this ExpressionSyntax node) + public static SimpleNameSyntax? GetRightmostName(this ExpressionSyntax node) { if (node is MemberAccessExpressionSyntax memberAccess && memberAccess.Name != null) { @@ -821,7 +852,7 @@ public static bool TryConvertToStatement( this ExpressionSyntax expression, SyntaxToken? semicolonTokenOpt, bool createReturnStatementForExpression, - out StatementSyntax statement) + [NotNullWhen(true)] out StatementSyntax? statement) { // It's tricky to convert an arrow expression with directives over to a block. // We'd need to find and remove the directives *after* the arrow expression and @@ -840,7 +871,7 @@ public static bool TryConvertToStatement( private static StatementSyntax ConvertToStatement(ExpressionSyntax expression, SyntaxToken semicolonToken, bool createReturnStatementForExpression) { - if (expression.IsKind(SyntaxKind.ThrowExpression, out ThrowExpressionSyntax throwExpression)) + if (expression.IsKind(SyntaxKind.ThrowExpression, out ThrowExpressionSyntax? throwExpression)) { return SyntaxFactory.ThrowStatement(throwExpression.ThrowKeyword, throwExpression.Expression, semicolonToken); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs index f2ef822ad2493..b51477e9de31c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs @@ -44,6 +44,7 @@ public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).Identifier; case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).Identifier; @@ -82,6 +83,7 @@ public static int GetArity(this MemberDeclarationSyntax member) case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).Arity; case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).Arity; @@ -103,6 +105,7 @@ public static TypeParameterListSyntax GetTypeParameterList(this MemberDeclaratio case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).TypeParameterList; case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).TypeParameterList; @@ -114,32 +117,6 @@ public static TypeParameterListSyntax GetTypeParameterList(this MemberDeclaratio return null; } - public static BaseParameterListSyntax GetParameterList(this MemberDeclarationSyntax member) - { - if (member != null) - { - switch (member.Kind()) - { - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).ParameterList; - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).ParameterList; - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)member).ParameterList; - case SyntaxKind.DestructorDeclaration: - return ((DestructorDeclarationSyntax)member).ParameterList; - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)member).ParameterList; - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)member).ParameterList; - case SyntaxKind.ConversionOperatorDeclaration: - return ((ConversionOperatorDeclarationSyntax)member).ParameterList; - } - } - - return null; - } - public static MemberDeclarationSyntax WithParameterList( this MemberDeclarationSyntax member, BaseParameterListSyntax parameterList) @@ -189,6 +166,7 @@ public static MemberDeclarationSyntax WithAttributeLists( case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).WithAttributeLists(attributeLists); case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).WithAttributeLists(attributeLists); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs index 232aa93716cd4..2671f3305a661 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.PooledObjects; @@ -16,7 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions { internal static class ParenthesizedExpressionSyntaxExtensions { - public static bool CanRemoveParentheses(this ParenthesizedExpressionSyntax node, SemanticModel semanticModel) + public static bool CanRemoveParentheses( + this ParenthesizedExpressionSyntax node, SemanticModel semanticModel, CancellationToken cancellationToken) { if (node.OpenParenToken.IsMissing || node.CloseParenToken.IsMissing) { @@ -136,7 +138,8 @@ public static bool CanRemoveParentheses(this ParenthesizedExpressionSyntax node, // Handle expression-level ambiguities if (RemovalMayIntroduceCastAmbiguity(node) || RemovalMayIntroduceCommaListAmbiguity(node) || - RemovalMayIntroduceInterpolationAmbiguity(node)) + RemovalMayIntroduceInterpolationAmbiguity(node) || + RemovalWouldChangeConstantReferenceToTypeReference(node, expression, semanticModel, cancellationToken)) { return false; } @@ -306,6 +309,24 @@ public static bool CanRemoveParentheses(this ParenthesizedExpressionSyntax node, return parentExpression != null && !RemovalChangesAssociation(node, parentExpression, semanticModel); } + private static bool RemovalWouldChangeConstantReferenceToTypeReference( + ParenthesizedExpressionSyntax node, ExpressionSyntax expression, + SemanticModel semanticModel, CancellationToken cancellationToken) + { + // With cases like: `if (x is (Y))` then we cannot remove the parens if it would make Y now bind to a type + // instead of a constant. + if (node.Parent is not ConstantPatternSyntax { Parent: IsPatternExpressionSyntax }) + return false; + + var exprSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).Symbol; + if (exprSymbol is not IFieldSymbol { IsConst: true } field) + return false; + + // See if interpreting the same expression as a type in this location binds. + var potentialType = semanticModel.GetSpeculativeTypeInfo(expression.SpanStart, expression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; + return potentialType is not (null or IErrorTypeSymbol); + } + private static readonly ObjectPool> s_nodeStackPool = SharedPools.Default>(); private static bool RemovalMayIntroduceInterpolationAmbiguity(ParenthesizedExpressionSyntax node) @@ -668,12 +689,7 @@ private static bool IsNextExpressionPotentiallyAmbiguous(ExpressionSyntax node) } private static bool IsSimpleOrDottedName(ExpressionSyntax expression) - { - return expression.IsKind( - SyntaxKind.IdentifierName, - SyntaxKind.QualifiedName, - SyntaxKind.SimpleMemberAccessExpression); - } + => expression.Kind() is SyntaxKind.IdentifierName or SyntaxKind.QualifiedName or SyntaxKind.SimpleMemberAccessExpression; public static bool CanRemoveParentheses(this ParenthesizedPatternSyntax node) { @@ -689,6 +705,11 @@ public static bool CanRemoveParentheses(this ParenthesizedPatternSyntax node) if (pattern is ParenthesizedPatternSyntax) return true; + // We're parenthesized discard pattern. We cannot remove parens. + // x is (_) + if (pattern is DiscardPatternSyntax && node.Parent is IsPatternExpressionSyntax) + return false; + // (not ...) -> not ... // // this is safe because unary patterns have the highest precedence, so even if you had: diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 49724219205b3..b13fd14d2f076 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -21,6 +21,11 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions { internal static partial class SyntaxNodeExtensions { + public static void Deconstruct(this SyntaxNode node, out SyntaxKind kind) + { + kind = node.Kind(); + } + public static bool IsKind([NotNullWhen(returnValue: true)] this SyntaxNode? node, SyntaxKind kind, [NotNullWhen(returnValue: true)] out TNode? result) where TNode : SyntaxNode { @@ -257,8 +262,8 @@ node is UsingStatementSyntax || _ => null, }; - public static BaseParameterListSyntax? GetParameterList(this SyntaxNode declaration) - => declaration.Kind() switch + public static BaseParameterListSyntax? GetParameterList(this SyntaxNode? declaration) + => declaration?.Kind() switch { SyntaxKind.DelegateDeclaration => ((DelegateDeclarationSyntax)declaration).ParameterList, SyntaxKind.MethodDeclaration => ((MethodDeclarationSyntax)declaration).ParameterList, @@ -270,6 +275,7 @@ node is UsingStatementSyntax || SyntaxKind.ParenthesizedLambdaExpression => ((ParenthesizedLambdaExpressionSyntax)declaration).ParameterList, SyntaxKind.LocalFunctionStatement => ((LocalFunctionStatementSyntax)declaration).ParameterList, SyntaxKind.AnonymousMethodExpression => ((AnonymousMethodExpressionSyntax)declaration).ParameterList, + SyntaxKind.RecordDeclaration => ((RecordDeclarationSyntax)declaration).ParameterList, _ => null, }; @@ -933,7 +939,7 @@ public static (SyntaxToken openParen, SyntaxToken closeParen) GetParentheses(thi } } - public static (SyntaxToken openBrace, SyntaxToken closeBrace) GetBrackets(this SyntaxNode node) + public static (SyntaxToken openBracket, SyntaxToken closeBracket) GetBrackets(this SyntaxNode node) { switch (node) { @@ -952,6 +958,8 @@ public static SyntaxTokenList GetModifiers(this SyntaxNode? member) { case MemberDeclarationSyntax memberDecl: return memberDecl.Modifiers; case AccessorDeclarationSyntax accessor: return accessor.Modifiers; + case LocalFunctionStatementSyntax localFunction: return localFunction.Modifiers; + case LocalDeclarationStatementSyntax localDeclaration: return localDeclaration.Modifiers; } return default; @@ -963,6 +971,8 @@ public static SyntaxTokenList GetModifiers(this SyntaxNode? member) { case MemberDeclarationSyntax memberDecl: return memberDecl.WithModifiers(modifiers); case AccessorDeclarationSyntax accessor: return accessor.WithModifiers(modifiers); + case LocalFunctionStatementSyntax localFunction: return localFunction.WithModifiers(modifiers); + case LocalDeclarationStatementSyntax localDeclaration: return localDeclaration.WithModifiers(modifiers); } return null; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxTreeExtensions.cs index b35949920d477..19648cd2fd42f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxTreeExtensions.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -66,13 +65,6 @@ public static ISet GetPrecedingModifiers( continue; } - if (token.HasMatchingText(SyntaxKind.DataKeyword)) - { - result.Add(SyntaxKind.DataKeyword); - token = token.GetPreviousToken(includeSkipped: true); - continue; - } - break; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs index 178c1ae4a89d3..8b74c5262537e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/NewLineUserSettingFormattingRule.cs @@ -126,9 +126,11 @@ private static bool IsControlBlock(SyntaxNode node) } } - // new { - Object Initialization, or with { - Record with initializer + // new { - Object Initialization, or with { - Record with initializer, or is { - property pattern clauses if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && - (currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) || currentToken.Parent.IsKind(SyntaxKind.WithInitializerExpression))) + (currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) || + currentToken.Parent.IsKind(SyntaxKind.WithInitializerExpression) || + currentToken.Parent.IsKind(SyntaxKind.PropertyPatternClause))) { if (!_options.NewLinesForBracesInObjectCollectionArrayInitializers) { @@ -313,10 +315,12 @@ private static bool IsControlBlock(SyntaxNode node) // new MyObject { - Object Initialization // new List { - Collection Initialization // with { - Record with initializer - if (currentToken.Kind() == SyntaxKind.OpenBraceToken && - (currentToken.Parent.Kind() == SyntaxKind.ObjectInitializerExpression || - currentToken.Parent.Kind() == SyntaxKind.CollectionInitializerExpression || - currentToken.Parent.Kind() == SyntaxKind.WithInitializerExpression)) + // is { - property pattern clauses + if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && + (currentToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) || + currentToken.Parent.IsKind(SyntaxKind.CollectionInitializerExpression) || + currentToken.Parent.IsKind(SyntaxKind.WithInitializerExpression) || + currentToken.Parent.IsKind(SyntaxKind.PropertyPatternClause))) { if (_options.NewLinesForBracesInObjectCollectionArrayInitializers) { @@ -437,7 +441,7 @@ private static bool IsControlBlock(SyntaxNode node) } } - // Wrapping - Leave statements on same line (false): + // Wrapping - Leave statements on same line (false): // Insert a newline between the previous statement and this one. // ; * if (previousToken.Kind() == SyntaxKind.SemicolonToken diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 5994a68efeb51..5bdfd4b869e29 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -34,7 +34,7 @@ private CSharpSemanticFacts() public bool ExposesAnonymousFunctionParameterNames => false; public bool IsWrittenTo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) - => (node as ExpressionSyntax).IsWrittenTo(); + => (node as ExpressionSyntax).IsWrittenTo(semanticModel, cancellationToken); public bool IsOnlyWrittenTo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsOnlyWrittenTo(); @@ -60,10 +60,19 @@ public ISymbol GetDeclaredSymbol(SemanticModel semanticModel, SyntaxToken token, var symbol = semanticModel.GetDeclaredSymbol(ancestor, cancellationToken); if (symbol != null) { - // The token may be part of a larger name (for example, `int` in `public static operator int[](Goo g);`. - // So check if the symbol's location encompasses the span of the token we're asking about. - if (symbol.Locations.Any(loc => loc.SourceTree == location.SourceTree && loc.SourceSpan.Contains(location.SourceSpan))) - return symbol; + if (symbol is IMethodSymbol { MethodKind: MethodKind.Conversion }) + { + // The token may be part of a larger name (for example, `int` in `public static operator int[](Goo g);`. + // So check if the symbol's location encompasses the span of the token we're asking about. + if (symbol.Locations.Any(loc => loc.SourceTree == location.SourceTree && loc.SourceSpan.Contains(location.SourceSpan))) + return symbol; + } + else + { + // For any other symbols, we only care if the name directly matches the span of the token + if (symbol.Locations.Contains(location)) + return symbol; + } // We found some symbol, but it defined something else. We're not going to have a higher node defining _another_ symbol with this token, so we can stop now. return null; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index abf316d7a1e17..11e1bd60da9c8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -64,6 +64,9 @@ public bool SupportsLocalFunctionDeclaration(ParseOptions options) public bool SupportsRecord(ParseOptions options) => ((CSharpParseOptions)options).LanguageVersion >= LanguageVersion.CSharp9; + public bool SupportsRecordStruct(ParseOptions options) + => ((CSharpParseOptions)options).LanguageVersion.IsCSharp10OrAbove(); + public SyntaxToken ParseToken(string text) => SyntaxFactory.ParseToken(text); @@ -393,6 +396,9 @@ private static PredefinedOperator GetPredefinedOperator(SyntaxToken token) case SyntaxKind.LessThanLessThanEqualsToken: return PredefinedOperator.LeftShift; + case SyntaxKind.LessThanToken: + return PredefinedOperator.LessThan; + case SyntaxKind.LessThanEqualsToken: return PredefinedOperator.LessThanOrEqual; @@ -705,8 +711,8 @@ public bool IsMemberInitializerNamedAssignmentIdentifier( return false; } - public bool IsElementAccessExpression(SyntaxNode node) - => node.Kind() == SyntaxKind.ElementAccessExpression; + public bool IsElementAccessExpression(SyntaxNode? node) + => node.IsKind(SyntaxKind.ElementAccessExpression); [return: NotNullIfNotNull("node")] public SyntaxNode? ConvertToSingleLine(SyntaxNode? node, bool useElasticTrivia = false) @@ -721,8 +727,8 @@ public void GetPartsOfParenthesizedExpression( closeParen = parenthesizedExpression.CloseParenToken; } - public bool IsIndexerMemberCRef(SyntaxNode node) - => node.Kind() == SyntaxKind.IndexerMemberCref; + public bool IsIndexerMemberCRef(SyntaxNode? node) + => node.IsKind(SyntaxKind.IndexerMemberCref); public SyntaxNode? GetContainingMemberDeclaration(SyntaxNode? root, int position, bool useFullSpan = true) { @@ -1268,6 +1274,12 @@ public SeparatedSyntaxList GetArgumentsOfObjectCreationExpression(Sy public SeparatedSyntaxList GetArgumentsOfArgumentList(SyntaxNode? argumentList) => (argumentList as BaseArgumentListSyntax)?.Arguments ?? default; + public SyntaxNode GetArgumentListOfInvocationExpression(SyntaxNode invocationExpression) + => ((InvocationExpressionSyntax)invocationExpression).ArgumentList; + + public SyntaxNode? GetArgumentListOfObjectCreationExpression(SyntaxNode objectCreationExpression) + => ((ObjectCreationExpressionSyntax)objectCreationExpression)!.ArgumentList; + public bool IsRegularComment(SyntaxTrivia trivia) => trivia.IsRegularComment(); @@ -1402,6 +1414,12 @@ public SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node) public SyntaxToken GetIdentifierOfVariableDeclarator(SyntaxNode node) => ((VariableDeclaratorSyntax)node).Identifier; + public SyntaxToken GetIdentifierOfParameter(SyntaxNode node) + => ((ParameterSyntax)node).Identifier; + + public SyntaxToken GetIdentifierOfIdentifierName(SyntaxNode node) + => ((IdentifierNameSyntax)node).Identifier; + public bool IsLocalFunctionStatement([NotNullWhen(true)] SyntaxNode? node) => node.IsKind(SyntaxKind.LocalFunctionStatement); @@ -1778,6 +1796,7 @@ public override bool CanHaveAccessibility(SyntaxNode declaration) case SyntaxKind.ClassDeclaration: case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: @@ -1909,10 +1928,10 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) switch (declaration.Kind()) { case SyntaxKind.ClassDeclaration: - return DeclarationKind.Class; case SyntaxKind.RecordDeclaration: - return DeclarationKind.RecordClass; + return DeclarationKind.Class; case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return DeclarationKind.Struct; case SyntaxKind.InterfaceDeclaration: return DeclarationKind.Interface; @@ -2044,6 +2063,7 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) case SyntaxKind.GetAccessorDeclaration: return DeclarationKind.GetAccessor; case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: return DeclarationKind.SetAccessor; case SyntaxKind.AddAccessorDeclaration: return DeclarationKind.AddAccessor; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs index c106ad1e93174..967017bf9634c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs @@ -85,10 +85,13 @@ public TSyntaxKind Convert(int kind) where TSyntaxKind : struct public int Parameter => (int)SyntaxKind.Parameter; public int TypeConstraint => (int)SyntaxKind.TypeConstraint; public int VariableDeclarator => (int)SyntaxKind.VariableDeclarator; + public int FieldDeclaration => (int)SyntaxKind.FieldDeclaration; public int ParameterList => (int)SyntaxKind.ParameterList; public int TypeArgumentList => (int)SyntaxKind.TypeArgumentList; public int? GlobalStatement => (int)SyntaxKind.GlobalStatement; + public int EqualsValueClause => (int)SyntaxKind.EqualsValueClause; + public int Interpolation => (int)SyntaxKind.Interpolation; public int InterpolatedStringExpression => (int)SyntaxKind.InterpolatedStringExpression; public int InterpolatedStringText => (int)SyntaxKind.InterpolatedStringText; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/FormattingRangeHelper.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/FormattingRangeHelper.cs index c36091a18099e..36d53ddbc4041 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/FormattingRangeHelper.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/FormattingRangeHelper.cs @@ -372,6 +372,7 @@ private static bool IsSpecialContainingNode(SyntaxNode node) node.Kind() == SyntaxKind.CheckedStatement || node.Kind() == SyntaxKind.GetAccessorDeclaration || node.Kind() == SyntaxKind.SetAccessorDeclaration || + node.Kind() == SyntaxKind.InitAccessorDeclaration || node.Kind() == SyntaxKind.AddAccessorDeclaration || node.Kind() == SyntaxKind.RemoveAccessorDeclaration; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/UsingsAndExternAliasesDirectiveComparer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/UsingsAndExternAliasesDirectiveComparer.cs index 8f268228f72d8..c676afc63beda 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/UsingsAndExternAliasesDirectiveComparer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/UsingsAndExternAliasesDirectiveComparer.cs @@ -35,6 +35,9 @@ private UsingsAndExternAliasesDirectiveComparer( private enum UsingKind { Extern, + GlobalNamespace, + GlobalUsingStatic, + GlobalAlias, Namespace, UsingStatic, Alias @@ -51,15 +54,26 @@ private static UsingKind GetUsingKind(UsingDirectiveSyntax? usingDirective, Exte RoslynDebug.AssertNotNull(usingDirective); } - if (usingDirective.Alias != null) + if (usingDirective.GlobalKeyword != default) { - return UsingKind.Alias; + if (usingDirective.Alias != null) + return UsingKind.GlobalAlias; + + if (usingDirective.StaticKeyword != default) + return UsingKind.GlobalUsingStatic; + + return UsingKind.GlobalNamespace; } - if (usingDirective.StaticKeyword != default) + else { - return UsingKind.UsingStatic; + if (usingDirective.Alias != null) + return UsingKind.Alias; + + if (usingDirective.StaticKeyword != default) + return UsingKind.UsingStatic; + + return UsingKind.Namespace; } - return UsingKind.Namespace; } public int Compare(SyntaxNode? directive1, SyntaxNode? directive2) @@ -70,9 +84,7 @@ public int Compare(SyntaxNode? directive1, SyntaxNode? directive2) return 1; if (directive1 == directive2) - { return 0; - } var using1 = directive1 as UsingDirectiveSyntax; var using2 = directive2 as UsingDirectiveSyntax; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs index ad0da9a5af9ad..7b6500a99088e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs @@ -37,7 +37,7 @@ internal interface ICodeStyleOption : IObjectWritable /// hosts that expect the value to be a boolean. Specifically, if the enum value is 0 or 1 /// then those values will write back as false/true. /// - internal partial class CodeStyleOption2 : ICodeStyleOption, IEquatable?> + internal sealed partial class CodeStyleOption2 : ICodeStyleOption, IEquatable?> { static CodeStyleOption2() { @@ -48,7 +48,7 @@ static CodeStyleOption2() private const int SerializationVersion = 1; - private NotificationOption2 _notification; + private readonly NotificationOption2 _notification; public CodeStyleOption2(T value, NotificationOption2 notification) { @@ -56,7 +56,7 @@ public CodeStyleOption2(T value, NotificationOption2 notification) _notification = notification ?? throw new ArgumentNullException(nameof(notification)); } - public T Value { get; set; } + public T Value { get; } object? ICodeStyleOption.Value => this.Value; ICodeStyleOption ICodeStyleOption.WithValue(object value) => new CodeStyleOption2((T)value, Notification); @@ -75,7 +75,6 @@ ICodeStyleOption ICodeStyleOption.AsCodeStyleOption() public NotificationOption2 Notification { get => _notification; - set => _notification = value ?? throw new ArgumentNullException(nameof(value)); } public XElement ToXElement() => diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs index 157e6f583f586..86675b916d43d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs @@ -182,6 +182,25 @@ public void AddRange(ImmutableArray items) } } + public void AddRange(in TemporaryArray items) + { + if (_count + items.Count <= InlineCapacity) + { + foreach (var item in items) + { + // Increase _count before assigning values since the indexer has a bounds check. + _count++; + this[_count - 1] = item; + } + } + else + { + MoveInlineToBuilder(); + foreach (var item in items) + _builder.Add(item); + } + } + public void Clear() { if (_builder is not null) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 0cd48b4c2c189..faf79cc6b0e29 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -50,11 +50,15 @@ + Collections\ArrayBuilderExtensions.cs + + Collections\DictionaryExtensions.cs + Collections\Boxes.cs @@ -276,7 +280,6 @@ - @@ -313,6 +316,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs index 3b0d62642c9a9..34d3c61dd7f1a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs @@ -73,5 +73,13 @@ public static PredefinedOperator GetPredefinedOperator(this IMethodSymbol symbol "op_Subtraction" or "op_UnaryNegation" => PredefinedOperator.Subtraction, _ => PredefinedOperator.None, }; + + public static bool IsEntryPoint(this IMethodSymbol methodSymbol, INamedTypeSymbol? taskType, INamedTypeSymbol? genericTaskType) + => methodSymbol.Name is WellKnownMemberNames.EntryPointMethodName or WellKnownMemberNames.TopLevelStatementsEntryPointMethodName && + methodSymbol.IsStatic && + (methodSymbol.ReturnsVoid || + methodSymbol.ReturnType.SpecialType == SpecialType.System_Int32 || + methodSymbol.ReturnType.OriginalDefinition.Equals(taskType) || + methodSymbol.ReturnType.OriginalDefinition.Equals(genericTaskType)); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs index 95ab3e5184bc7..db8f0b67019e5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs @@ -240,7 +240,7 @@ public static bool IsOrdinaryMethod([NotNullWhen(returnValue: true)] this ISymbo public static bool IsOrdinaryMethodOrLocalFunction([NotNullWhen(returnValue: true)] this ISymbol? symbol) { - if (!(symbol is IMethodSymbol method)) + if (symbol is not IMethodSymbol method) { return false; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs index 7a924e1fec556..07338bafacab2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs @@ -24,41 +24,6 @@ public static ImmutableArray ToImmutableArrayOrEmpty(this T[]? items) return ImmutableArray.Create(items); } - public static ImmutableArray ToImmutableArrayOrEmpty(this IEnumerable? items) - { - if (items == null) - { - return ImmutableArray.Create(); - } - - if (items is ImmutableArray array) - { - return array.NullToEmpty(); - } - - return ImmutableArray.CreateRange(items); - } - - public static IReadOnlyList ToBoxedImmutableArray(this IEnumerable? items) - { - if (items is null) - { - return SpecializedCollections.EmptyBoxedImmutableArray(); - } - - if (items is ImmutableArray array) - { - return array.IsDefaultOrEmpty ? SpecializedCollections.EmptyBoxedImmutableArray() : (IReadOnlyList)items; - } - - if (items is ICollection collection && collection.Count == 0) - { - return SpecializedCollections.EmptyBoxedImmutableArray(); - } - - return ImmutableArray.CreateRange(items); - } - public static ConcatImmutableArray ConcatFast(this ImmutableArray first, ImmutableArray second) => new(first, second); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs index c8649fda48558..3e3a9dc2fbfe0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs @@ -104,6 +104,18 @@ private static string GetEndOfLineEditorConfigString(string option) internal static readonly PerLanguageOption2 AutoFormattingOnReturn = CreatePerLanguageOption(OptionGroup.Default, nameof(AutoFormattingOnReturn), defaultValue: true, storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Return")); + public static readonly PerLanguageOption2 AutoFormattingOnTyping = CreatePerLanguageOption( + OptionGroup.Default, nameof(AutoFormattingOnTyping), defaultValue: true, + storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Typing")); + + public static readonly PerLanguageOption2 AutoFormattingOnSemicolon = CreatePerLanguageOption( + OptionGroup.Default, nameof(AutoFormattingOnSemicolon), defaultValue: true, + storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Semicolon")); + + public static readonly PerLanguageOption2 FormatOnPaste = CreatePerLanguageOption( + OptionGroup.Default, nameof(FormatOnPaste), defaultValue: true, + storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.FormatOnPaste")); + static FormattingOptions2() { // Note that the static constructor executes after all the static field initializers for the options have executed, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ListPool.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ListPool.cs deleted file mode 100644 index 5c8b156bd9127..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ListPool.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.Formatting -{ - internal static class ListPool - { - public static List Allocate() - => SharedPools.Default>().AllocateAndClear(); - - public static void Free(List list) - => SharedPools.Default>().ClearAndFree(list); - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index dc5ce56cbc42c..138d11391b46d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -84,8 +84,8 @@ internal enum FunctionId Workspace_ApplyChanges = 62, Workspace_TryGetDocument = 63, Workspace_TryGetDocumentFromInProgressSolution = 64, - Workspace_Solution_LinkedFileDiffMergingSession = 65, - Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup = 66, + // obsolete: Workspace_Solution_LinkedFileDiffMergingSession = 65, + // obsolete: Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup = 66, Workspace_Solution_Info = 67, EndConstruct_DoStatement = 68, @@ -287,7 +287,7 @@ internal enum FunctionId DiagnosticAnalyzerService_Analyzers = 230, DiagnosticAnalyzerDriver_AnalyzerCrash = 231, DiagnosticAnalyzerDriver_AnalyzerTypeCount = 232, - PersistedSemanticVersion_Info = 233, + // obsolete: PersistedSemanticVersion_Info = 233, StorageDatabase_Exceptions = 234, WorkCoordinator_ShutdownTimeout = 235, Diagnostics_HyperLink = 236, @@ -412,7 +412,7 @@ internal enum FunctionId Extension_InfoBar = 327, FxCopAnalyzersInstall = 328, AssetStorage_ForceGC = 329, - RemoteHost_Bitness = 330, + // obsolete: RemoteHost_Bitness = 330, Intellisense_Completion = 331, MetadataOnlyImage_EmitFailure = 332, LiveTableDataSource_OnDiagnosticsUpdated = 333, @@ -421,9 +421,9 @@ internal enum FunctionId Diagnostics_BadAnalyzer = 336, CodeAnalysisService_ReportAnalyzerPerformance = 337, PerformanceTrackerService_AddSnapshot = 338, - AbstractProject_SetIntelliSenseBuild = 339, - AbstractProject_Created = 340, - AbstractProject_PushedToWorkspace = 341, + // obsolete: AbstractProject_SetIntelliSenseBuild = 339, + // obsolete: AbstractProject_Created = 340, + // obsolete: AbstractProject_PushedToWorkspace = 341, ExternalErrorDiagnosticUpdateSource_AddError = 342, DiagnosticIncrementalAnalyzer_SynchronizeWithBuildAsync = 343, Completion_ExecuteCommand_TypeChar = 344, @@ -431,7 +431,7 @@ internal enum FunctionId SymbolFinder_Solution_Pattern_FindSourceDeclarationsAsync = 346, SymbolFinder_Project_Pattern_FindSourceDeclarationsAsync = 347, - Intellisense_Completion_Commit = 348, + // obsolete: Intellisense_Completion_Commit = 348, CodeCleanupInfobar_BarDisplayed = 349, CodeCleanupInfobar_ConfigureNow = 350, @@ -464,9 +464,9 @@ internal enum FunctionId RemoteHostService_IsExperimentEnabledAsync = 373, PartialLoad_FullyLoaded = 374, Liveshare_UnknownCodeAction = 375, - Liveshare_LexicalClassifications = 376, - Liveshare_SyntacticClassifications = 377, - Liveshare_SyntacticTagger = 378, + // obsolete: Liveshare_LexicalClassifications = 376, + // obsolete: Liveshare_SyntacticClassifications = 377, + // obsolete: Liveshare_SyntacticTagger = 378, CommandHandler_GoToBase = 379, @@ -514,5 +514,10 @@ internal enum FunctionId Intellicode_UnknownIntent = 485, LSP_CompletionListCacheMiss = 486, + + InheritanceMargin_TargetsMenuOpen = 487, + InheritanceMargin_NavigateToTarget = 488, + + VS_ErrorReportingService_ShowGlobalErrorInfo = 489, } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogLevel.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogLevel.cs new file mode 100644 index 0000000000000..a7847b5d0b94f --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogLevel.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Internal.Log +{ + /// + /// Defines logging severity levels. Each logger may choose to report differently based on the level of the message being logged. + /// + /// Copied from Microsoft.Extensions.Logging https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel + /// + /// + internal enum LogLevel + { + /// + /// Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are disabled by default and should never be enabled in a production environment. + /// + Trace = 0, + + /// + /// Logs that are used for interactive investigation during development. These logs should primarily contain information useful for debugging and have no long-term value. + /// + Debug = 1, + + /// + /// Logs that track the general flow of the application. These logs should have long-term value. + /// + Information = 2, + + /// + /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop. + /// + Warning = 3, + + /// + /// Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure in the current activity, not an application-wide failure. + /// + Error = 4, + + /// + /// Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate attention. + /// + Critical = 5, + + /// + /// Not used for writing log messages. Specifies that a logging category should not write any messages. + /// + None = 6 + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogMessage.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogMessage.cs index d6f278401ef11..ab5fed9825199 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogMessage.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/LogMessage.cs @@ -12,23 +12,25 @@ namespace Microsoft.CodeAnalysis.Internal.Log /// internal abstract class LogMessage { - public static LogMessage Create(string message) - => StaticLogMessage.Construct(message); + public LogLevel LogLevel { get; protected set; } = LogLevel.Debug; - public static LogMessage Create(Func messageGetter) - => LazyLogMessage.Construct(messageGetter); + public static LogMessage Create(string message, LogLevel logLevel) + => StaticLogMessage.Construct(message, logLevel); - public static LogMessage Create(Func messageGetter, TArg arg) - => LazyLogMessage.Construct(messageGetter, arg); + public static LogMessage Create(Func messageGetter, LogLevel logLevel) + => LazyLogMessage.Construct(messageGetter, logLevel); - public static LogMessage Create(Func messageGetter, TArg0 arg0, TArg1 arg1) - => LazyLogMessage.Construct(messageGetter, arg0, arg1); + public static LogMessage Create(Func messageGetter, TArg arg, LogLevel logLevel) + => LazyLogMessage.Construct(messageGetter, arg, logLevel); - public static LogMessage Create(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2) - => LazyLogMessage.Construct(messageGetter, arg0, arg1, arg2); + public static LogMessage Create(Func messageGetter, TArg0 arg0, TArg1 arg1, LogLevel logLevel) + => LazyLogMessage.Construct(messageGetter, arg0, arg1, logLevel); - public static LogMessage Create(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3) - => LazyLogMessage.Construct(messageGetter, arg0, arg1, arg2, arg3); + public static LogMessage Create(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, LogLevel logLevel) + => LazyLogMessage.Construct(messageGetter, arg0, arg1, arg2, logLevel); + + public static LogMessage Create(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, LogLevel logLevel) + => LazyLogMessage.Construct(messageGetter, arg0, arg1, arg2, arg3, logLevel); // message will be either initially set or lazily set by caller private string? _message; @@ -61,10 +63,11 @@ private sealed class StaticLogMessage : LogMessage { private static readonly ObjectPool s_pool = SharedPools.Default(); - public static LogMessage Construct(string message) + public static LogMessage Construct(string message, LogLevel logLevel) { var logMessage = s_pool.Allocate(); logMessage._message = message; + logMessage.LogLevel = logLevel; return logMessage; } @@ -90,10 +93,11 @@ private sealed class LazyLogMessage : LogMessage private Func? _messageGetter; - public static LogMessage Construct(Func messageGetter) + public static LogMessage Construct(Func messageGetter, LogLevel logLevel) { var logMessage = s_pool.Allocate(); logMessage._messageGetter = messageGetter; + logMessage.LogLevel = logLevel; return logMessage; } @@ -120,11 +124,12 @@ private sealed class LazyLogMessage : LogMessage private Func? _messageGetter; private TArg0? _arg; - public static LogMessage Construct(Func messageGetter, TArg0 arg) + public static LogMessage Construct(Func messageGetter, TArg0 arg, LogLevel logLevel) { var logMessage = s_pool.Allocate(); logMessage._messageGetter = messageGetter; logMessage._arg = arg; + logMessage.LogLevel = logLevel; return logMessage; } @@ -153,12 +158,13 @@ private sealed class LazyLogMessage : LogMessage private TArg0? _arg0; private TArg1? _arg1; - internal static LogMessage Construct(Func messageGetter, TArg0 arg0, TArg1 arg1) + internal static LogMessage Construct(Func messageGetter, TArg0 arg0, TArg1 arg1, LogLevel logLevel) { var logMessage = s_pool.Allocate(); logMessage._messageGetter = messageGetter; logMessage._arg0 = arg0; logMessage._arg1 = arg1; + logMessage.LogLevel = logLevel; return logMessage; } @@ -189,13 +195,14 @@ private sealed class LazyLogMessage : LogMessage private TArg1? _arg1; private TArg2? _arg2; - public static LogMessage Construct(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2) + public static LogMessage Construct(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, LogLevel logLevel) { var logMessage = s_pool.Allocate(); logMessage._messageGetter = messageGetter; logMessage._arg0 = arg0; logMessage._arg1 = arg1; logMessage._arg2 = arg2; + logMessage.LogLevel = logLevel; return logMessage; } @@ -228,7 +235,7 @@ private sealed class LazyLogMessage : LogMessage private TArg2? _arg2; private TArg3? _arg3; - public static LogMessage Construct(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3) + public static LogMessage Construct(Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, LogLevel logLevel) { var logMessage = s_pool.Allocate(); logMessage._messageGetter = messageGetter; @@ -236,6 +243,7 @@ public static LogMessage Construct(Func mess logMessage._arg1 = arg1; logMessage._arg2 = arg2; logMessage._arg3 = arg3; + logMessage.LogLevel = logLevel; return logMessage; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs index 25b9961eb2d61..d9c423c7bf8ca 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs @@ -42,7 +42,7 @@ internal static partial class Logger /// /// log a specific event with a simple context message which should be very cheap to create /// - public static void Log(FunctionId functionId, string? message = null) + public static void Log(FunctionId functionId, string? message = null, LogLevel logLevel = LogLevel.Debug) { var logger = GetLogger(); if (logger == null) @@ -55,14 +55,14 @@ public static void Log(FunctionId functionId, string? message = null) return; } - logger.Log(functionId, LogMessage.Create(message ?? "")); + logger.Log(functionId, LogMessage.Create(message ?? "", logLevel: logLevel)); } /// /// log a specific event with a context message that will only be created when it is needed. /// the messageGetter should be cheap to create. in another word, it shouldn't capture any locals /// - public static void Log(FunctionId functionId, Func messageGetter) + public static void Log(FunctionId functionId, Func messageGetter, LogLevel logLevel = LogLevel.Debug) { var logger = GetLogger(); if (logger == null) @@ -75,7 +75,7 @@ public static void Log(FunctionId functionId, Func messageGetter) return; } - var logMessage = LogMessage.Create(messageGetter); + var logMessage = LogMessage.Create(messageGetter, logLevel); logger.Log(functionId, logMessage); logMessage.Free(); @@ -85,7 +85,7 @@ public static void Log(FunctionId functionId, Func messageGetter) /// log a specific event with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static void Log(FunctionId functionId, Func messageGetter, TArg arg) + public static void Log(FunctionId functionId, Func messageGetter, TArg arg, LogLevel logLevel = LogLevel.Debug) { var logger = GetLogger(); if (logger == null) @@ -98,7 +98,7 @@ public static void Log(FunctionId functionId, Func messageGe return; } - var logMessage = LogMessage.Create(messageGetter, arg); + var logMessage = LogMessage.Create(messageGetter, arg, logLevel); logger.Log(functionId, logMessage); logMessage.Free(); } @@ -107,7 +107,7 @@ public static void Log(FunctionId functionId, Func messageGe /// log a specific event with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1) + public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, LogLevel logLevel = LogLevel.Debug) { var logger = GetLogger(); if (logger == null) @@ -120,7 +120,7 @@ public static void Log(FunctionId functionId, Func(FunctionId functionId, Func - public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2) + public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, LogLevel logLevel = LogLevel.Debug) { var logger = GetLogger(); if (logger == null) @@ -142,7 +142,7 @@ public static void Log(FunctionId functionId, Func(FunctionId functionId, Func - public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3) + public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, LogLevel logLevel = LogLevel.Debug) { var logger = GetLogger(); if (logger == null) @@ -164,7 +164,7 @@ public static void Log(FunctionId functionId, Func /// simplest way to log a start and end pair with a simple context message which should be very cheap to create /// - public static IDisposable LogBlock(FunctionId functionId, string? message, CancellationToken token) + public static IDisposable LogBlock(FunctionId functionId, string? message, CancellationToken token, LogLevel logLevel = LogLevel.Trace) { var logger = GetLogger(); if (logger == null) @@ -217,14 +217,14 @@ public static IDisposable LogBlock(FunctionId functionId, string? message, Cance return EmptyLogBlock.Instance; } - return CreateLogBlock(functionId, LogMessage.Create(message ?? ""), GetNextUniqueBlockId(), token); + return CreateLogBlock(functionId, LogMessage.Create(message ?? "", logLevel), GetNextUniqueBlockId(), token); } /// /// log a start and end pair with a context message that will only be created when it is needed. /// the messageGetter should be cheap to create. in another word, it shouldn't capture any locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, CancellationToken token) + public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, CancellationToken token, LogLevel logLevel = LogLevel.Trace) { var logger = GetLogger(); if (logger == null) @@ -237,14 +237,14 @@ public static IDisposable LogBlock(FunctionId functionId, Func messageGe return EmptyLogBlock.Instance; } - return CreateLogBlock(functionId, LogMessage.Create(messageGetter), GetNextUniqueBlockId(), token); + return CreateLogBlock(functionId, LogMessage.Create(messageGetter, logLevel), GetNextUniqueBlockId(), token); } /// /// log a start and end pair with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg arg, CancellationToken token) + public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg arg, CancellationToken token, LogLevel logLevel = LogLevel.Trace) { var logger = GetLogger(); if (logger == null) @@ -257,14 +257,14 @@ public static IDisposable LogBlock(FunctionId functionId, Func /// log a start and end pair with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, CancellationToken token) + public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, CancellationToken token, LogLevel logLevel = LogLevel.Trace) { var logger = GetLogger(); if (logger == null) @@ -277,14 +277,14 @@ public static IDisposable LogBlock(FunctionId functionId, Func /// log a start and end pair with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, CancellationToken token) + public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, CancellationToken token, LogLevel logLevel = LogLevel.Trace) { var logger = GetLogger(); if (logger == null) @@ -297,14 +297,14 @@ public static IDisposable LogBlock(FunctionId functionId, F return EmptyLogBlock.Instance; } - return CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2), GetNextUniqueBlockId(), token); + return CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, logLevel), GetNextUniqueBlockId(), token); } /// /// log a start and end pair with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, CancellationToken token) + public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, CancellationToken token, LogLevel logLevel = LogLevel.Trace) { var logger = GetLogger(); if (logger == null) @@ -317,7 +317,7 @@ public static IDisposable LogBlock(FunctionId functi return EmptyLogBlock.Instance; } - return CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, arg3), GetNextUniqueBlockId(), token); + return CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, arg3, logLevel), GetNextUniqueBlockId(), token); } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyle.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyle.cs index bcd577f1f920b..b4ff771f2268b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyle.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyle.cs @@ -12,6 +12,7 @@ using System.Xml.Linq; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -431,7 +432,8 @@ private string FinishFixingName(string name) if (words.Count() == 1) // Only Split if words have not been split before { var isWord = true; - var parts = StringBreaker.GetParts(name, isWord); + using var parts = TemporaryArray.Empty; + StringBreaker.AddParts(name, isWord, ref parts.AsRef()); var newWords = new string[parts.Count]; for (var i = 0; i < parts.Count; i++) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index b4432f9402954..e20d223b20a6e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -31,6 +31,7 @@ internal interface ISyntaxFacts bool SupportsLocalFunctionDeclaration(ParseOptions options); bool SupportsNotPattern(ParseOptions options); bool SupportsRecord(ParseOptions options); + bool SupportsRecordStruct(ParseOptions options); bool SupportsThrowExpression(ParseOptions options); SyntaxToken ParseToken(string text); @@ -148,6 +149,7 @@ void GetPartsOfTupleExpression(SyntaxNode node, void GetPartsOfInterpolationExpression(SyntaxNode node, out SyntaxToken stringStartToken, out SyntaxList contents, out SyntaxToken stringEndToken); + bool IsVerbatimInterpolatedStringExpression(SyntaxNode node); SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node); @@ -255,7 +257,9 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, SyntaxToken GetIdentifierOfGenericName(SyntaxNode? node); SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node); + SyntaxToken GetIdentifierOfParameter(SyntaxNode node); SyntaxToken GetIdentifierOfVariableDeclarator(SyntaxNode node); + SyntaxToken GetIdentifierOfIdentifierName(SyntaxNode node); SyntaxNode GetTypeOfVariableDeclarator(SyntaxNode node); /// @@ -274,6 +278,8 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, SeparatedSyntaxList GetArgumentsOfInvocationExpression(SyntaxNode? node); SeparatedSyntaxList GetArgumentsOfObjectCreationExpression(SyntaxNode? node); SeparatedSyntaxList GetArgumentsOfArgumentList(SyntaxNode? node); + SyntaxNode GetArgumentListOfInvocationExpression(SyntaxNode node); + SyntaxNode? GetArgumentListOfObjectCreationExpression(SyntaxNode node); bool IsUsingDirectiveName([NotNullWhen(true)] SyntaxNode? node); @@ -318,8 +324,8 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, bool IsBaseConstructorInitializer(SyntaxToken token); bool IsQueryKeyword(SyntaxToken token); bool IsThrowExpression(SyntaxNode node); - bool IsElementAccessExpression(SyntaxNode node); - bool IsIndexerMemberCRef(SyntaxNode node); + bool IsElementAccessExpression([NotNullWhen(true)] SyntaxNode? node); + bool IsIndexerMemberCRef([NotNullWhen(true)] SyntaxNode? node); bool IsIdentifierStartCharacter(char c); bool IsIdentifierPartCharacter(char c); bool IsIdentifierEscapeCharacter(char c); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs index b3a6c35623adf..87d43d2514f41 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs @@ -48,7 +48,7 @@ public static bool IsWord(this ISyntaxFacts syntaxFacts, SyntaxToken token) } public static bool IsAnyMemberAccessExpression( - this ISyntaxFacts syntaxFacts, SyntaxNode node) + this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) { return syntaxFacts.IsSimpleMemberAccessExpression(node) || syntaxFacts.IsPointerMemberAccessExpression(node); } @@ -365,7 +365,7 @@ public static bool IsTrueLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNu #endregion - #region + #region expressions public static bool IsAwaitExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) => node?.RawKind == syntaxFacts.SyntaxKinds.AwaitExpression; @@ -462,11 +462,21 @@ public static bool IsTypeConstraint(this ISyntaxFacts syntaxFacts, [NotNullWhen( public static bool IsVariableDeclarator(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) => node?.RawKind == syntaxFacts.SyntaxKinds.VariableDeclarator; + public static bool IsFieldDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) + => node?.RawKind == syntaxFacts.SyntaxKinds.FieldDeclaration; + public static bool IsTypeArgumentList(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) => node?.RawKind == syntaxFacts.SyntaxKinds.TypeArgumentList; #endregion + #region clauses + + public static bool IsEqualsValueClause(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) + => node?.RawKind == syntaxFacts.SyntaxKinds.EqualsValueClause; + + #endregion + #endregion } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs index faa395706c265..48e85806e42cd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs @@ -127,10 +127,18 @@ internal interface ISyntaxKinds int Parameter { get; } int TypeConstraint { get; } int VariableDeclarator { get; } + int FieldDeclaration { get; } int IncompleteMember { get; } int TypeArgumentList { get; } int ParameterList { get; } + + #endregion + + #region clauses + + int EqualsValueClause { get; } + #endregion #region other diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs index 08c56912e7124..de6a6120dd08b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs @@ -22,11 +22,11 @@ internal static class Contract /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value) + public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0) { if (value is null) { - Fail("Unexpected null"); + Fail("Unexpected null", lineNumber); } } @@ -35,11 +35,11 @@ public static void ThrowIfNull([NotNull] T value) /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, string message) + public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0) { if (value is null) { - Fail(message); + Fail(message, lineNumber); } } @@ -48,11 +48,11 @@ public static void ThrowIfNull([NotNull] T value, string message) /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0) { if (!condition) { - Fail("Unexpected false"); + Fail("Unexpected false", lineNumber); } } @@ -61,11 +61,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) { if (!condition) { - Fail(message); + Fail(message, lineNumber); } } @@ -74,11 +74,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0) { if (condition) { - Fail("Unexpected true"); + Fail("Unexpected true", lineNumber); } } @@ -87,18 +87,18 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) { if (condition) { - Fail(message); + Fail(message, lineNumber); } } [DebuggerHidden] [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void Fail(string message = "Unexpected") - => throw new InvalidOperationException(message); + public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0) + => throw new InvalidOperationException($"{message} - line {lineNumber}"); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs index 3692557caed59..89961784b19cf 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs @@ -53,6 +53,15 @@ public static ImmutableDictionary> ToImmutableMultiDictiona return result.ToImmutable(); } + public static void FreeValues(this IReadOnlyDictionary> builders) + where K : notnull + { + foreach (var (_, items) in builders) + { + items.Free(); + } + } + public static ImmutableArray ToFlattenedImmutableArrayAndFree(this ArrayBuilder> builders) { try diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringBreaker.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringBreaker.cs index 9662636e90f8e..58bead3ec4317 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringBreaker.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringBreaker.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Shared.Utilities @@ -13,15 +13,14 @@ internal static class StringBreaker /// /// Breaks an identifier string into constituent parts. /// - public static ArrayBuilder GetWordParts(string identifier) - => GetParts(identifier, word: true); + public static void AddWordParts(string identifier, ref TemporaryArray parts) + => AddParts(identifier, word: true, ref parts); - public static ArrayBuilder GetCharacterParts(string identifier) - => GetParts(identifier, word: false); + public static void AddCharacterParts(string identifier, ref TemporaryArray parts) + => AddParts(identifier, word: false, ref parts); - public static ArrayBuilder GetParts(string text, bool word) + public static void AddParts(string text, bool word, ref TemporaryArray parts) { - var parts = ArrayBuilder.GetInstance(); for (var start = 0; start < text.Length;) { var span = StringBreaker.GenerateSpan(text, start, word); @@ -36,8 +35,6 @@ public static ArrayBuilder GetParts(string text, bool word) parts.Add(span); start = span.End; } - - return parts; } public static TextSpan GenerateSpan(string identifier, int wordStart, bool word) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs index 2b7e6ffe74b06..c03064717b8a1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/TaskExtensions.cs @@ -97,17 +97,6 @@ bool continuationFunction(Task antecedent) return task.SafeContinueWith(continuationFunction, cancellationToken, continuationOptions, scheduler); } - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task SafeContinueWith( - this Task task, - Func, TResult> continuationFunction, - CancellationToken cancellationToken, - TaskScheduler scheduler) - { - return SafeContinueWith( - task, continuationFunction, cancellationToken, TaskContinuationOptions.None, scheduler); - } - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] public static Task SafeContinueWith( this Task task, @@ -180,16 +169,6 @@ TResult outerFunction(Task t) return task.ContinueWith(outerFunction, cancellationToken, continuationOptions | TaskContinuationOptions.LazyCancellation, scheduler); } - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task SafeContinueWith( - this Task task, - Func continuationFunction, - CancellationToken cancellationToken, - TaskScheduler scheduler) - { - return task.SafeContinueWith(continuationFunction, cancellationToken, TaskContinuationOptions.None, scheduler); - } - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] public static Task SafeContinueWith( this Task task, @@ -199,24 +178,6 @@ public static Task SafeContinueWith( return task.SafeContinueWith(continuationAction, CancellationToken.None, TaskContinuationOptions.None, scheduler); } - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task SafeContinueWith( - this Task task, - Action> continuationFunction, - TaskScheduler scheduler) - { - return task.SafeContinueWith(continuationFunction, CancellationToken.None, TaskContinuationOptions.None, scheduler); - } - - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task SafeContinueWith( - this Task task, - Func, TResult> continuationFunction, - TaskScheduler scheduler) - { - return task.SafeContinueWith(continuationFunction, CancellationToken.None, TaskContinuationOptions.None, scheduler); - } - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] public static Task SafeContinueWith( this Task task, @@ -227,75 +188,6 @@ public static Task SafeContinueWith( return task.SafeContinueWith(continuationAction, cancellationToken, TaskContinuationOptions.None, scheduler); } - // Code provided by Stephen Toub. - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task ContinueWithAfterDelay( - this Task task, - Func, TResult> continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWith( - _ => continuationFunction(t), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task ContinueWithAfterDelay( - this Task task, - Func continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWith( - _ => continuationFunction(), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task ContinueWithAfterDelay( - this Task task, - Func continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWith( - _ => continuationFunction(t), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - public static Task ContinueWithAfterDelay( - this Task task, - Action continuationAction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationAction, nameof(continuationAction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWith( - _ => continuationAction(), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - public static Task SafeContinueWithFromAsync( this Task task, Func, Task> continuationFunction, @@ -383,15 +275,6 @@ public static Task SafeContinueWithFromAsync( return nextTask; } - public static Task SafeContinueWithFromAsync( - this Task task, - Func, Task> continuationFunction, - CancellationToken cancellationToken, - TaskScheduler scheduler) - { - return task.SafeContinueWithFromAsync(continuationFunction, cancellationToken, TaskContinuationOptions.None, scheduler); - } - public static Task SafeContinueWithFromAsync( this Task task, Func, Task> continuationFunction, @@ -422,54 +305,6 @@ public static Task SafeContinueWithFromAsync( return nextTask; } - public static Task ContinueWithAfterDelayFromAsync( - this Task task, - Func> continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWithFromAsync( - _ => continuationFunction(), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - - public static Task ContinueWithAfterDelayFromAsync( - this Task task, - Func> continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWithFromAsync( - _ => continuationFunction(t), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - - public static Task ContinueWithAfterDelayFromAsync( - this Task task, - Func continuationFunction, - CancellationToken cancellationToken, - int millisecondsDelay, - TaskContinuationOptions taskContinuationOptions, - TaskScheduler scheduler) - { - Contract.ThrowIfNull(continuationFunction, nameof(continuationFunction)); - - return task.SafeContinueWith(t => - Task.Delay(millisecondsDelay, cancellationToken).SafeContinueWithFromAsync( - _ => continuationFunction(), cancellationToken, TaskContinuationOptions.None, scheduler), - cancellationToken, taskContinuationOptions, scheduler).Unwrap(); - } - public static Task ContinueWithAfterDelayFromAsync( this Task task, Func continuationFunction, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.cs.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.cs.xlf index fb699716494e7..86531d5a2b7cc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.cs.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Předvolby nových řádků diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.de.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.de.xlf index fbddfe4b5c24b..72938cf574b65 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.de.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.de.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Einstellungen für neue Zeilen diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.es.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.es.xlf index 89cc4d7e16d24..231b5678f5c66 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.es.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.es.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Nuevas preferencias de línea diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.fr.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.fr.xlf index 1b28ef82f4c95..dbaaf7bdfcbb5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.fr.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Préférences de nouvelle ligne diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.it.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.it.xlf index 7161e5a0fedb2..f8d477b2f5916 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.it.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.it.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Preferenze per nuova riga diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ja.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ja.xlf index 467434eb8a581..ce319723cc813 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ja.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + 改行設定 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ko.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ko.xlf index d7ba8b76e23d2..cf949f923fd6d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ko.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + 새 줄 기본 설정 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pl.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pl.xlf index d30ba71d57268..2ae80ae3b0797 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pl.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Preferencje nowego wiersza diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pt-BR.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pt-BR.xlf index 5544196d56c29..8e6cb125f584d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pt-BR.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Preferências de nova linha diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ru.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ru.xlf index 073faf21b8ffb..2cc7754733cf1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ru.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Предпочтения для новых строк diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.tr.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.tr.xlf index 1bfd27afe0aa6..e72af78bf4530 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.tr.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + Yeni satır tercihleri diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hans.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hans.xlf index ca8e08f2582cc..c9569239383ed 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hans.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + 新行首选项 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hant.xlf b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hant.xlf index cde9f2c960232..ef12873d85c26 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hant.xlf +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/xlf/CompilerExtensionsResources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -99,7 +99,7 @@ New line preferences - New line preferences + 新行喜好設定 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb index 43be5115ca79b..4447aa8c2cdaf 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb @@ -50,6 +50,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeStyle "visual_basic_style_prefer_isnot_expression", $"TextEditor.%LANGUAGE%.Specific.{NameOf(PreferIsNotExpression)}") + Public Shared ReadOnly PreferSimplifiedObjectCreation As Option2(Of CodeStyleOption2(Of Boolean)) = CreateOption( + VisualBasicCodeStyleOptionGroups.ExpressionLevelPreferences, NameOf(PreferSimplifiedObjectCreation), + defaultValue:=New CodeStyleOption2(Of Boolean)(True, NotificationOption2.Suggestion), + "visual_basic_style_prefer_simplified_object_creation", + $"TextEditor.%LANGUAGE%.Specific.{NameOf(PreferSimplifiedObjectCreation)}") + Public Shared ReadOnly UnusedValueExpressionStatement As [Option2](Of CodeStyleOption2(Of UnusedValuePreference)) = CodeStyleHelpers.CreateUnusedExpressionAssignmentOption( group:=VisualBasicCodeStyleOptionGroups.ExpressionLevelPreferences, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb index 2dfec57f06b7f..a0d382b89bdd6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb @@ -315,6 +315,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Public Function IsWrittenTo(expression As ExpressionSyntax, semanticModel As SemanticModel, cancellationToken As CancellationToken) As Boolean + If expression Is Nothing Then + Return False + End If + If IsOnlyWrittenTo(expression) Then Return True End If @@ -339,6 +343,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return True End If + ' Extension method with a 'ref' parameter can write to the value it is called on. + If TypeOf expression.Parent Is MemberAccessExpressionSyntax Then + Dim memberAccess = DirectCast(expression.Parent, MemberAccessExpressionSyntax) + If memberAccess.Expression Is expression Then + Dim method = TryCast(semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol, IMethodSymbol) + If method IsNot Nothing Then + If method.MethodKind = MethodKind.ReducedExtension AndAlso + method.ReducedFrom.Parameters.Length > 0 AndAlso + method.ReducedFrom.Parameters.First().RefKind = RefKind.Ref Then + + Return True + End If + End If + End If + End If + Return False End If diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb index 46ef39df5b8dd..c04d8c8d15650 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb @@ -1239,5 +1239,50 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return Nothing End Select End Function + + ''' + ''' If "node" is the begin statement of a declaration block, return that block, otherwise + ''' return node. + ''' + + Public Function GetBlockFromBegin(node As SyntaxNode) As SyntaxNode + Dim parent As SyntaxNode = node.Parent + Dim begin As SyntaxNode = Nothing + + If parent IsNot Nothing Then + Select Case parent.Kind + Case SyntaxKind.NamespaceBlock + begin = DirectCast(parent, NamespaceBlockSyntax).NamespaceStatement + + Case SyntaxKind.ModuleBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ClassBlock + begin = DirectCast(parent, TypeBlockSyntax).BlockStatement + + Case SyntaxKind.EnumBlock + begin = DirectCast(parent, EnumBlockSyntax).EnumStatement + + Case SyntaxKind.SubBlock, SyntaxKind.FunctionBlock, SyntaxKind.ConstructorBlock, + SyntaxKind.OperatorBlock, SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, + SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock, SyntaxKind.RaiseEventAccessorBlock + begin = DirectCast(parent, MethodBlockBaseSyntax).BlockStatement + + Case SyntaxKind.PropertyBlock + begin = DirectCast(parent, PropertyBlockSyntax).PropertyStatement + + Case SyntaxKind.EventBlock + begin = DirectCast(parent, EventBlockSyntax).EventStatement + + Case SyntaxKind.VariableDeclarator + If DirectCast(parent, VariableDeclaratorSyntax).Names.Count = 1 Then + begin = node + End If + End Select + End If + + If begin Is node Then + Return parent + Else + Return node + End If + End Function End Module End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index b82a633089694..9a6dfb6265fca 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -76,6 +76,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Return False End Function + Public Function SupportsRecordStruct(options As ParseOptions) As Boolean Implements ISyntaxFacts.SupportsRecordStruct + Return False + End Function + Public Function ParseToken(text As String) As SyntaxToken Implements ISyntaxFacts.ParseToken Return SyntaxFactory.ParseToken(text, startStatement:=True) End Function @@ -1297,6 +1301,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Return If(arguments.HasValue, arguments.Value, Nothing) End Function + Public Function GetArgumentListOfInvocationExpression(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetArgumentListOfInvocationExpression + Return DirectCast(node, InvocationExpressionSyntax).ArgumentList + End Function + + Public Function GetArgumentListOfObjectCreationExpression(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetArgumentListOfObjectCreationExpression + Return DirectCast(node, ObjectCreationExpressionSyntax).ArgumentList + End Function + Public Function ConvertToSingleLine(node As SyntaxNode, Optional useElasticTrivia As Boolean = False) As SyntaxNode Implements ISyntaxFacts.ConvertToSingleLine Return node.ConvertToSingleLine(useElasticTrivia) End Function @@ -1457,6 +1469,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Return DirectCast(node, VariableDeclaratorSyntax).Names.Last().Identifier End Function + Public Function GetIdentifierOfParameter(node As SyntaxNode) As SyntaxToken Implements ISyntaxFacts.GetIdentifierOfParameter + Return DirectCast(node, ParameterSyntax).Identifier.Identifier + End Function + + Public Function GetIdentifierOfIdentifierName(node As SyntaxNode) As SyntaxToken Implements ISyntaxFacts.GetIdentifierOfIdentifierName + Return DirectCast(node, IdentifierNameSyntax).Identifier + End Function + Public Function IsLocalFunctionStatement(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsLocalFunctionStatement ' VB does not have local functions Return False diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb index d5b9f15f94747..9b8666bb8cb5f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb @@ -87,10 +87,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Public ReadOnly Property Parameter As Integer = SyntaxKind.Parameter Implements ISyntaxKinds.Parameter Public ReadOnly Property TypeConstraint As Integer = SyntaxKind.TypeConstraint Implements ISyntaxKinds.TypeConstraint Public ReadOnly Property VariableDeclarator As Integer = SyntaxKind.VariableDeclarator Implements ISyntaxKinds.VariableDeclarator + Public ReadOnly Property FieldDeclaration As Integer = SyntaxKind.FieldDeclaration Implements ISyntaxKinds.FieldDeclaration Public ReadOnly Property ParameterList As Integer = SyntaxKind.ParameterList Implements ISyntaxKinds.ParameterList Public ReadOnly Property TypeArgumentList As Integer = SyntaxKind.TypeArgumentList Implements ISyntaxKinds.TypeArgumentList Public ReadOnly Property GlobalStatement As Integer? Implements ISyntaxKinds.GlobalStatement + Public ReadOnly Property EqualsValueClause As Integer = SyntaxKind.EqualsValue Implements ISyntaxKinds.EqualsValueClause + Public ReadOnly Property Interpolation As Integer = SyntaxKind.Interpolation Implements ISyntaxKinds.Interpolation Public ReadOnly Property InterpolatedStringExpression As Integer = SyntaxKind.InterpolatedStringExpression Implements ISyntaxKinds.InterpolatedStringExpression Public ReadOnly Property InterpolatedStringText As Integer = SyntaxKind.InterpolatedStringText Implements ISyntaxKinds.InterpolatedStringText diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index c2e776b00ed15..ce43f266c63a0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -201,7 +201,7 @@ private static CSharpSyntaxContext CreateContextWorker(Workspace? workspace, Sem var isDestructorTypeContext = targetToken.IsKind(SyntaxKind.TildeToken) && targetToken.Parent.IsKind(SyntaxKind.DestructorDeclaration) && - targetToken.Parent.Parent.IsKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration); + targetToken.Parent.Parent.IsKind(SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration); // Typing a dot after a numeric expression (numericExpression.) // - maybe a start of MemberAccessExpression like numericExpression.Member. @@ -321,11 +321,18 @@ public bool IsMemberAttributeContext(ISet validTypeDeclarations, Can var token = this.TargetToken; if (token.Kind() == SyntaxKind.OpenBracketToken && - token.Parent.IsKind(SyntaxKind.AttributeList) && - this.SyntaxTree.IsMemberDeclarationContext( - token.SpanStart, contextOpt: null, validModifiers: null, validTypeDeclarations: validTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + token.Parent.IsKind(SyntaxKind.AttributeList)) { - return true; + if (token.Parent.Parent is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } }) + { + return true; + } + + if (SyntaxTree.IsMemberDeclarationContext( + token.SpanStart, contextOpt: null, validModifiers: null, validTypeDeclarations: validTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + return true; + } } return false; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index 51da23b3c3795..f74395440d726 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -6,8 +6,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Formatting; -using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -457,7 +455,9 @@ public static bool IsTypeDeclarationContext( token = token.GetPreviousTokenIfTouchingWord(position); // a type decl can't come before usings/externs - if (originalToken.GetNextToken(includeSkipped: true).IsUsingOrExternKeyword()) + var nextToken = originalToken.GetNextToken(includeSkipped: true); + if (nextToken.IsUsingOrExternKeyword() || + (nextToken.Kind() == SyntaxKind.GlobalKeyword && nextToken.GetAncestor()?.GlobalKeyword == nextToken)) { return false; } @@ -611,11 +611,6 @@ public static bool IsTypeDeclarationContext( return true; } - if (token.IsKindOrHasMatchingText(SyntaxKind.DataKeyword)) - { - return true; - } - // using static | is never a type declaration context if (token.IsStaticKeywordInUsingDirective()) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs index d7e764548fb61..3daf95d5c0943 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs @@ -300,9 +300,10 @@ public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) } } - if (symbol.NullableAnnotation == NullableAnnotation.Annotated && - !symbol.IsValueType) + if (symbol is { IsValueType: false, NullableAnnotation: NullableAnnotation.Annotated }) { + // value type with nullable annotation may be composed from unconstrained nullable generic + // doesn't mean nullable value type in this case typeSyntax = AddInformationTo(SyntaxFactory.NullableType(typeSyntax), symbol); } @@ -355,8 +356,12 @@ public override TypeSyntax VisitPointerType(IPointerTypeSymbol symbol) public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol) { TypeSyntax typeSyntax = AddInformationTo(symbol.Name.ToIdentifierName(), symbol); - if (symbol.NullableAnnotation == NullableAnnotation.Annotated) + if (symbol is { IsValueType: false, NullableAnnotation: NullableAnnotation.Annotated }) + { + // value type with nullable annotation may be composed from unconstrained nullable generic + // doesn't mean nullable value type in this case typeSyntax = AddInformationTo(SyntaxFactory.NullableType(typeSyntax), symbol); + } return typeSyntax; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpAddImportsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpAddImportsService.cs index 2d0c06bb407ab..773278521e68d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpAddImportsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpAddImportsService.cs @@ -9,7 +9,6 @@ using System.Threading; using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs index 95638f77b5652..2a0c055bec68b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs @@ -26,10 +26,8 @@ namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryImports internal partial class CSharpRemoveUnnecessaryImportsService : AbstractRemoveUnnecessaryImportsService { - public static readonly CSharpRemoveUnnecessaryImportsService Instance = new(); - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Incorrectly used in production code: https://github.com/dotnet/roslyn/issues/42839")] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpRemoveUnnecessaryImportsService() { } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs index 675cd6ca71d44..464cc5b06ad77 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs @@ -132,6 +132,9 @@ internal static SyntaxTokenList GetParameterModifiers(RefKind refKind, bool forF _ => throw ExceptionUtilities.UnexpectedValue(refKind), }; + internal override SyntaxNode Type(ITypeSymbol typeSymbol, bool typeContext) + => typeContext ? typeSymbol.GenerateTypeSyntax() : typeSymbol.GenerateExpressionSyntax(); + #region Patterns internal override bool SupportsPatterns(ParseOptions options) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs index a946ffa7d3426..ef4cbce90fe0c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs @@ -73,6 +73,7 @@ internal class SyntaxKindSet SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, SyntaxKind.EnumDeclaration, }; @@ -82,6 +83,7 @@ internal class SyntaxKindSet SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, }; public static readonly ISet ClassInterfaceRecordTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) @@ -96,11 +98,13 @@ internal class SyntaxKindSet SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, }; public static readonly ISet StructOnlyTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, }; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs index 888238efefd53..976f450bbb254 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; @@ -17,13 +15,10 @@ internal abstract class SimpleCodeAction : CodeAction { public SimpleCodeAction( string title, - string? equivalenceKey = null, - IEnumerable? customTags = null) + string? equivalenceKey = null) { Title = title; EquivalenceKey = equivalenceKey; - - CustomTags = customTags.ToImmutableArrayOrEmpty(); } public sealed override string Title { get; } @@ -37,9 +32,8 @@ internal class DocumentChangeAction : SimpleCodeAction public DocumentChangeAction( string title, Func> createChangedDocument, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedDocument = createChangedDocument; } @@ -55,9 +49,8 @@ internal class SolutionChangeAction : SimpleCodeAction public SolutionChangeAction( string title, Func> createChangedSolution, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedSolution = createChangedSolution; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs index 06ab4e6c127a5..a08242af3caab 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs @@ -45,46 +45,26 @@ public static Project GetRequiredProject(this Solution solution, ProjectId proje } public static Document GetRequiredDocument(this Solution solution, DocumentId documentId) - => solution.GetDocument(documentId) ?? throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); + => solution.GetDocument(documentId) ?? throw CreateDocumentNotFoundException(); #if !CODE_STYLE - public static async Task GetRequiredDocumentAsync(this Solution solution, DocumentId documentId, bool includeSourceGenerated = false, CancellationToken cancellationToken = default) - => (await solution.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken).ConfigureAwait(false)) ?? throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); + => (await solution.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken).ConfigureAwait(false)) ?? throw CreateDocumentNotFoundException(); + public static async Task GetRequiredTextDocumentAsync(this Solution solution, DocumentId documentId, CancellationToken cancellationToken = default) + => (await solution.GetTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false)) ?? throw CreateDocumentNotFoundException(); #endif public static TextDocument GetRequiredAdditionalDocument(this Solution solution, DocumentId documentId) - { - var document = solution.GetAdditionalDocument(documentId); - if (document == null) - { - throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); - } - - return document; - } + => solution.GetAdditionalDocument(documentId) ?? throw CreateDocumentNotFoundException(); public static TextDocument GetRequiredAnalyzerConfigDocument(this Solution solution, DocumentId documentId) - { - var document = solution.GetAnalyzerConfigDocument(documentId); - if (document == null) - { - throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); - } - - return document; - } + => solution.GetAnalyzerConfigDocument(documentId) ?? throw CreateDocumentNotFoundException(); public static TextDocument GetRequiredTextDocument(this Solution solution, DocumentId documentId) - { - var document = solution.GetTextDocument(documentId); - if (document == null) - { - throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); - } + => solution.GetTextDocument(documentId) ?? throw CreateDocumentNotFoundException(); - return document; - } + private static Exception CreateDocumentNotFoundException() + => new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs index 379dbfcc2039f..bcfd8fbf938c9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs @@ -57,13 +57,14 @@ private static SyntaxNode CreateArgument( public static SyntaxNode GetDefaultEqualityComparer( this SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, Compilation compilation, ITypeSymbol type) { var equalityComparerType = compilation.EqualityComparerOfTType(); var typeExpression = equalityComparerType == null ? factory.GenericName(nameof(EqualityComparer), type) - : factory.TypeExpression(equalityComparerType.Construct(type)); + : generatorInternal.Type(equalityComparerType.Construct(type), typeContext: false); return factory.MemberAccessExpression(typeExpression, factory.IdentifierName(DefaultName)); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs index af133bb5d0cac..1ccf4c423bec3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs @@ -16,6 +16,7 @@ internal static partial class SyntaxGeneratorExtensions public static ImmutableArray GetGetHashCodeComponents( this SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, Compilation compilation, INamedTypeSymbol? containingType, ImmutableArray members, @@ -31,7 +32,7 @@ public static ImmutableArray GetGetHashCodeComponents( foreach (var member in members) { - result.Add(GetMemberForGetHashCode(factory, compilation, member, justMemberReference)); + result.Add(GetMemberForGetHashCode(factory, generatorInternal, compilation, member, justMemberReference)); } return result.ToImmutableAndFree(); @@ -84,7 +85,7 @@ public static ImmutableArray CreateGetHashCodeMethodStatements( bool useInt64) { var components = GetGetHashCodeComponents( - factory, compilation, containingType, members, justMemberReference: false); + factory, generatorInternal, compilation, containingType, members, justMemberReference: false); if (components.Length == 0) { @@ -215,6 +216,7 @@ where method.IsOverride && private static SyntaxNode GetMemberForGetHashCode( SyntaxGenerator factory, + SyntaxGeneratorInternal generatorInternal, Compilation compilation, ISymbol member, bool justMemberReference) @@ -243,7 +245,7 @@ private static SyntaxNode GetMemberForGetHashCode( { return factory.InvocationExpression( factory.MemberAccessExpression( - GetDefaultEqualityComparer(factory, compilation, GetType(compilation, member)), + GetDefaultEqualityComparer(factory, generatorInternal, compilation, GetType(compilation, member)), getHashCodeNameExpression), thisSymbol); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs index 8543fa1fd7ebf..2592e4dc04bf5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs @@ -30,7 +30,8 @@ internal abstract partial class AbstractSemanticFactsService : ISemanticFacts s.Kind == SymbolKind.Parameter || s.Kind == SymbolKind.RangeVariable || s.Kind == SymbolKind.Field || - s.Kind == SymbolKind.Property; + s.Kind == SymbolKind.Property || + (s.Kind == SymbolKind.NamedType && s.IsStatic); public SyntaxToken GenerateUniqueName( SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs index 86d20ee5c4377..a8ce4735408bd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs @@ -74,6 +74,23 @@ internal SyntaxNode LocalDeclarationStatement(SyntaxToken name, SyntaxNode initi internal abstract SyntaxNode InterpolationFormatClause(string format); internal abstract SyntaxNode TypeParameterList(IEnumerable typeParameterNames); + /// + /// Produces an appropriate TypeSyntax for the given . The + /// flag controls how this should be created depending on if this node is intended for use in a type-only + /// context, or an expression-level context. In the former case, both C# and VB will create QualifiedNameSyntax + /// nodes for dotted type names, whereas in the latter case both languages will create MemberAccessExpressionSyntax + /// nodes. The final stringified result will be the same in both cases. However, the structure of the trees + /// will be substantively different, which can impact how the compilation layers analyze the tree and how + /// transformational passes affect it. + /// + /// + /// Passing in the right value for is necessary for correctness and for use + /// of compilation (and other) layers in a supported fashion. For example, if a QualifiedTypeSyntax is + /// sed in a place the compiler would have parsed out a MemberAccessExpression, then it is undefined behavior + /// what will happen if that tree is passed to any other components. + /// + internal abstract SyntaxNode Type(ITypeSymbol typeSymbol, bool typeContext); + #region Patterns internal abstract bool SupportsPatterns(ParseOptions options); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSymbolDeclarationService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSymbolDeclarationService.vb index 4c08b36597d06..013c5160bf21e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSymbolDeclarationService.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSymbolDeclarationService.vb @@ -8,7 +8,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.LanguageServices Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic @@ -31,50 +30,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Function(r) New BlockSyntaxReference(r))) End Function - ''' - ''' If "node" is the begin statement of a declaration block, return that block, otherwise - ''' return node. - ''' - Private Shared Function GetBlockFromBegin(node As SyntaxNode) As SyntaxNode - Dim parent As SyntaxNode = node.Parent - Dim begin As SyntaxNode = Nothing - - If parent IsNot Nothing Then - Select Case parent.Kind - Case SyntaxKind.NamespaceBlock - begin = DirectCast(parent, NamespaceBlockSyntax).NamespaceStatement - - Case SyntaxKind.ModuleBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ClassBlock - begin = DirectCast(parent, TypeBlockSyntax).BlockStatement - - Case SyntaxKind.EnumBlock - begin = DirectCast(parent, EnumBlockSyntax).EnumStatement - - Case SyntaxKind.SubBlock, SyntaxKind.FunctionBlock, SyntaxKind.ConstructorBlock, - SyntaxKind.OperatorBlock, SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, - SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock, SyntaxKind.RaiseEventAccessorBlock - begin = DirectCast(parent, MethodBlockBaseSyntax).BlockStatement - - Case SyntaxKind.PropertyBlock - begin = DirectCast(parent, PropertyBlockSyntax).PropertyStatement - - Case SyntaxKind.EventBlock - begin = DirectCast(parent, EventBlockSyntax).EventStatement - - Case SyntaxKind.VariableDeclarator - If DirectCast(parent, VariableDeclaratorSyntax).Names.Count = 1 Then - begin = node - End If - End Select - End If - - If begin Is node Then - Return parent - Else - Return node - End If - End Function - Private Class BlockSyntaxReference Inherits SyntaxReference diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb index a1fc6954474e3..937c0a338f0ef 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb @@ -122,6 +122,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration typeParameterNames.Select(Function(n) SyntaxFactory.TypeParameter(n)))) End Function + Friend Overrides Function Type(typeSymbol As ITypeSymbol, typeContext As Boolean) As SyntaxNode + Return If(typeContext, typeSymbol.GenerateTypeSyntax(), typeSymbol.GenerateExpressionSyntax()) + End Function + #Region "Patterns" Friend Overrides Function SupportsPatterns(options As ParseOptions) As Boolean diff --git a/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/VisualBasicSyntaxClassificationService.vb b/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/VisualBasicSyntaxClassificationService.vb index 7c0de7573c6bf..cc7cfe5001f5c 100644 --- a/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/VisualBasicSyntaxClassificationService.vb +++ b/src/Workspaces/VisualBasic/Portable/Classification/SyntaxClassification/VisualBasicSyntaxClassificationService.vb @@ -44,8 +44,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification ClassificationHelpers.AddLexicalClassifications(text, textSpan, result, cancellationToken) End Sub - Public Overrides Sub AddSyntacticClassifications(syntaxTree As SyntaxTree, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) - Dim root = syntaxTree.GetRoot(cancellationToken) + Public Overrides Sub AddSyntacticClassifications(root As SyntaxNode, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) Worker.CollectClassifiedSpans(root, textSpan, result, cancellationToken) End Sub diff --git a/src/Workspaces/VisualBasic/Portable/Classification/VisualBasicClassificationService.vb b/src/Workspaces/VisualBasic/Portable/Classification/VisualBasicClassificationService.vb index c445aed728cee..94ed43b998979 100644 --- a/src/Workspaces/VisualBasic/Portable/Classification/VisualBasicClassificationService.vb +++ b/src/Workspaces/VisualBasic/Portable/Classification/VisualBasicClassificationService.vb @@ -19,11 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification Public Sub New() End Sub - Public Overrides Sub AddLexicalClassifications(text As SourceText, textSpan As TextSpan, result As List(Of ClassifiedSpan), cancellationToken As CancellationToken) - Dim temp = ArrayBuilder(Of ClassifiedSpan).GetInstance() - ClassificationHelpers.AddLexicalClassifications(text, textSpan, temp, cancellationToken) - AddRange(temp, result) - temp.Free() + Public Overrides Sub AddLexicalClassifications(text As SourceText, textSpan As TextSpan, result As ArrayBuilder(Of ClassifiedSpan), cancellationToken As CancellationToken) + ClassificationHelpers.AddLexicalClassifications(text, textSpan, result, cancellationToken) End Sub Public Overrides Function AdjustStaleClassification(text As SourceText, classifiedSpan As ClassifiedSpan) As ClassifiedSpan diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index d9ee4bddcda2d..7f01fc3b020c4 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -290,7 +290,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Friend Overrides Function MemberAccessExpressionWorker(expression As SyntaxNode, simpleName As SyntaxNode) As SyntaxNode Return SyntaxFactory.SimpleMemberAccessExpression( - ParenthesizeLeft(expression), + If(expression IsNot Nothing, ParenthesizeLeft(expression), Nothing), SyntaxFactory.Token(SyntaxKind.DotToken), DirectCast(simpleName, SimpleNameSyntax)) End Function diff --git a/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.vb b/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.vb index 1ab6a292dae38..70fc7b223c7ac 100644 --- a/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.vb +++ b/src/Workspaces/VisualBasic/Portable/Indentation/VisualBasicIndentationService.vb @@ -17,13 +17,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Indentation Partial Friend NotInheritable Class VisualBasicIndentationService Inherits AbstractIndentationService(Of CompilationUnitSyntax) - Public Shared ReadOnly DefaultInstance As New VisualBasicIndentationService() Public Shared ReadOnly WithoutParameterAlignmentInstance As New VisualBasicIndentationService(NoOpFormattingRule.Instance) Private ReadOnly _specializedIndentationRule As AbstractFormattingRule - + Public Sub New() Me.New(Nothing) End Sub diff --git a/src/Workspaces/VisualBasic/Portable/ReassignedVariable/VisualBasicReassignedVariableService.vb b/src/Workspaces/VisualBasic/Portable/ReassignedVariable/VisualBasicReassignedVariableService.vb new file mode 100644 index 0000000000000..3777b790d3bdd --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/ReassignedVariable/VisualBasicReassignedVariableService.vb @@ -0,0 +1,58 @@ +' 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.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.ReassignedVariable +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.ReassignedVariable + + Friend Class VisualBasicReassignedVariableService + Inherits AbstractReassignedVariableService(Of + ParameterSyntax, + ModifiedIdentifierSyntax, + ModifiedIdentifierSyntax, + IdentifierNameSyntax) + + + + Public Sub New() + End Sub + + Protected Overrides Function GetIdentifierOfVariable(variable As ModifiedIdentifierSyntax) As SyntaxToken + Return variable.Identifier + End Function + + Protected Overrides Function GetIdentifierOfSingleVariableDesignation(variable As ModifiedIdentifierSyntax) As SyntaxToken + Return variable.Identifier + End Function + + Protected Overrides Function GetMemberBlock(methodOrPropertyDeclaration As SyntaxNode) As SyntaxNode + Return methodOrPropertyDeclaration.GetBlockFromBegin() + End Function + + Protected Overrides Function HasInitializer(variable As SyntaxNode) As Boolean + Return TryCast(variable.Parent, VariableDeclaratorSyntax)?.Initializer IsNot Nothing + End Function + + Protected Overrides Function GetParentScope(localDeclaration As SyntaxNode) As SyntaxNode + Dim current = localDeclaration + While current IsNot Nothing + If TypeOf current Is StatementSyntax Then + Exit While + End If + + current = current.Parent + End While + + If TypeOf current Is LocalDeclarationStatementSyntax Then + Return current.Parent + End If + + Return current + End Function + End Class +End Namespace +